@ankhorage/data-sources 1.0.1 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +6 -41
  3. package/dist/ankh.provider.js +1 -1
  4. package/dist/ankh.provider.js.map +1 -1
  5. package/dist/discovery/http.d.ts +10 -0
  6. package/dist/discovery/http.d.ts.map +1 -0
  7. package/dist/discovery/http.js +35 -0
  8. package/dist/discovery/http.js.map +1 -0
  9. package/dist/discovery/index.d.ts +9 -9
  10. package/dist/discovery/index.d.ts.map +1 -1
  11. package/dist/discovery/index.js +27 -55
  12. package/dist/discovery/index.js.map +1 -1
  13. package/dist/graphql/index.d.ts +8 -76
  14. package/dist/graphql/index.d.ts.map +1 -1
  15. package/dist/graphql/index.js +17 -197
  16. package/dist/graphql/index.js.map +1 -1
  17. package/dist/graphql/introspection.d.ts +5 -0
  18. package/dist/graphql/introspection.d.ts.map +1 -0
  19. package/dist/graphql/introspection.js +136 -0
  20. package/dist/graphql/introspection.js.map +1 -0
  21. package/dist/graphql/operation.d.ts +5 -0
  22. package/dist/graphql/operation.d.ts.map +1 -0
  23. package/dist/graphql/operation.js +39 -0
  24. package/dist/graphql/operation.js.map +1 -0
  25. package/dist/graphql/types.d.ts +71 -0
  26. package/dist/graphql/types.d.ts.map +1 -0
  27. package/dist/graphql/types.js +2 -0
  28. package/dist/graphql/types.js.map +1 -0
  29. package/dist/index.d.ts +1 -17
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +1 -34
  32. package/dist/index.js.map +1 -1
  33. package/dist/openapi/endpoints.d.ts +5 -0
  34. package/dist/openapi/endpoints.d.ts.map +1 -0
  35. package/dist/openapi/endpoints.js +77 -0
  36. package/dist/openapi/endpoints.js.map +1 -0
  37. package/dist/openapi/index.d.ts +7 -87
  38. package/dist/openapi/index.d.ts.map +1 -1
  39. package/dist/openapi/index.js +8 -267
  40. package/dist/openapi/index.js.map +1 -1
  41. package/dist/openapi/operation.d.ts +5 -0
  42. package/dist/openapi/operation.d.ts.map +1 -0
  43. package/dist/openapi/operation.js +141 -0
  44. package/dist/openapi/operation.js.map +1 -0
  45. package/dist/openapi/schema.d.ts +5 -0
  46. package/dist/openapi/schema.d.ts.map +1 -0
  47. package/dist/openapi/schema.js +84 -0
  48. package/dist/openapi/schema.js.map +1 -0
  49. package/dist/openapi/types.d.ts +82 -0
  50. package/dist/openapi/types.d.ts.map +1 -0
  51. package/dist/openapi/types.js +2 -0
  52. package/dist/openapi/types.js.map +1 -0
  53. package/dist/packageInfo.d.ts +18 -0
  54. package/dist/packageInfo.d.ts.map +1 -0
  55. package/dist/packageInfo.js +30 -0
  56. package/dist/packageInfo.js.map +1 -0
  57. package/dist/rest/index.d.ts +5 -5
  58. package/dist/rest/index.d.ts.map +1 -1
  59. package/dist/rest/index.js +49 -48
  60. package/dist/rest/index.js.map +1 -1
  61. package/dist/test-runner/index.d.ts +2 -57
  62. package/dist/test-runner/index.d.ts.map +1 -1
  63. package/dist/test-runner/index.js +58 -160
  64. package/dist/test-runner/index.js.map +1 -1
  65. package/dist/test-runner/response.d.ts +3 -0
  66. package/dist/test-runner/response.d.ts.map +1 -0
  67. package/dist/test-runner/response.js +29 -0
  68. package/dist/test-runner/response.js.map +1 -0
  69. package/dist/test-runner/result.d.ts +6 -0
  70. package/dist/test-runner/result.d.ts.map +1 -0
  71. package/dist/test-runner/result.js +17 -0
  72. package/dist/test-runner/result.js.map +1 -0
  73. package/dist/test-runner/serialization.d.ts +12 -0
  74. package/dist/test-runner/serialization.d.ts.map +1 -0
  75. package/dist/test-runner/serialization.js +99 -0
  76. package/dist/test-runner/serialization.js.map +1 -0
  77. package/dist/test-runner/types.d.ts +58 -0
  78. package/dist/test-runner/types.d.ts.map +1 -0
  79. package/dist/test-runner/types.js +2 -0
  80. package/dist/test-runner/types.js.map +1 -0
  81. package/package.json +12 -13
  82. package/src/ankh.provider.ts +1 -1
  83. package/src/api-phase1.contract.test.ts +24 -0
  84. package/src/discovery/http.ts +38 -0
  85. package/src/discovery/index.test.ts +76 -75
  86. package/src/discovery/index.ts +49 -72
  87. package/src/graphql/index.test.ts +10 -11
  88. package/src/graphql/index.ts +35 -315
  89. package/src/graphql/introspection.ts +163 -0
  90. package/src/graphql/operation.ts +60 -0
  91. package/src/graphql/types.ts +79 -0
  92. package/src/index.test.ts +21 -13
  93. package/src/index.ts +1 -54
  94. package/src/openapi/endpoints.ts +124 -0
  95. package/src/openapi/index.test.ts +47 -47
  96. package/src/openapi/index.ts +12 -467
  97. package/src/openapi/operation.ts +231 -0
  98. package/src/openapi/schema.ts +127 -0
  99. package/src/openapi/types.ts +93 -0
  100. package/src/packageInfo.ts +50 -0
  101. package/src/rest/index.test.ts +17 -14
  102. package/src/rest/index.ts +67 -61
  103. package/src/test-runner/index.test.ts +153 -120
  104. package/src/test-runner/index.ts +97 -299
  105. package/src/test-runner/response.ts +32 -0
  106. package/src/test-runner/result.ts +35 -0
  107. package/src/test-runner/serialization.ts +151 -0
  108. package/src/test-runner/types.ts +81 -0
  109. package/dist/generated-api/index.d.ts +0 -10
  110. package/dist/generated-api/index.d.ts.map +0 -1
  111. package/dist/generated-api/index.js +0 -274
  112. package/dist/generated-api/index.js.map +0 -1
  113. package/dist/generated-api/validateGeneratedApiCollection.d.ts +0 -3
  114. package/dist/generated-api/validateGeneratedApiCollection.d.ts.map +0 -1
  115. package/dist/generated-api/validateGeneratedApiCollection.js +0 -35
  116. package/dist/generated-api/validateGeneratedApiCollection.js.map +0 -1
  117. package/src/generated-api/index.test.ts +0 -173
  118. package/src/generated-api/index.ts +0 -414
  119. package/src/generated-api/validateGeneratedApiCollection.test.ts +0 -91
  120. package/src/generated-api/validateGeneratedApiCollection.ts +0 -76
