-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathopenapi-provider.test.ts
More file actions
98 lines (86 loc) · 4.63 KB
/
Copy pathopenapi-provider.test.ts
File metadata and controls
98 lines (86 loc) · 4.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
//
// ADR-0097 — the `openapi` provider factory: materialize a declarative
// `provider: 'openapi'` connector instance from a spec document + resolved auth.
import { describe, it, expect } from 'vitest';
import type { ConnectorProviderContext } from '@objectstack/spec/integration';
import { createOpenApiProviderFactory, OPENAPI_PROVIDER_KEY } from './openapi-provider.js';
const petstore = {
openapi: '3.0.0',
info: { title: 'Petstore', version: '1.0.0' },
servers: [{ url: 'https://petstore.example.com' }],
paths: {
'/pets': {
get: { operationId: 'listPets', summary: 'List pets', responses: { '200': { description: 'ok' } } },
},
},
};
function ctx(partial: Partial<ConnectorProviderContext> & Pick<ConnectorProviderContext, 'providerConfig'>): ConnectorProviderContext {
return { name: 'pets', label: 'Pets', type: 'api', ...partial };
}
describe('openapi provider factory (ADR-0097)', () => {
it('advertises the openapi provider key', () => {
expect(OPENAPI_PROVIDER_KEY).toBe('openapi');
});
it('materializes actions from an inline OpenAPI document (no network at boot)', async () => {
const factory = createOpenApiProviderFactory();
const { def, handlers } = await factory(ctx({ providerConfig: { spec: petstore } }));
expect(def.name).toBe('pets');
expect(Object.keys(handlers)).toEqual(['listPets']);
expect(def.actions?.map((a) => a.key)).toEqual(['listPets']);
});
it('fetches the document when spec is an http(s) URL, via the injected fetch', async () => {
let fetched: string | undefined;
const fetchImpl = (async (url: string) => {
fetched = url;
return { ok: true, status: 200, json: async () => petstore } as unknown as Response;
}) as unknown as typeof fetch;
const factory = createOpenApiProviderFactory({ fetchImpl });
const { def } = await factory(ctx({ providerConfig: { spec: 'https://petstore.example.com/openapi.json' } }));
expect(fetched).toBe('https://petstore.example.com/openapi.json');
expect(def.actions?.map((a) => a.key)).toEqual(['listPets']);
});
it('throws when spec is missing', async () => {
const factory = createOpenApiProviderFactory();
await expect(factory(ctx({ providerConfig: {} }))).rejects.toThrow(/providerConfig\.spec/);
});
// ── File-path specs (#3016 — ADR-0097 follow-up) ────────────────────────
it('reads a file-path spec through the host loadPackageFile capability', async () => {
const requested: string[] = [];
const loadPackageFile = async (rel: string) => {
requested.push(rel);
return JSON.stringify(petstore);
};
const factory = createOpenApiProviderFactory();
const { def, handlers } = await factory(
ctx({ providerConfig: { spec: './specs/petstore.json' }, loadPackageFile }),
);
expect(requested).toEqual(['./specs/petstore.json']);
expect(def.actions?.map((a) => a.key)).toEqual(['listPets']);
expect(Object.keys(handlers)).toEqual(['listPets']);
});
it('surfaces a loader failure (missing file / traversal rejection) with the connector name', async () => {
const loadPackageFile = async (rel: string) => {
throw new Error(`package file ref '${rel}' could not be read`);
};
const factory = createOpenApiProviderFactory();
await expect(
factory(ctx({ providerConfig: { spec: './missing.json' }, loadPackageFile })),
).rejects.toThrow(/'pets' failed to read providerConfig\.spec '\.\/missing\.json'.*could not be read/s);
});
it('rejects an unparseable file-path spec with a clear message', async () => {
const factory = createOpenApiProviderFactory();
await expect(
factory(ctx({ providerConfig: { spec: './broken.json' }, loadPackageFile: async () => 'not-json{' })),
).rejects.toThrow(/not a parseable.*OpenAPI JSON document/s);
await expect(
factory(ctx({ providerConfig: { spec: './array.json' }, loadPackageFile: async () => '[1,2]' })),
).rejects.toThrow(/not a parseable.*OpenAPI JSON document/s);
});
it('rejects a file-path spec with a clear message when the host has no package file access', async () => {
const factory = createOpenApiProviderFactory();
await expect(
factory(ctx({ providerConfig: { spec: './petstore.json' } })),
).rejects.toThrow(/no package file access/);
});
});