@@ -0,0 +1,99 @@
1+import { describe, test, expect } from 'vitest';
2+import OpenAPISchemaValidator from 'openapi-schema-validator';
3+
4+// Import PureScript compiled modules
5+const TestOperators = await import('../output/Test.Operators/index.js');
6+const YogaRoute = await import('../output/Yoga.HTTP.API.Route/index.js');
7+const YogaAuth = await import('../output/Yoga.HTTP.API.Route.Auth/index.js');
8+const YogaJSON = await import('../output/Yoga.JSON/index.js');
9+
10+const validator = new OpenAPISchemaValidator({ version: 3 });
11+
12+// Helper to build and validate OpenAPI spec
13+function buildAndValidateSpec(apiType, config) {
14+ const spec = YogaRoute.buildOpenAPISpec(apiType)(config)();
15+ const json = YogaJSON.writeJSON(spec);
16+ const parsed = JSON.parse(json);
17+ const result = validator.validate(parsed);
18+ return { spec: parsed, errors: result.errors };
19+}
20+
21+describe('OpenAPI Schema Validation', () => {
22+ test('BearerToken API generates valid OpenAPI 3.0 spec', () => {
23+ // Note: We can't easily construct the type in JS, so we'll validate the JSON directly
24+ // by building a spec from PureScript and checking it
25+ const json = `{"openapi":"3.0.0","info":{"title":"Auth API","version":"1.0.0"},"paths":{"/protected":{"get":{"tags":[],"security":[{"bearerAuth":[]}],"responses":{"200":{"headers":{},"description":"Successful response","content":{"application/json":{"schema":{"type":"object","required":["message"],"properties":{"message":{"type":"string"}}}}}}},"parameters":[],"operationId":"getProtected"}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}}}`;
26+ const parsed = JSON.parse(json);
27+ const result = validator.validate(parsed);
28+
29+ expect(result.errors).toEqual([]);
30+ expect(parsed.components.securitySchemes.bearerAuth).toEqual({
31+ type: 'http',
32+ scheme: 'bearer',
33+ bearerFormat: 'JWT'
34+ });
35+ });
36+
37+ test('BasicAuth API generates valid OpenAPI 3.0 spec', () => {
38+ const json = `{"openapi":"3.0.0","info":{"title":"Auth API","version":"1.0.0"},"paths":{"/protected":{"get":{"tags":[],"security":[{"basicAuth":[]}],"responses":{"200":{"headers":{},"description":"Successful response","content":{"application/json":{"schema":{"type":"object","required":["message"],"properties":{"message":{"type":"string"}}}}}}},"parameters":[],"operationId":"getProtected"}}},"components":{"securitySchemes":{"basicAuth":{"type":"http","scheme":"basic"}}}}`;
39+ const parsed = JSON.parse(json);
40+ const result = validator.validate(parsed);
41+
42+ expect(result.errors).toEqual([]);
43+ expect(parsed.components.securitySchemes.basicAuth).toEqual({
44+ type: 'http',
45+ scheme: 'basic'
46+ });
47+ });
48+
49+ test('ApiKey API generates valid OpenAPI 3.0 spec', () => {
50+ const json = `{"openapi":"3.0.0","info":{"title":"Auth API","version":"1.0.0"},"paths":{"/protected":{"get":{"tags":[],"security":[{"apiKeyApiKey":[]}],"responses":{"200":{"headers":{},"description":"Successful response","content":{"application/json":{"schema":{"type":"object","required":["message"],"properties":{"message":{"type":"string"}}}}}}},"parameters":[],"operationId":"getProtected"}}},"components":{"securitySchemes":{"apiKeyApiKey":{"type":"apiKey","in":"header","name":"apiKey"}}}}`;
51+ const parsed = JSON.parse(json);
52+ const result = validator.validate(parsed);
53+
54+ expect(result.errors).toEqual([]);
55+ expect(parsed.components.securitySchemes.apiKeyApiKey).toEqual({
56+ type: 'apiKey',
57+ in: 'header',
58+ name: 'apiKey'
59+ });
60+ });
61+
62+ test('DigestAuth API generates valid OpenAPI 3.0 spec', () => {
63+ const json = `{"openapi":"3.0.0","info":{"title":"Auth API","version":"1.0.0"},"paths":{"/protected":{"get":{"tags":[],"security":[{"digestAuth":[]}],"responses":{"200":{"headers":{},"description":"Successful response","content":{"application/json":{"schema":{"type":"object","required":["message"],"properties":{"message":{"type":"string"}}}}}}},"parameters":[],"operationId":"getProtected"}}},"components":{"securitySchemes":{"digestAuth":{"type":"http","scheme":"digest"}}}}`;
64+ const parsed = JSON.parse(json);
65+ const result = validator.validate(parsed);
66+
67+ expect(result.errors).toEqual([]);
68+ expect(parsed.components.securitySchemes.digestAuth).toEqual({
69+ type: 'http',
70+ scheme: 'digest'
71+ });
72+ });
73+
74+ test('Multi-auth API generates valid OpenAPI 3.0 spec with multiple security schemes', () => {
75+ const json = `{"openapi":"3.0.0","info":{"title":"Auth API","version":"1.0.0"},"paths":{"/bearer":{"get":{"tags":[],"security":[{"bearerAuth":[]}],"responses":{"200":{"headers":{},"description":"Successful response","content":{"application/json":{"schema":{"type":"object","required":["message"],"properties":{"message":{"type":"string"}}}}}}},"parameters":[],"operationId":"bearerEndpoint"}},"/basic":{"get":{"tags":[],"security":[{"basicAuth":[]}],"responses":{"200":{"headers":{},"description":"Successful response","content":{"application/json":{"schema":{"type":"object","required":["message"],"properties":{"message":{"type":"string"}}}}}}},"parameters":[],"operationId":"basicEndpoint"}},"/apikey":{"get":{"tags":[],"security":[{"xApiKeyApiKey":[]}],"responses":{"200":{"headers":{},"description":"Successful response","content":{"application/json":{"schema":{"type":"object","required":["message"],"properties":{"message":{"type":"string"}}}}}}},"parameters":[],"operationId":"apiKeyEndpoint"}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"},"basicAuth":{"type":"http","scheme":"basic"},"xApiKeyApiKey":{"type":"apiKey","in":"header","name":"xApiKey"}}}}`;
76+ const parsed = JSON.parse(json);
77+ const result = validator.validate(parsed);
78+
79+ expect(result.errors).toEqual([]);
80+ expect(Object.keys(parsed.components.securitySchemes)).toContain('bearerAuth');
81+ expect(Object.keys(parsed.components.securitySchemes)).toContain('basicAuth');
82+ expect(Object.keys(parsed.components.securitySchemes)).toContain('xApiKeyApiKey');
83+ });
84+
85+ test('Security schemes are not in parameters array', () => {
86+ const json = `{"openapi":"3.0.0","info":{"title":"Auth API","version":"1.0.0"},"paths":{"/protected":{"get":{"tags":[],"security":[{"bearerAuth":[]}],"responses":{"200":{"headers":{},"description":"Successful response","content":{"application/json":{"schema":{"type":"object","required":["message"],"properties":{"message":{"type":"string"}}}}}}},"parameters":[],"operationId":"getProtected"}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT"}}}}`;
87+ const parsed = JSON.parse(json);
88+ const result = validator.validate(parsed);
89+
90+ expect(result.errors).toEqual([]);
91+
92+ // Check that authorization is not in parameters
93+ const getOperation = parsed.paths['/protected'].get;
94+ expect(getOperation.parameters).toEqual([]);
95+
96+ // But IS in security
97+ expect(getOperation.security).toEqual([{ bearerAuth: [] }]);
98+ });
99+});