Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions tests/features.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { buildIR } from "../src/ir.js";
import { generateTargets } from "../src/generators/index.js";
import { renderConnectFiles } from "../src/connectgen.js";
import { paginationShape } from "../src/generators/common.js";
import { renderMcpFiles } from "../src/mcpgen.js";

const SPEC = {
openapi: "3.0.0",
Expand Down Expand Up @@ -114,6 +115,81 @@ function buildFeatureIR() {
});
}

function buildXquikMcpIR() {
const spec = {
openapi: "3.1.0",
info: { title: "Xquik API", version: "1.0" },
servers: [{ url: "https://xquik.com" }],
components: {
securitySchemes: {
ApiKeyAuth: { type: "apiKey", in: "header", name: "x-api-key" },
},
schemas: {
SearchResults: {
type: "object",
properties: {
data: { type: "array", items: { type: "object" } },
},
},
},
},
paths: {
"/api/v1/x/tweets/search": {
get: {
operationId: "searchTweets",
tags: ["x"],
summary: "Search public tweets",
parameters: [
{ name: "q", in: "query", required: true, schema: { type: "string" } },
{ name: "limit", in: "query", schema: { type: "integer" } },
],
responses: {
"200": {
description: "Search results",
content: { "application/json": { schema: { $ref: "#/components/schemas/SearchResults" } } },
},
},
security: [{ ApiKeyAuth: [] }],
},
},
},
};
const config = {
sdkgen: 1,
project: { name: "xquik", display_name: "Xquik" },
spec: { path: "./openapi.yaml" },
targets: {
typescript: { package_name: "@example/xquik" },
},
client: {
class_name: "Xquik",
env_prefix: "XQUIK",
base_url: { default: "https://xquik.com" },
auth: {
api_key: {
env: "XQUIK_API_KEY",
security_scheme: "ApiKeyAuth",
header: "x-api-key",
},
},
},
mcp: {
tools: "typed",
permissions: {
allow_http_gets: true,
},
},
};

return buildIR({
config: config as never,
configRaw: JSON.stringify(config),
spec: spec as never,
specRaw: JSON.stringify(spec),
diagnostics: [],
});
}

async function read(dir: string, rel: string): Promise<string> {
return readFile(join(dir, rel), "utf8");
}
Expand Down Expand Up @@ -156,6 +232,17 @@ async function main(): Promise<void> {
assert.equal(ir.client.oauth2?.device_authorization_url, "/oauth/device", "oauth device_authorization_url");
assert.ok(ir.client.auth?.basic, "basic auth configured");

// MCP auth: API-key header APIs should not fall back to bearer auth.
const xquikMcpServer = renderMcpFiles(buildXquikMcpIR())["src/server.ts"] ?? "";
assert.match(xquikMcpServer, /"bearerEnv": null/, "MCP disables bearer fallback");
assert.match(xquikMcpServer, /"name": "x-api-key"/, "MCP keeps API-key header name");
assert.match(xquikMcpServer, /"env": "XQUIK_API_KEY"/, "MCP keeps API-key env");
assert.match(
xquikMcpServer,
/headers\[authConfig\.apiKeyHeader\.name\.toLowerCase\(\)\] = key/,
"MCP applies API-key header",
);

// --- Generated output across languages ---
const dir = await mkdtemp("/tmp/feat-test-");
try {
Expand Down