Skip to content

Commit ff4c899

Browse files
Copilothotlong
andcommitted
feat(kernel): add Dev Mode Plugin Protocol schema
Add dev-plugin.zod.ts defining the protocol for a development-mode plugin that automatically enables all platform services for local simulation. Includes DevServiceOverrideSchema, DevFixtureConfigSchema, DevToolsConfigSchema, DevPluginPreset, and DevPluginConfigSchema. 18 tests added covering all schemas and edge cases. Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 302ecf8 commit ff4c899

3 files changed

Lines changed: 476 additions & 0 deletions

File tree

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
import { describe, it, expect } from 'vitest';
2+
import {
3+
DevServiceOverrideSchema,
4+
DevFixtureConfigSchema,
5+
DevToolsConfigSchema,
6+
DevPluginPreset,
7+
DevPluginConfigSchema,
8+
} from './dev-plugin.zod';
9+
10+
describe('Dev Mode Plugin Protocol', () => {
11+
// ==========================================================================
12+
// DevServiceOverrideSchema
13+
// ==========================================================================
14+
describe('DevServiceOverrideSchema', () => {
15+
it('should validate minimal service override', () => {
16+
const result = DevServiceOverrideSchema.safeParse({ service: 'auth' });
17+
expect(result.success).toBe(true);
18+
if (result.success) {
19+
expect(result.data.enabled).toBe(true);
20+
expect(result.data.strategy).toBe('memory');
21+
}
22+
});
23+
24+
it('should validate full service override', () => {
25+
const override = {
26+
service: 'eventBus',
27+
enabled: true,
28+
strategy: 'mock' as const,
29+
config: { recordCalls: true },
30+
};
31+
32+
const result = DevServiceOverrideSchema.safeParse(override);
33+
expect(result.success).toBe(true);
34+
if (result.success) {
35+
expect(result.data.strategy).toBe('mock');
36+
expect(result.data.config?.recordCalls).toBe(true);
37+
}
38+
});
39+
40+
it('should accept all strategy values', () => {
41+
const strategies = ['mock', 'memory', 'stub', 'passthrough'] as const;
42+
for (const strategy of strategies) {
43+
const result = DevServiceOverrideSchema.safeParse({ service: 'svc', strategy });
44+
expect(result.success).toBe(true);
45+
}
46+
});
47+
48+
it('should reject empty service name', () => {
49+
const result = DevServiceOverrideSchema.safeParse({ service: '' });
50+
expect(result.success).toBe(false);
51+
});
52+
53+
it('should reject invalid strategy', () => {
54+
const result = DevServiceOverrideSchema.safeParse({
55+
service: 'auth',
56+
strategy: 'invalid',
57+
});
58+
expect(result.success).toBe(false);
59+
});
60+
});
61+
62+
// ==========================================================================
63+
// DevFixtureConfigSchema
64+
// ==========================================================================
65+
describe('DevFixtureConfigSchema', () => {
66+
it('should apply default values', () => {
67+
const result = DevFixtureConfigSchema.parse({});
68+
expect(result.enabled).toBe(true);
69+
expect(result.resetBeforeLoad).toBe(true);
70+
});
71+
72+
it('should validate full fixture config', () => {
73+
const config = {
74+
enabled: true,
75+
paths: ['./fixtures/*.json', './test/data/*.yml'],
76+
resetBeforeLoad: false,
77+
envFilter: ['dev', 'demo'],
78+
};
79+
80+
const result = DevFixtureConfigSchema.safeParse(config);
81+
expect(result.success).toBe(true);
82+
if (result.success) {
83+
expect(result.data.paths).toHaveLength(2);
84+
expect(result.data.envFilter).toEqual(['dev', 'demo']);
85+
}
86+
});
87+
88+
it('should allow disabled fixtures', () => {
89+
const result = DevFixtureConfigSchema.parse({ enabled: false });
90+
expect(result.enabled).toBe(false);
91+
});
92+
});
93+
94+
// ==========================================================================
95+
// DevToolsConfigSchema
96+
// ==========================================================================
97+
describe('DevToolsConfigSchema', () => {
98+
it('should apply default values', () => {
99+
const result = DevToolsConfigSchema.parse({});
100+
expect(result.hotReload).toBe(true);
101+
expect(result.requestInspector).toBe(false);
102+
expect(result.dbExplorer).toBe(false);
103+
expect(result.verboseLogging).toBe(true);
104+
expect(result.apiDocs).toBe(true);
105+
expect(result.mailCatcher).toBe(false);
106+
});
107+
108+
it('should accept all tools enabled', () => {
109+
const config = {
110+
hotReload: true,
111+
requestInspector: true,
112+
dbExplorer: true,
113+
verboseLogging: true,
114+
apiDocs: true,
115+
mailCatcher: true,
116+
};
117+
118+
const result = DevToolsConfigSchema.safeParse(config);
119+
expect(result.success).toBe(true);
120+
if (result.success) {
121+
expect(result.data.mailCatcher).toBe(true);
122+
expect(result.data.dbExplorer).toBe(true);
123+
}
124+
});
125+
});
126+
127+
// ==========================================================================
128+
// DevPluginPreset
129+
// ==========================================================================
130+
describe('DevPluginPreset', () => {
131+
it('should validate preset values', () => {
132+
expect(DevPluginPreset.safeParse('minimal').success).toBe(true);
133+
expect(DevPluginPreset.safeParse('standard').success).toBe(true);
134+
expect(DevPluginPreset.safeParse('full').success).toBe(true);
135+
});
136+
137+
it('should reject invalid preset', () => {
138+
expect(DevPluginPreset.safeParse('custom').success).toBe(false);
139+
});
140+
});
141+
142+
// ==========================================================================
143+
// DevPluginConfigSchema
144+
// ==========================================================================
145+
describe('DevPluginConfigSchema', () => {
146+
it('should accept zero-config (empty object)', () => {
147+
const result = DevPluginConfigSchema.parse({});
148+
expect(result.preset).toBe('standard');
149+
expect(result.port).toBe(4400);
150+
expect(result.open).toBe(false);
151+
expect(result.seedAdminUser).toBe(true);
152+
expect(result.simulatedLatency).toBe(0);
153+
});
154+
155+
it('should accept preset-only config', () => {
156+
const result = DevPluginConfigSchema.parse({ preset: 'full' });
157+
expect(result.preset).toBe('full');
158+
});
159+
160+
it('should accept config with per-service overrides', () => {
161+
const config = {
162+
preset: 'standard' as const,
163+
services: {
164+
auth: { enabled: true, strategy: 'mock' as const },
165+
fileStorage: { enabled: false },
166+
},
167+
};
168+
169+
const result = DevPluginConfigSchema.safeParse(config);
170+
expect(result.success).toBe(true);
171+
if (result.success) {
172+
expect(result.data.services?.auth.enabled).toBe(true);
173+
expect(result.data.services?.auth.strategy).toBe('mock');
174+
expect(result.data.services?.fileStorage.enabled).toBe(false);
175+
}
176+
});
177+
178+
it('should accept full configuration', () => {
179+
const config = {
180+
preset: 'full' as const,
181+
services: {
182+
auth: { enabled: true, strategy: 'mock' as const },
183+
eventBus: { enabled: true, strategy: 'memory' as const },
184+
fileStorage: { enabled: false },
185+
},
186+
fixtures: {
187+
enabled: true,
188+
paths: ['./fixtures/*.json'],
189+
resetBeforeLoad: true,
190+
},
191+
tools: {
192+
hotReload: true,
193+
dbExplorer: true,
194+
apiDocs: true,
195+
},
196+
port: 5500,
197+
open: true,
198+
seedAdminUser: true,
199+
simulatedLatency: 200,
200+
};
201+
202+
const result = DevPluginConfigSchema.safeParse(config);
203+
expect(result.success).toBe(true);
204+
if (result.success) {
205+
expect(result.data.port).toBe(5500);
206+
expect(result.data.open).toBe(true);
207+
expect(result.data.simulatedLatency).toBe(200);
208+
expect(result.data.fixtures?.paths).toEqual(['./fixtures/*.json']);
209+
}
210+
});
211+
212+
it('should reject invalid port', () => {
213+
expect(
214+
DevPluginConfigSchema.safeParse({ port: 0 }).success,
215+
).toBe(false);
216+
expect(
217+
DevPluginConfigSchema.safeParse({ port: 99999 }).success,
218+
).toBe(false);
219+
});
220+
221+
it('should reject negative simulated latency', () => {
222+
expect(
223+
DevPluginConfigSchema.safeParse({ simulatedLatency: -10 }).success,
224+
).toBe(false);
225+
});
226+
});
227+
});

0 commit comments

Comments
 (0)