@@ -0,0 +1,79 @@
1
+ import type {
2
+ CredentialRef,
3
+ DataContractValue,
4
+ DataSchema,
5
+ OperationId,
6
+ } from '@ankhorage/contracts/data';
7
+
8
+ export type GraphQlOperationKind = 'mutation' | 'query' | 'subscription';
9
+
10
+ export interface GraphQlIntrospectionTypeRef {
11
+ readonly kind: string;
12
+ readonly name?: string | null;
13
+ readonly ofType?: GraphQlIntrospectionTypeRef | null;
14
+ }
15
+
16
+ export interface GraphQlIntrospectionInputValue {
17
+ readonly name: string;
18
+ readonly description?: string | null;
19
+ readonly type: GraphQlIntrospectionTypeRef;
20
+ readonly defaultValue?: string | null;
21
+ }
22
+
23
+ export interface GraphQlIntrospectionField {
24
+ readonly name: string;
25
+ readonly description?: string | null;
26
+ readonly args?: readonly GraphQlIntrospectionInputValue[] | null;
27
+ readonly type: GraphQlIntrospectionTypeRef;
28
+ }
29
+
30
+ export interface GraphQlIntrospectionEnumValue {
31
+ readonly name: string;
32
+ readonly description?: string | null;
33
+ }
34
+
35
+ export interface GraphQlIntrospectionType {
36
+ readonly kind: string;
37
+ readonly name?: string | null;
38
+ readonly description?: string | null;
39
+ readonly fields?: readonly GraphQlIntrospectionField[] | null;
40
+ readonly inputFields?: readonly GraphQlIntrospectionInputValue[] | null;
41
+ readonly enumValues?: readonly GraphQlIntrospectionEnumValue[] | null;
42
+ readonly possibleTypes?: readonly GraphQlIntrospectionTypeRef[] | null;
43
+ }
44
+
45
+ export interface GraphQlIntrospectionSchema {
46
+ readonly queryType?: { readonly name?: string | null } | null;
47
+ readonly mutationType?: { readonly name?: string | null } | null;
48
+ readonly subscriptionType?: { readonly name?: string | null } | null;
49
+ readonly types?: readonly GraphQlIntrospectionType[] | null;
50
+ }
51
+
52
+ export interface GraphQlIntrospectionResult {
53
+ readonly __schema?: GraphQlIntrospectionSchema;
54
+ }
55
+
56
+ export interface GraphQlOperationDefinition {
57
+ readonly id: OperationId;
58
+ readonly kind: GraphQlOperationKind;
59
+ readonly name?: string;
60
+ readonly description?: string;
61
+ readonly document?: string;
62
+ readonly variables?: DataSchema;
63
+ readonly response?: DataSchema;
64
+ readonly selectionPath?: string;
65
+ readonly metadata?: DataContractValue;
66
+ }
67
+
68
+ export interface GraphQlApiDefinition {
69
+ readonly id: string;
70
+ readonly endpointUrl: string;
71
+ readonly name?: string;
72
+ readonly description?: string;
73
+ readonly credential?: CredentialRef;
74
+ readonly introspection?: GraphQlIntrospectionResult;
75
+ readonly introspectionEnabled?: boolean;
76
+ readonly schemaVersion?: string;
77
+ readonly operations?: readonly GraphQlOperationDefinition[];
78
+ readonly metadata?: DataContractValue;
79
+ }
package/src/index.test.ts CHANGED
@@ -1,4 +1,7 @@
1
- import type { ExternalRestApiDataSourceConfig } from '@ankhorage/contracts/data';
1
+ import type {
2
+ DatabaseDataSourceConfig,
3
+ ExternalRestApiDefinition,
4
+ } from '@ankhorage/contracts/data';
2
5
  import { describe, expect, it } from 'bun:test';
