Skip to content

Commit bd0068c

Browse files
CopilotCopilot
andcommitted
Add tests for new schemas: execution, export, feed-api, discovery, storage
- Create execution.test.ts: ExecutionStatus, ExecutionStepLogSchema, ExecutionLogSchema, ExecutionErrorSchema, CheckpointSchema, ConcurrencyPolicySchema, ScheduleStateSchema - Create export.test.ts: ExportFormat, ExportJobStatus, CreateExportJob*, ExportJobProgress, ImportValidation*, FieldMappingEntry, ExportImportTemplate, ScheduledExport, ExportApiContracts - Extend feed-api.test.ts: Pin/Star/Search/Changelog schemas, new FeedApiErrorCode values - Extend discovery.test.ts: version, rateLimit, capabilities, schemaDiscovery fields - Extend storage.test.ts: FileTypeValidation, chunked upload schemas, UploadProgress Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent b50a05d commit bd0068c

13 files changed

Lines changed: 2682 additions & 6 deletions

packages/spec/src/api/discovery.test.ts

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,3 +536,150 @@ describe('DiscoverySchema with services', () => {
536536
expect(discovery.services['ai'].enabled).toBe(false);
537537
});
538538
});
539+
540+
// ==========================================
541+
// ServiceInfoSchema — version & rateLimit
542+
// ==========================================
543+
544+
describe('ServiceInfoSchema (version field)', () => {
545+
it('should accept a service with version', () => {
546+
const info = ServiceInfoSchema.parse({
547+
enabled: true,
548+
status: 'available',
549+
route: '/api/v1/data',
550+
provider: 'objectql',
551+
version: '3.0.6',
552+
});
553+
expect(info.version).toBe('3.0.6');
554+
});
555+
556+
it('should allow version to be omitted', () => {
557+
const info = ServiceInfoSchema.parse({
558+
enabled: true,
559+
status: 'available',
560+
});
561+
expect(info.version).toBeUndefined();
562+
});
563+
});
564+
565+
describe('ServiceInfoSchema (rateLimit field)', () => {
566+
it('should accept a service with rateLimit', () => {
567+
const info = ServiceInfoSchema.parse({
568+
enabled: true,
569+
status: 'available',
570+
rateLimit: {
571+
requestsPerMinute: 60,
572+
requestsPerHour: 1000,
573+
burstLimit: 10,
574+
retryAfterMs: 5000,
575+
},
576+
});
577+
expect(info.rateLimit?.requestsPerMinute).toBe(60);
578+
expect(info.rateLimit?.burstLimit).toBe(10);
579+
});
580+
581+
it('should allow rateLimit to be omitted', () => {
582+
const info = ServiceInfoSchema.parse({
583+
enabled: true,
584+
status: 'available',
585+
});
586+
expect(info.rateLimit).toBeUndefined();
587+
});
588+
589+
it('should allow partial rateLimit fields', () => {
590+
const info = ServiceInfoSchema.parse({
591+
enabled: true,
592+
status: 'available',
593+
rateLimit: {
594+
requestsPerMinute: 100,
595+
},
596+
});
597+
expect(info.rateLimit?.requestsPerMinute).toBe(100);
598+
expect(info.rateLimit?.requestsPerHour).toBeUndefined();
599+
});
600+
});
601+
602+
// ==========================================
603+
// DiscoverySchema — capabilities
604+
// ==========================================
605+
606+
describe('DiscoverySchema (capabilities field)', () => {
607+
const fixture = {
608+
name: 'ObjectStack',
609+
version: '1.0.0',
610+
environment: 'production' as const,
611+
routes: { data: '/api/v1/data', metadata: '/api/v1/meta' },
612+
services: minimalServices,
613+
locale: { default: 'en-US', supported: ['en-US'], timezone: 'UTC' },
614+
};
615+
616+
it('should accept discovery with capabilities', () => {
617+
const discovery = DiscoverySchema.parse({
618+
...fixture,
619+
capabilities: {
620+
comments: {
621+
enabled: true,
622+
features: { threaded: true, reactions: true, mentions: true },
623+
description: 'Feed and comments support',
624+
},
625+
automation: {
626+
enabled: false,
627+
description: 'Flow automation engine',
628+
},
629+
},
630+
});
631+
expect(discovery.capabilities?.comments.enabled).toBe(true);
632+
expect(discovery.capabilities?.comments.features?.threaded).toBe(true);
633+
expect(discovery.capabilities?.automation.enabled).toBe(false);
634+
});
635+
636+
it('should allow capabilities to be omitted', () => {
637+
const discovery = DiscoverySchema.parse(fixture);
638+
expect(discovery.capabilities).toBeUndefined();
639+
});
640+
});
641+
642+
// ==========================================
643+
// DiscoverySchema — schemaDiscovery
644+
// ==========================================
645+
646+
describe('DiscoverySchema (schemaDiscovery field)', () => {
647+
const fixture = {
648+
name: 'ObjectStack',
649+
version: '1.0.0',
650+
environment: 'production' as const,
651+
routes: { data: '/api/v1/data', metadata: '/api/v1/meta' },
652+
services: minimalServices,
653+
locale: { default: 'en-US', supported: ['en-US'], timezone: 'UTC' },
654+
};
655+
656+
it('should accept discovery with schemaDiscovery', () => {
657+
const discovery = DiscoverySchema.parse({
658+
...fixture,
659+
schemaDiscovery: {
660+
openapi: '/api/v1/openapi.json',
661+
graphql: '/graphql',
662+
jsonSchema: '/api/v1/schemas',
663+
},
664+
});
665+
expect(discovery.schemaDiscovery?.openapi).toBe('/api/v1/openapi.json');
666+
expect(discovery.schemaDiscovery?.graphql).toBe('/graphql');
667+
expect(discovery.schemaDiscovery?.jsonSchema).toBe('/api/v1/schemas');
668+
});
669+
670+
it('should allow schemaDiscovery to be omitted', () => {
671+
const discovery = DiscoverySchema.parse(fixture);
672+
expect(discovery.schemaDiscovery).toBeUndefined();
673+
});
674+
675+
it('should allow partial schemaDiscovery fields', () => {
676+
const discovery = DiscoverySchema.parse({
677+
...fixture,
678+
schemaDiscovery: {
679+
openapi: '/api/v1/openapi.json',
680+
},
681+
});
682+
expect(discovery.schemaDiscovery?.openapi).toBe('/api/v1/openapi.json');
683+
expect(discovery.schemaDiscovery?.graphql).toBeUndefined();
684+
});
685+
});

