import { describe, expect, it } from "@effect/vitest";import { Effect } from "effect"; import { OpenApiExtractionError } from "./errors";import { previewSpecText, previewSpecTextStreaming } from "./preview";import type { SpecPreview } from "./preview"; // Streamable block-YAML fixture: parameter/schema `$ref`s, a path-level// parameter, a deprecated operation, top-level security, and a transitive// schema chain (WidgetList → Widget → Owner) for the response-field walk.const fixture = `openapi: 3.0.4info: title: Streamed Fixture version: 1.2.3 description: Fixture for streaming preview parityservers: - url: https://api.example.com/v1security: - appAuth: []paths: /widgets: get: operationId: widgets.list summary: List widgets tags: - widgets parameters: - $ref: '#/components/parameters/PageSize' responses: "200": description: OK content: application/json: schema: $ref: '#/components/schemas/WidgetList' post: operationId: widgets.create summary: Create a widget tags: - widgets requestBody: content: application/json: schema: $ref: '#/components/schemas/Widget' responses: "201": description: Created content: application/json: schema: $ref: '#/components/schemas/Widget' /widgets/{widget-id}: parameters: - name: widget-id in: path required: true schema: type: string get: operationId: widgets.get deprecated: true tags: - widgets - detail responses: "200": description: OK content: application/json: schema: $ref: '#/components/schemas/Widget'components: parameters: PageSize: name: pageSize in: query required: false description: Page size schema: type: integer securitySchemes: appAuth: type: oauth2 flows: authorizationCode: authorizationUrl: https://auth.example.com/authorize tokenUrl: https://auth.example.com/token scopes: widgets.read: Read widgets schemas: WidgetList: type: object properties: items: type: array items: $ref: '#/components/schemas/Widget' Widget: type: object properties: id: type: string owner: $ref: '#/components/schemas/Owner' Owner: type: object properties: name: type: string`; const sortedOperations = (preview: SpecPreview) => [...preview.operations].sort((a, b) => `${a.operationId}:${a.method}`.localeCompare(`${b.operationId}:${b.method}`), ); describe("previewSpecTextStreaming", () => { it.effect("matches the whole-document preview on a streamable spec", () => Effect.gen(function* () { const whole = yield* previewSpecText(fixture); const streamed = yield* previewSpecTextStreaming(fixture); expect(streamed.title).toEqual(whole.title); expect(streamed.description).toEqual(whole.description); expect(streamed.version).toEqual(whole.version); expect(streamed.servers).toEqual(whole.servers); expect(streamed.operationCount).toBe(whole.operationCount); expect(streamed.tags).toEqual(whole.tags); expect(streamed.securitySchemes).toEqual(whole.securitySchemes); expect(streamed.authStrategies).toEqual(whole.authStrategies); expect(streamed.headerPresets).toEqual(whole.headerPresets); expect(streamed.oauth2Presets).toEqual(whole.oauth2Presets); expect(streamed.healthCheckCandidates).toEqual(whole.healthCheckCandidates); expect(sortedOperations(streamed)).toEqual(sortedOperations(whole)); }), ); it.effect("projects response fields through the transitive schema closure", () => Effect.gen(function* () { const streamed = yield* previewSpecTextStreaming(fixture); const listCandidate = streamed.healthCheckCandidates.find( (candidate) => candidate.method === "get" && candidate.operation.endsWith("list"), ); expect(listCandidate).toBeDefined(); expect(listCandidate!.responseFields ?? []).not.toHaveLength(0); }), ); it.effect("applies the keep filter to counts, tags, and candidates", () => Effect.gen(function* () { const streamed = yield* previewSpecTextStreaming(fixture, (path, pathItem) => path === "/widgets/{widget-id}" ? null : pathItem, ); expect(streamed.operationCount).toBe(2); expect(streamed.tags).toEqual(["widgets"]); expect( streamed.operations.every((operation) => operation.operationId !== "widgets.get"), ).toBe(true); expect( streamed.healthCheckCandidates.every((candidate) => candidate.method !== "delete"), ).toBe(true); expect(streamed.healthCheckCandidates).toHaveLength(2); }), ); it.effect("fails cleanly on a spec outside the streamable profile", () => Effect.gen(function* () { const error = yield* previewSpecTextStreaming( JSON.stringify({ openapi: "3.1.0", info: { title: "Inline", version: "1.0.0" }, paths: {}, }), ).pipe(Effect.flip); expect(error).toBeInstanceOf(OpenApiExtractionError); expect(error).toHaveProperty("message", expect.stringMatching(/streamable/)); }), );});
import { describe, expect, it } from "@effect/vitest";
import { Effect } from "effect";
import { OpenApiExtractionError } from "./errors";
import { previewSpecText, previewSpecTextStreaming } from "./preview";
import type { SpecPreview } from "./preview";
// Streamable block-YAML fixture: parameter/schema `$ref`s, a path-level
// parameter, a deprecated operation, top-level security, and a transitive
// schema chain (WidgetList → Widget → Owner) for the response-field walk.
const fixture = `openapi: 3.0.4
info:
title: Streamed Fixture
version: 1.2.3
description: Fixture for streaming preview parity
servers:
- url: https://api.example.com/v1
security:
- appAuth: []
paths:
/widgets:
get:
operationId: widgets.list
summary: List widgets
tags:
- widgets
parameters:
- $ref: '#/components/parameters/PageSize'
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/WidgetList'
post:
operationId: widgets.create
summary: Create a widget
tags:
- widgets
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/Widget'
responses:
"201":
description: Created
content:
application/json:
schema:
$ref: '#/components/schemas/Widget'
/widgets/{widget-id}:
parameters:
- name: widget-id
in: path
required: true
schema:
type: string
get:
operationId: widgets.get
deprecated: true
tags:
- widgets
- detail
responses:
"200":
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/Widget'
components:
parameters:
PageSize:
name: pageSize
in: query
required: false
description: Page size
schema:
type: integer
securitySchemes:
appAuth:
type: oauth2
flows:
authorizationCode:
authorizationUrl: https://auth.example.com/authorize
tokenUrl: https://auth.example.com/token
scopes:
widgets.read: Read widgets
schemas:
WidgetList:
type: object
properties:
items:
type: array
items:
$ref: '#/components/schemas/Widget'
Widget:
type: object
properties:
id:
type: string
owner:
$ref: '#/components/schemas/Owner'
Owner:
type: object
properties:
name:
type: string
`;
const sortedOperations = (preview: SpecPreview) =>
[...preview.operations].sort((a, b) =>
`${a.operationId}:${a.method}`.localeCompare(`${b.operationId}:${b.method}`),
);
describe("previewSpecTextStreaming", () => {
it.effect("matches the whole-document preview on a streamable spec", () =>
Effect.gen(function* () {
const whole = yield* previewSpecText(fixture);
const streamed = yield* previewSpecTextStreaming(fixture);
expect(streamed.title).toEqual(whole.title);
expect(streamed.description).toEqual(whole.description);
expect(streamed.version).toEqual(whole.version);
expect(streamed.servers).toEqual(whole.servers);
expect(streamed.operationCount).toBe(whole.operationCount);
expect(streamed.tags).toEqual(whole.tags);
expect(streamed.securitySchemes).toEqual(whole.securitySchemes);
expect(streamed.authStrategies).toEqual(whole.authStrategies);
expect(streamed.headerPresets).toEqual(whole.headerPresets);
expect(streamed.oauth2Presets).toEqual(whole.oauth2Presets);
expect(streamed.healthCheckCandidates).toEqual(whole.healthCheckCandidates);
expect(sortedOperations(streamed)).toEqual(sortedOperations(whole));
}),
);
it.effect("projects response fields through the transitive schema closure", () =>
Effect.gen(function* () {
const streamed = yield* previewSpecTextStreaming(fixture);
const listCandidate = streamed.healthCheckCandidates.find(
(candidate) => candidate.method === "get" && candidate.operation.endsWith("list"),
);
expect(listCandidate).toBeDefined();
expect(listCandidate!.responseFields ?? []).not.toHaveLength(0);
}),
);
it.effect("applies the keep filter to counts, tags, and candidates", () =>
Effect.gen(function* () {
const streamed = yield* previewSpecTextStreaming(fixture, (path, pathItem) =>
path === "/widgets/{widget-id}" ? null : pathItem,
);
expect(streamed.operationCount).toBe(2);
expect(streamed.tags).toEqual(["widgets"]);
expect(
streamed.operations.every((operation) => operation.operationId !== "widgets.get"),
).toBe(true);
expect(
streamed.healthCheckCandidates.every((candidate) => candidate.method !== "delete"),
).toBe(true);
expect(streamed.healthCheckCandidates).toHaveLength(2);
}),
);
it.effect("fails cleanly on a spec outside the streamable profile", () =>
Effect.gen(function* () {
const error = yield* previewSpecTextStreaming(
JSON.stringify({
openapi: "3.1.0",
info: { title: "Inline", version: "1.0.0" },
paths: {},
}),
).pipe(Effect.flip);
expect(error).toBeInstanceOf(OpenApiExtractionError);
expect(error).toHaveProperty("message", expect.stringMatching(/streamable/));
}),
);
});