3
6
 
4
7
  import {
@@ -11,36 +14,41 @@ import {
11
14
  } from './index';
12
15
 
13
16
  describe('data-sources package model', () => {
14
- it('exports orthogonal package capabilities', () => {
17
+ it('exports canonical API and database capabilities separately', () => {
15
18
  expect(getDataSourcesPackageInfo()).toEqual({
16
19
  packageName: DATA_SOURCES_PACKAGE_NAME,
17
- supportedKinds: ['api', 'database'],
18
- supportedApiOrigins: ['external', 'generated'],
20
+ supportedKinds: ['database'],
21
+ supportedApiOrigins: ['external'],
19
22
  supportedApiProtocols: ['graphql', 'rest'],
20
23
  });
21
24
  });
22
25
 
23
- it('recognizes only canonical source dimensions', () => {
24
- expect(isSupportedDataSourceKind('api')).toBe(true);
26
+ it('recognizes only currently executable API origins', () => {
25
27
  expect(isSupportedDataSourceKind('database')).toBe(true);
26
- expect(isSupportedDataSourceKind('managed-api')).toBe(false);
27
- expect(isSupportedDataSourceKind('openapi')).toBe(false);
28
+ expect(isSupportedDataSourceKind('api')).toBe(false);
28
29
  expect(isSupportedApiOrigin('external')).toBe(true);
29
- expect(isSupportedApiOrigin('generated')).toBe(true);
30
+ expect(isSupportedApiOrigin('internal')).toBe(false);
31
+ expect(isSupportedApiOrigin('generated')).toBe(false);
30
32
  expect(isSupportedApiProtocol('rest')).toBe(true);
31
33
  expect(isSupportedApiProtocol('graphql')).toBe(true);
32
34
  });
33
35
 
34
- it('consumes canonical contracts from @ankhorage/contracts', () => {
35
- const source: ExternalRestApiDataSourceConfig = {
36
+ it('consumes canonical API contracts from @ankhorage/contracts', () => {
37
+ const api: ExternalRestApiDefinition = {
36
38
  id: 'cms',
37
- kind: 'api',
38
39
  origin: 'external',
39
40
  protocol: 'rest',
40
41
  baseUrl: 'https://cms.example.com',
41
42
  endpoints: {},
42
43
  };
44
+ const source: DatabaseDataSourceConfig = {
45
+ id: 'primary-db',
46
+ kind: 'database',
47
+ adapter: { id: 'database-adapter', kind: 'database' },
48
+ endpoints: {},
49
+ };
43
50
 
44
- expect(getDataSourceKind(source)).toBe('api');
51
+ expect(api.origin).toBe('external');
52
+ expect(getDataSourceKind(source)).toBe('database');
45
53
  });
46
54
  });
package/src/index.ts CHANGED
@@ -1,59 +1,6 @@
1
- import type {
2
- ApiOrigin,
3
- ApiProtocol,
4
- DataSourceConfig,
5
- DataSourceKind,
6
- } from '@ankhorage/contracts/data';
7
-
8
1
  export * from './discovery';
9
- export * from './generated-api';
10
2
  export * from './graphql';
11
3
  export * from './openapi';
4
+ export * from './packageInfo';
12
5
  export * from './rest';
13
6
  export * from './test-runner';
14
-
15
- export const DATA_SOURCES_PACKAGE_NAME = '@ankhorage/data-sources' as const;
16
- export const SUPPORTED_DATA_SOURCE_KINDS = [
17
- 'api',
18
- 'database',
19
- ] as const satisfies readonly DataSourceKind[];
20
- export const SUPPORTED_API_ORIGINS = [
21
- 'external',
22
- 'generated',
23
- ] as const satisfies readonly ApiOrigin[];
24
- export const SUPPORTED_API_PROTOCOLS = [
25
- 'graphql',
26
- 'rest',
27
- ] as const satisfies readonly ApiProtocol[];
28
-
29
- export interface DataSourcesPackageInfo {
30
- readonly packageName: typeof DATA_SOURCES_PACKAGE_NAME;
31
- readonly supportedKinds: typeof SUPPORTED_DATA_SOURCE_KINDS;
32
- readonly supportedApiOrigins: typeof SUPPORTED_API_ORIGINS;
33
- readonly supportedApiProtocols: typeof SUPPORTED_API_PROTOCOLS;
34
- }
35
-
36
- export function getDataSourcesPackageInfo(): DataSourcesPackageInfo {
37
- return {
38
- packageName: DATA_SOURCES_PACKAGE_NAME,
39
- supportedKinds: SUPPORTED_DATA_SOURCE_KINDS,
40
- supportedApiOrigins: SUPPORTED_API_ORIGINS,
41
- supportedApiProtocols: SUPPORTED_API_PROTOCOLS,
42
- };
43
- }
44
-
45
- export function isSupportedDataSourceKind(kind: string): kind is DataSourceKind {
46
- return SUPPORTED_DATA_SOURCE_KINDS.some((supported) => supported === kind);
47
- }
48
-
49
- export function isSupportedApiOrigin(origin: string): origin is ApiOrigin {
50
- return SUPPORTED_API_ORIGINS.some((supported) => supported === origin);
51
- }
52
-
53
- export function isSupportedApiProtocol(protocol: string): protocol is ApiProtocol {
54
- return SUPPORTED_API_PROTOCOLS.some((supported) => supported === protocol);
55
- }
56
-
57
- export function getDataSourceKind(source: DataSourceConfig): DataSourceKind {
58
- return source.kind;
59
- }
@@ -0,0 +1,124 @@
1
+ import type {
2
+ DataEndpointConfig,
3
+ DataSourceDiagnostic,
4
+ EndpointId,
5
+ OperationId,
6
+ } from '@ankhorage/contracts/data';
7
+
8
+ import { normalizeOpenApiOperation } from './operation';
9
+ import type {
10
+ OpenApiHttpMethod,
11
+ OpenApiImportInput,
12
+ OpenApiOperationObject,
13
+ OpenApiPathItemObject,
14
+ } from './types';
15
+
16
+ const OPENAPI_HTTP_METHODS = [
17
+ 'delete',
18
+ 'get',
19
+ 'head',
20
+ 'options',
21
+ 'patch',
22
+ 'post',
23
+ 'put',
24
+ ] as const satisfies readonly OpenApiHttpMethod[];
25
+
26
+ export function normalizeOpenApiEndpointId(path: string): EndpointId {
27
+ const normalized = path
28
+ .replace(/\{([^}]+)\}/g, '$1')
29
+ .replace(/[^A-Za-z0-9]+/g, '-')
30
+ .replace(/^-+|-+$/g, '')
31
+ .toLowerCase();
32
+ return normalized.length > 0 ? normalized : 'root';
33
+ }
34
+
35
+ export function normalizeOpenApiEndpoints(
36
+ input: OpenApiImportInput,
37
+ diagnostics: DataSourceDiagnostic[],
38
+ ): Record<EndpointId, DataEndpointConfig> {
39
+ const { paths } = input.document;
40
+ if (paths === undefined || Object.keys(paths).length === 0) {
41
+ diagnostics.push({
42
+ code: 'invalid-config',
43
+ apiId: input.id,
44
+ message: 'OpenAPI document must define at least one path.',
45
+ path: 'paths',
46
+ severity: 'error',
47
+ });
48
+ return {};
49
+ }
50
+
51
+ const operationIds = new Set<OperationId>();
52
+ return Object.fromEntries(
53
+ Object.entries(paths).map(([path, pathItem]) => {
54
+ const endpointId = normalizeOpenApiEndpointId(path);
55
+ return [
56
+ endpointId,
57
+ normalizeOpenApiEndpoint(input, path, pathItem, endpointId, operationIds, diagnostics),
58
+ ];
59
+ }),
60
+ );
61
+ }
62
+
63
+ function normalizeOpenApiEndpoint(
64
+ input: OpenApiImportInput,
65
+ path: string,
66
+ pathItem: OpenApiPathItemObject,
67
+ endpointId: EndpointId,
68
+ operationIds: Set<OperationId>,
69
+ diagnostics: DataSourceDiagnostic[],
70
+ ): DataEndpointConfig {
71
+ const operations = Object.fromEntries(
72
+ getOpenApiOperations(pathItem).map(([method, operation]) => {
73
+ const normalized = normalizeOpenApiOperation(
74
+ input,
75
+ endpointId,
76
+ path,
77
+ method,
78
+ operation,
79
+ pathItem,
80
+ operationIds,
81
+ diagnostics,
82
+ );
83
+ return [normalized.id, normalized];
84
+ }),
85
+ );
86
+ return {
87
+ id: endpointId,
88
+ kind: 'http',
89
+ path,
90
+ credential: input.credential,
91
+ operations,
92
+ };
93
+ }
94
+
95
+ function getOpenApiOperations(
96
+ pathItem: OpenApiPathItemObject,
97
+ ): readonly [OpenApiHttpMethod, OpenApiOperationObject][] {
98
+ return OPENAPI_HTTP_METHODS.flatMap((method) => {
99
+ const operation = getOpenApiOperation(pathItem, method);
100
+ return operation === undefined ? [] : [[method, operation]];
101
+ });
102
+ }
103
+
104
+ function getOpenApiOperation(
105
+ pathItem: OpenApiPathItemObject,
106
+ method: OpenApiHttpMethod,
107
+ ): OpenApiOperationObject | undefined {
108
+ switch (method) {
109
+ case 'delete':
110
+ return pathItem.delete;
111
+ case 'get':
112
+ return pathItem.get;
113
+ case 'head':
114
+ return pathItem.head;
115
+ case 'options':
116
+ return pathItem.options;
117
+ case 'patch':
118
+ return pathItem.patch;
119
+ case 'post':
120
+ return pathItem.post;
121
+ case 'put':
122
+ return pathItem.put;
123
+ }
124
+ }
@@ -1,4 +1,4 @@
1
- import { describe, expect, it } from 'bun:test';
1
+ import { expect, it } from 'bun:test';
2
2
 
3
3
  import {
4
4
  importOpenApiDocument,
@@ -31,56 +31,56 @@ function document(): OpenApiDocumentObject {
31
31
  };
32
32
  }
33
33
 
34
- describe('OpenAPI normalization', () => {
35
- it('normalizes endpoint, operation and schema shapes', () => {
36
- expect(normalizeOpenApiEndpointId('/pets/{petId}')).toBe('pets-petid');
37
- expect(normalizeOpenApiOperationId('get', '/pets/{petId}')).toBe('get-pets-petid');
38
- expect(normalizeOpenApiSchema({ type: 'array', items: { type: 'string' } }).items?.type).toBe(
39
- 'string',
40
- );
41
- });
34
+ it('normalizes OpenAPI endpoint, operation and schema shapes', () => {
35
+ expect(normalizeOpenApiEndpointId('/pets/{petId}')).toBe('pets-petid');
36
+ expect(normalizeOpenApiOperationId('get', '/pets/{petId}')).toBe('get-pets-petid');
37
+ expect(normalizeOpenApiSchema({ type: 'array', items: { type: 'string' } }).items?.type).toBe(
38
+ 'string',
39
+ );
40
+ });
42
41
 
43
- it('imports OpenAPI as an external REST source with OpenAPI metadata', () => {
44
- const result = importOpenApiDocument({
45
- id: 'pet-store',
46
- document: document(),
47
- documentUrl: 'https://api.example.com/openapi.json',
48
- });
49
- expect(result.ok).toBe(true);
50
- if (result.ok) {
51
- expect(result.data).toMatchObject({ kind: 'api', origin: 'external', protocol: 'rest' });
52
- expect(result.data.openApi?.version).toBe('2026-08-06');
53
- expect(result.data.endpoints.pets?.operations.listpets?.intent).toBe('read');
54
- expect(result.data.endpoints.pets?.operations.createpet?.intent).toBe('create');
55
- }
42
+ it('imports OpenAPI as a canonical external REST API', () => {
43
+ const result = importOpenApiDocument({
44
+ id: 'pet-store',
45
+ document: document(),
46
+ documentUrl: 'https://api.example.com/openapi.json',
56
47
  });
48
+ expect(result.ok).toBe(true);
49
+ if (result.ok) {
50
+ expect(result.data).toMatchObject({ origin: 'external', protocol: 'rest' });
51
+ expect('kind' in result.data).toBe(false);
52
+ expect(result.data.openApi?.version).toBe('2026-08-06');
53
+ expect(result.data.endpoints.pets?.baseUrl).toBeUndefined();
54
+ expect(result.data.endpoints.pets?.operations.listpets?.intent).toBe('read');
55
+ expect(result.data.endpoints.pets?.operations.createpet?.intent).toBe('create');
56
+ }
57
+ });
57
58
 
58
- it('uses an explicit base URL and reports ambiguous servers', () => {
59
- const result = importOpenApiDocument({
60
- id: 'pet-store',
61
- document: {
62
- ...document(),
63
- servers: [{ url: 'https://one.example.com' }, { url: 'https://two.example.com' }],
64
- },
65
- baseUrl: 'https://override.example.com',
66
- });
67
- expect(result.ok).toBe(true);
68
- if (result.ok) expect(result.data.baseUrl).toBe('https://override.example.com');
59
+ it('uses an explicit OpenAPI base URL with ambiguous servers', () => {
60
+ const result = importOpenApiDocument({
61
+ id: 'pet-store',
62
+ document: {
63
+ ...document(),
64
+ servers: [{ url: 'https://one.example.com' }, { url: 'https://two.example.com' }],
65
+ },
66
+ baseUrl: 'https://override.example.com',
69
67
  });
68
+ expect(result.ok).toBe(true);
69
+ if (result.ok) expect(result.data.baseUrl).toBe('https://override.example.com');
70
+ });
70
71
 
71
- it('requires a resolvable base URL and paths', () => {
72
- expect(
73
- importOpenApiDocument({
74
- id: 'no-server',
75
- document: { openapi: '3.1.0', paths: { '/x': { get: {} } } },
76
- }).ok,
77
- ).toBe(false);
78
- expect(
79
- importOpenApiDocument({
80
- id: 'no-paths',
81
- baseUrl: 'https://example.com',
82
- document: { openapi: '3.1.0' },
83
- }).ok,
84
- ).toBe(false);
72
+ it('requires a resolvable OpenAPI base URL and paths', () => {
73
+ const noServer = importOpenApiDocument({
74
+ id: 'no-server',
75
+ document: { openapi: '3.1.0', paths: { '/x': { get: {} } } },
76
+ });
77
+ const noPaths = importOpenApiDocument({
78
+ id: 'no-paths',
79
+ baseUrl: 'https://example.com',
80
+ document: { openapi: '3.1.0' },
85
81
  });
82
+ expect(noServer.ok).toBe(false);
83
+ if (!noServer.ok) expect(noServer.diagnostics[0]?.apiId).toBe('no-server');
84
+ expect(noPaths.ok).toBe(false);
85
+ if (!noPaths.ok) expect(noPaths.diagnostics[0]?.apiId).toBe('no-paths');
86
86
  });