packages/spec/src/api/discovery.zod.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,17 @@ export const ServiceInfoSchema = z.object({
1717
route: z.string().optional().describe('e.g. /api/v1/analytics'),
1818
/** Implementation provider name */
1919
provider: z.string().optional().describe('e.g. "objectql", "plugin-redis", "driver-memory"'),
20+
/** Service version */
21+
version: z.string().optional().describe('Semantic version of the service implementation (e.g. "3.0.6")'),
2022
/** Human-readable reason if unavailable */
2123
message: z.string().optional().describe('e.g. "Install plugin-workflow to enable"'),
24+
/** Rate limit configuration for this service */
25+
rateLimit: z.object({
26+
requestsPerMinute: z.number().int().optional().describe('Maximum requests per minute'),
27+
requestsPerHour: z.number().int().optional().describe('Maximum requests per hour'),
28+
burstLimit: z.number().int().optional().describe('Maximum burst request count'),
29+
retryAfterMs: z.number().int().optional().describe('Suggested retry-after delay in milliseconds when rate-limited'),
30+
}).optional().describe('Rate limit and quota info for this service'),
2231
});
2332

2433
/**
@@ -108,6 +117,29 @@ export const DiscoverySchema = z.object({
108117
'Per-service availability map keyed by CoreServiceName'
109118
),
110119

120+
/**
121+
* Hierarchical capability descriptors.
122+
* Declares platform features so clients can adapt UI without probing individual services.
123+
* Each key is a capability domain (e.g., "comments", "automation", "search"),
124+
* and its value describes what sub-features are available.
125+
*/
126+
capabilities: z.record(z.string(), z.object({
127+
enabled: z.boolean().describe('Whether this capability is available'),
128+
features: z.record(z.string(), z.boolean()).optional()
129+
.describe('Sub-feature flags within this capability'),
130+
description: z.string().optional()
131+
.describe('Human-readable capability description'),
132+
})).optional().describe('Hierarchical capability descriptors for frontend intelligent adaptation'),
133+
134+
/**
135+
* Schema discovery URLs for cross-ecosystem interoperability.
136+
*/
137+
schemaDiscovery: z.object({
138+
openapi: z.string().optional().describe('URL to OpenAPI (Swagger) specification (e.g., "/api/v1/openapi.json")'),
139+
graphql: z.string().optional().describe('URL to GraphQL schema endpoint (e.g., "/graphql")'),
140+
jsonSchema: z.string().optional().describe('URL to JSON Schema definitions'),
141+
}).optional().describe('Schema discovery endpoints for API toolchain integration'),
142+
111143
/**
112144
* Custom metadata key-value pairs for extensibility
113145
*/

0 commit comments

Comments
 (0)