@objectstack/plugin-dev 4.0.4 → 4.0.5

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.
@@ -1,269 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { DevPlugin } from './dev-plugin';
3
-
4
- describe('DevPlugin', () => {
5
- it('should have correct metadata', () => {
6
- const plugin = new DevPlugin();
7
- expect(plugin.name).toBe('com.objectstack.plugin.dev');
8
- expect(plugin.type).toBe('standard');
9
- expect(plugin.version).toBe('1.0.0');
10
- });
11
-
12
- it('should accept default options', () => {
13
- const plugin = new DevPlugin();
14
- expect(plugin).toBeDefined();
15
- });
16
-
17
- it('should accept custom options including stack', () => {
18
- const plugin = new DevPlugin({
19
- port: 4000,
20
- seedAdminUser: false,
21
- verbose: false,
22
- services: { auth: false, dispatcher: false, security: false },
23
- stack: { manifest: { id: 'test', name: 'test', version: '1.0.0', type: 'app' } },
24
- });
25
- expect(plugin).toBeDefined();
26
- });
27
-
28
- it('should init with mocked context and handle missing deps gracefully', async () => {
29
- const ctx: any = {
30
- logger: {
31
- info: vi.fn(),
32
- debug: vi.fn(),
33
- warn: vi.fn(),
34
- error: vi.fn(),
35
- },
36
- getService: vi.fn().mockImplementation(() => { throw new Error('not found'); }),
37
- getServices: vi.fn().mockReturnValue(new Map()),
38
- registerService: vi.fn(),
39
- hook: vi.fn(),
40
- trigger: vi.fn(),
41
- getKernel: vi.fn(),
42
- };
43
-
44
- // DevPlugin should not throw even if peer dependencies are missing
45
- const plugin = new DevPlugin({ seedAdminUser: false });
46
- await expect(plugin.init(ctx)).resolves.not.toThrow();
47
- }, 15_000);
48
-
49
- it('should register contract-compliant dev stubs for all core services', async () => {
50
- const registeredServices = new Map<string, any>();
51
- const ctx: any = {
52
- logger: {
53
- info: vi.fn(),
54
- debug: vi.fn(),
55
- warn: vi.fn(),
56
- error: vi.fn(),
57
- },
58
- getService: vi.fn().mockImplementation((name: string) => {
59
- if (registeredServices.has(name)) return registeredServices.get(name);
60
- throw new Error('not found');
61
- }),
62
- getServices: vi.fn().mockReturnValue(new Map()),
63
- registerService: vi.fn().mockImplementation((name: string, svc: any) => {
64
- registeredServices.set(name, svc);
65
- }),
66
- hook: vi.fn(),
67
- trigger: vi.fn(),
68
- getKernel: vi.fn(),
69
- };
70
-
71
- // Disable real plugins (which need real packages) but allow stubs
72
- const plugin = new DevPlugin({
73
- seedAdminUser: false,
74
- services: {
75
- objectql: false,
76
- driver: false,
77
- auth: false,
78
- setup: false,
79
- security: false,
80
- server: false,
81
- rest: false,
82
- dispatcher: false,
83
- },
84
- });
85
-
86
- await plugin.init(ctx);
87
-
88
- // Should have registered stubs for all core + security services
89
- const stubLog = ctx.logger.info.mock.calls.find(
90
- (call: any[]) => typeof call[0] === 'string' && call[0].includes('dev stubs registered'),
91
- );
92
- expect(stubLog).toBeDefined();
93
-
94
- // ── Verify ICacheService contract ──
95
- const cache = registeredServices.get('cache');
96
- expect(cache._dev).toBe(true);
97
- await cache.set('k1', 'v1');
98
- expect(await cache.get('k1')).toBe('v1');
99
- expect(await cache.has('k1')).toBe(true);
100
- expect(await cache.delete('k1')).toBe(true);
101
- expect(await cache.has('k1')).toBe(false);
102
- const stats = await cache.stats();
103
- expect(typeof stats.hits).toBe('number');
104
- expect(typeof stats.misses).toBe('number');
105
- expect(typeof stats.keyCount).toBe('number');
106
-
107
- // ── Verify IQueueService contract ──
108
- const queue = registeredServices.get('queue');
109
- const msgId = await queue.publish('test-q', { hello: 'world' });
110
- expect(typeof msgId).toBe('string');
111
- expect(await queue.getQueueSize('test-q')).toBe(0);
112
-
113
- // ── Verify IJobService contract ──
114
- const job = registeredServices.get('job');
115
- const jobs = await job.listJobs();
116
- expect(Array.isArray(jobs)).toBe(true);
117
-
118
- // ── Verify IStorageService contract ──
119
- const storage = registeredServices.get('file-storage');
120
- await storage.upload('test.txt', Buffer.from('hello'));
121
- expect(await storage.exists('test.txt')).toBe(true);
122
- const info = await storage.getInfo('test.txt');
123
- expect(info.key).toBe('test.txt');
124
- expect(info.size).toBeGreaterThan(0);
125
- const downloaded = await storage.download('test.txt');
126
- expect(downloaded.toString()).toBe('hello');
127
-
128
- // ── Verify ISearchService contract ──
129
- const search = registeredServices.get('search');
130
- await search.index('users', '1', { name: 'Alice' });
131
- const searchResult = await search.search('users', 'alice');
132
- expect(searchResult.hits).toHaveLength(1);
133
- expect(typeof searchResult.totalHits).toBe('number');
134
-
135
- // ── Verify IAutomationService contract ──
136
- const automation = registeredServices.get('automation');
137
- const execResult = await automation.execute('test_flow');
138
- expect(execResult.success).toBe(true);
139
- expect(Array.isArray(await automation.listFlows())).toBe(true);
140
-
141
- // ── Verify IGraphQLService contract ──
142
- const gql = registeredServices.get('graphql');
143
- const gqlResult = await gql.execute({ query: '{ _dev }' });
144
- expect('data' in gqlResult || 'errors' in gqlResult).toBe(true);
145
- expect(typeof gql.getSchema()).toBe('string');
146
-
147
- // ── Verify IAnalyticsService contract ──
148
- const analytics = registeredServices.get('analytics');
149
- const analyticsResult = await analytics.query({ cube: 'test' });
150
- expect(Array.isArray(analyticsResult.rows)).toBe(true);
151
- expect(Array.isArray(analyticsResult.fields)).toBe(true);
152
- expect(Array.isArray(await analytics.getMeta())).toBe(true);
153
-
154
- // ── Verify IRealtimeService contract ──
155
- const realtime = registeredServices.get('realtime');
156
- const subId = await realtime.subscribe('ch', () => {});
157
- expect(typeof subId).toBe('string');
158
-
159
- // ── Verify INotificationService contract ──
160
- const notif = registeredServices.get('notification');
161
- const notifResult = await notif.send({ channel: 'email', to: 'test@dev', body: 'hello' });
162
- expect(notifResult.success).toBe(true);
163
- expect(typeof notifResult.messageId).toBe('string');
164
-
165
- // ── Verify IAIService contract ──
166
- const ai = registeredServices.get('ai');
167
- const chatResult = await ai.chat([{ role: 'user', content: 'hi' }]);
168
- expect(typeof chatResult.content).toBe('string');
169
- expect(typeof chatResult.model).toBe('string');
170
- expect(Array.isArray(await ai.listModels())).toBe(true);
171
-
172
- // ── Verify II18nService contract ──
173
- const i18n = registeredServices.get('i18n');
174
- i18n.loadTranslations('en', { 'hello': 'Hello {{name}}' });
175
- expect(i18n.t('hello', 'en', { name: 'World' })).toBe('Hello World');
176
- expect(i18n.t('missing', 'en')).toBe('missing');
177
- expect(Array.isArray(i18n.getLocales())).toBe(true);
178
-
179
- // ── Verify IUIService contract ──
180
- const ui = registeredServices.get('ui');
181
- ui.registerView('test_view', { name: 'test_view', object: 'account' });
182
- expect(ui.getView('test_view')).toBeDefined();
183
- expect(Array.isArray(ui.listViews())).toBe(true);
184
- expect(ui.listViews('account')).toHaveLength(1);
185
-
186
- // ── Verify IWorkflowService contract ──
187
- const workflow = registeredServices.get('workflow');
188
- const transResult = await workflow.transition({ recordId: 'r1', object: 'order', targetState: 'approved' });
189
- expect(transResult.success).toBe(true);
190
- expect(transResult.currentState).toBe('approved');
191
- const status = await workflow.getStatus('order', 'r1');
192
- expect(status.currentState).toBe('approved');
193
- expect(Array.isArray(status.availableTransitions)).toBe(true);
194
-
195
- // ── Verify IMetadataService contract (stub fallback) ──
196
- const metadata = registeredServices.get('metadata');
197
- metadata.register('object', { name: 'account' });
198
- expect(metadata.get('object', 'account')).toBeDefined();
199
- expect(Array.isArray(metadata.list('object'))).toBe(true);
200
- expect(Array.isArray(metadata.listObjects())).toBe(true);
201
-
202
- // Security sub-services are registered by either the real SecurityPlugin
203
- // or dev stubs (when security is disabled, they're skipped entirely).
204
- // The stubs follow the same contracts as the real implementations.
205
- });
206
-
207
- it('should skip disabled services', async () => {
208
- const ctx: any = {
209
- logger: {
210
- info: vi.fn(),
211
- debug: vi.fn(),
212
- warn: vi.fn(),
213
- error: vi.fn(),
214
- },
215
- getService: vi.fn().mockImplementation(() => { throw new Error('not found'); }),
216
- getServices: vi.fn().mockReturnValue(new Map()),
217
- registerService: vi.fn(),
218
- hook: vi.fn(),
219
- trigger: vi.fn(),
220
- getKernel: vi.fn(),
221
- };
222
-
223
- const plugin = new DevPlugin({
224
- seedAdminUser: false,
225
- services: {
226
- objectql: false,
227
- driver: false,
228
- auth: false,
229
- setup: false,
230
- server: false,
231
- rest: false,
232
- dispatcher: false,
233
- security: false,
234
- // Disable all core services too
235
- metadata: false,
236
- data: false,
237
- cache: false,
238
- queue: false,
239
- job: false,
240
- 'file-storage': false,
241
- search: false,
242
- automation: false,
243
- graphql: false,
244
- analytics: false,
245
- realtime: false,
246
- notification: false,
247
- ai: false,
248
- i18n: false,
249
- ui: false,
250
- workflow: false,
251
- },
252
- });
253
-
254
- await plugin.init(ctx);
255
-
256
- // No child plugins AND no stubs should be registered
257
- const initLog = ctx.logger.info.mock.calls.find(
258
- (call: any[]) => typeof call[0] === 'string' && call[0].includes('initialized'),
259
- );
260
- expect(initLog).toBeDefined();
261
- expect(initLog[0]).toContain('0 plugin');
262
- expect(initLog[0]).toContain('0 dev stub');
263
- });
264
-
265
- it('should destroy without errors', async () => {
266
- const plugin = new DevPlugin();
267
- await expect(plugin.destroy()).resolves.not.toThrow();
268
- });
269
- });