@geekmidas/cli 0.12.0 → 0.14.0
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.
- package/dist/bundler-BjholBlA.cjs +131 -0
- package/dist/bundler-BjholBlA.cjs.map +1 -0
- package/dist/bundler-DWctKN1z.mjs +130 -0
- package/dist/bundler-DWctKN1z.mjs.map +1 -0
- package/dist/config.d.cts +1 -1
- package/dist/config.d.mts +1 -1
- package/dist/dokploy-api-B7KxOQr3.cjs +3 -0
- package/dist/dokploy-api-C7F9VykY.cjs +317 -0
- package/dist/dokploy-api-C7F9VykY.cjs.map +1 -0
- package/dist/dokploy-api-CaETb2L6.mjs +305 -0
- package/dist/dokploy-api-CaETb2L6.mjs.map +1 -0
- package/dist/dokploy-api-DHvfmWbi.mjs +3 -0
- package/dist/{encryption-Dyf_r1h-.cjs → encryption-D7Efcdi9.cjs} +1 -1
- package/dist/{encryption-Dyf_r1h-.cjs.map → encryption-D7Efcdi9.cjs.map} +1 -1
- package/dist/{encryption-C8H-38Yy.mjs → encryption-h4Nb6W-M.mjs} +1 -1
- package/dist/{encryption-C8H-38Yy.mjs.map → encryption-h4Nb6W-M.mjs.map} +1 -1
- package/dist/index.cjs +1520 -1136
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1520 -1136
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-Bt_1FDpT.cjs → openapi-C89hhkZC.cjs} +3 -3
- package/dist/{openapi-Bt_1FDpT.cjs.map → openapi-C89hhkZC.cjs.map} +1 -1
- package/dist/{openapi-BfFlOBCG.mjs → openapi-CZVcfxk-.mjs} +3 -3
- package/dist/{openapi-BfFlOBCG.mjs.map → openapi-CZVcfxk-.mjs.map} +1 -1
- package/dist/{openapi-react-query-B6XTeGqS.mjs → openapi-react-query-CM2_qlW9.mjs} +1 -1
- package/dist/{openapi-react-query-B6XTeGqS.mjs.map → openapi-react-query-CM2_qlW9.mjs.map} +1 -1
- package/dist/{openapi-react-query-B-sNWHFU.cjs → openapi-react-query-iKjfLzff.cjs} +1 -1
- package/dist/{openapi-react-query-B-sNWHFU.cjs.map → openapi-react-query-iKjfLzff.cjs.map} +1 -1
- package/dist/openapi-react-query.cjs +1 -1
- package/dist/openapi-react-query.mjs +1 -1
- package/dist/openapi.cjs +1 -1
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.mjs +1 -1
- package/dist/{storage-C9PU_30f.mjs → storage-BaOP55oq.mjs} +48 -2
- package/dist/storage-BaOP55oq.mjs.map +1 -0
- package/dist/{storage-BXoJvmv2.cjs → storage-Bn3K9Ccu.cjs} +59 -1
- package/dist/storage-Bn3K9Ccu.cjs.map +1 -0
- package/dist/storage-UfyTn7Zm.cjs +7 -0
- package/dist/storage-nkGIjeXt.mjs +3 -0
- package/dist/{types-BR0M2v_c.d.mts → types-BgaMXsUa.d.cts} +3 -1
- package/dist/{types-BR0M2v_c.d.mts.map → types-BgaMXsUa.d.cts.map} +1 -1
- package/dist/{types-BhkZc-vm.d.cts → types-iFk5ms7y.d.mts} +3 -1
- package/dist/{types-BhkZc-vm.d.cts.map → types-iFk5ms7y.d.mts.map} +1 -1
- package/package.json +4 -4
- package/src/auth/__tests__/credentials.spec.ts +127 -0
- package/src/auth/__tests__/index.spec.ts +69 -0
- package/src/auth/credentials.ts +33 -0
- package/src/auth/index.ts +57 -50
- package/src/build/__tests__/bundler.spec.ts +444 -0
- package/src/build/__tests__/endpoint-analyzer.spec.ts +623 -0
- package/src/build/__tests__/handler-templates.spec.ts +272 -0
- package/src/build/bundler.ts +126 -8
- package/src/build/index.ts +31 -0
- package/src/build/types.ts +6 -0
- package/src/deploy/__tests__/dokploy-api.spec.ts +698 -0
- package/src/deploy/__tests__/dokploy.spec.ts +196 -6
- package/src/deploy/__tests__/index.spec.ts +339 -0
- package/src/deploy/__tests__/init.spec.ts +147 -16
- package/src/deploy/docker.ts +32 -3
- package/src/deploy/dokploy-api.ts +581 -0
- package/src/deploy/dokploy.ts +66 -93
- package/src/deploy/index.ts +587 -32
- package/src/deploy/init.ts +192 -249
- package/src/deploy/types.ts +19 -1
- package/src/dev/__tests__/index.spec.ts +95 -0
- package/src/docker/__tests__/templates.spec.ts +144 -0
- package/src/docker/index.ts +96 -6
- package/src/docker/templates.ts +114 -27
- package/src/generators/EndpointGenerator.ts +2 -2
- package/src/index.ts +34 -13
- package/src/secrets/__tests__/storage.spec.ts +208 -0
- package/src/secrets/storage.ts +73 -0
- package/src/types.ts +2 -0
- package/dist/bundler-DRXCw_YR.mjs +0 -70
- package/dist/bundler-DRXCw_YR.mjs.map +0 -1
- package/dist/bundler-WsEvH_b2.cjs +0 -71
- package/dist/bundler-WsEvH_b2.cjs.map +0 -1
- package/dist/storage-BUYQJgz7.cjs +0 -4
- package/dist/storage-BXoJvmv2.cjs.map +0 -1
- package/dist/storage-C9PU_30f.mjs.map +0 -1
- package/dist/storage-DLJAYxzJ.mjs +0 -3
|
@@ -0,0 +1,698 @@
|
|
|
1
|
+
import { HttpResponse, http } from 'msw';
|
|
2
|
+
import { setupServer } from 'msw/node';
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
|
4
|
+
import { DokployApi, DokployApiError } from '../dokploy-api';
|
|
5
|
+
|
|
6
|
+
const BASE_URL = 'https://dokploy.example.com';
|
|
7
|
+
const server = setupServer();
|
|
8
|
+
|
|
9
|
+
describe('DokployApi', () => {
|
|
10
|
+
let api: DokployApi;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
api = new DokployApi({
|
|
14
|
+
baseUrl: BASE_URL,
|
|
15
|
+
token: 'test-api-token',
|
|
16
|
+
});
|
|
17
|
+
server.listen({ onUnhandledRequest: 'bypass' });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
server.resetHandlers();
|
|
22
|
+
server.close();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('constructor', () => {
|
|
26
|
+
it('should remove trailing slash from baseUrl', () => {
|
|
27
|
+
const apiWithSlash = new DokployApi({
|
|
28
|
+
baseUrl: 'https://example.com/',
|
|
29
|
+
token: 'token',
|
|
30
|
+
});
|
|
31
|
+
// Can't directly test private property, but we can test through a request
|
|
32
|
+
expect(apiWithSlash).toBeDefined();
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('request handling', () => {
|
|
37
|
+
it('should include x-api-key header', async () => {
|
|
38
|
+
let capturedHeaders: Headers | undefined;
|
|
39
|
+
|
|
40
|
+
server.use(
|
|
41
|
+
http.get(`${BASE_URL}/api/project.all`, ({ request }) => {
|
|
42
|
+
capturedHeaders = request.headers;
|
|
43
|
+
return HttpResponse.json([]);
|
|
44
|
+
}),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
await api.listProjects();
|
|
48
|
+
|
|
49
|
+
expect(capturedHeaders?.get('x-api-key')).toBe('test-api-token');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should include Content-Type header', async () => {
|
|
53
|
+
let capturedHeaders: Headers | undefined;
|
|
54
|
+
|
|
55
|
+
server.use(
|
|
56
|
+
http.post(`${BASE_URL}/api/project.create`, ({ request }) => {
|
|
57
|
+
capturedHeaders = request.headers;
|
|
58
|
+
return HttpResponse.json({ projectId: 'proj_123' });
|
|
59
|
+
}),
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
await api.createProject('test');
|
|
63
|
+
|
|
64
|
+
expect(capturedHeaders?.get('content-type')).toBe('application/json');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should throw DokployApiError on non-ok response', async () => {
|
|
68
|
+
server.use(
|
|
69
|
+
http.get(`${BASE_URL}/api/project.all`, () => {
|
|
70
|
+
return HttpResponse.json(
|
|
71
|
+
{ message: 'Unauthorized' },
|
|
72
|
+
{ status: 401 },
|
|
73
|
+
);
|
|
74
|
+
}),
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
await expect(api.listProjects()).rejects.toThrow(DokployApiError);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should include error message from response', async () => {
|
|
81
|
+
server.use(
|
|
82
|
+
http.get(`${BASE_URL}/api/project.all`, () => {
|
|
83
|
+
return HttpResponse.json(
|
|
84
|
+
{ message: 'Invalid token' },
|
|
85
|
+
{ status: 401 },
|
|
86
|
+
);
|
|
87
|
+
}),
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
await api.listProjects();
|
|
92
|
+
} catch (error) {
|
|
93
|
+
expect(error).toBeInstanceOf(DokployApiError);
|
|
94
|
+
expect((error as DokployApiError).message).toContain('Invalid token');
|
|
95
|
+
expect((error as DokployApiError).status).toBe(401);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should include issues in error', async () => {
|
|
100
|
+
server.use(
|
|
101
|
+
http.post(`${BASE_URL}/api/project.create`, () => {
|
|
102
|
+
return HttpResponse.json(
|
|
103
|
+
{
|
|
104
|
+
message: 'Validation failed',
|
|
105
|
+
issues: [
|
|
106
|
+
{ message: 'Name is required' },
|
|
107
|
+
{ message: 'Name is too short' },
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
{ status: 400 },
|
|
111
|
+
);
|
|
112
|
+
}),
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
await api.createProject('');
|
|
117
|
+
} catch (error) {
|
|
118
|
+
expect(error).toBeInstanceOf(DokployApiError);
|
|
119
|
+
const err = error as DokployApiError;
|
|
120
|
+
expect(err.issues).toHaveLength(2);
|
|
121
|
+
expect(err.message).toContain('Name is required');
|
|
122
|
+
expect(err.message).toContain('Name is too short');
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should handle empty response body', async () => {
|
|
127
|
+
server.use(
|
|
128
|
+
http.post(`${BASE_URL}/api/application.deploy`, () => {
|
|
129
|
+
return new HttpResponse(null, { status: 200 });
|
|
130
|
+
}),
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
const result = await api.deployApplication('app_123');
|
|
134
|
+
expect(result).toBeUndefined();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should handle 204 No Content response', async () => {
|
|
138
|
+
server.use(
|
|
139
|
+
http.post(`${BASE_URL}/api/application.update`, () => {
|
|
140
|
+
return new HttpResponse(null, { status: 204 });
|
|
141
|
+
}),
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const result = await api.updateApplication('app_123', {
|
|
145
|
+
registryId: 'reg_456',
|
|
146
|
+
});
|
|
147
|
+
expect(result).toBeUndefined();
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('should handle whitespace-only response body', async () => {
|
|
151
|
+
server.use(
|
|
152
|
+
http.post(`${BASE_URL}/api/postgres.deploy`, () => {
|
|
153
|
+
return new HttpResponse(' \n\t ', { status: 200 });
|
|
154
|
+
}),
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
const result = await api.deployPostgres('pg_123');
|
|
158
|
+
expect(result).toBeUndefined();
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe('validateToken', () => {
|
|
163
|
+
it('should return true for valid token', async () => {
|
|
164
|
+
server.use(
|
|
165
|
+
http.get(`${BASE_URL}/api/project.all`, () => {
|
|
166
|
+
return HttpResponse.json([]);
|
|
167
|
+
}),
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
const result = await api.validateToken();
|
|
171
|
+
expect(result).toBe(true);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should return false for invalid token', async () => {
|
|
175
|
+
server.use(
|
|
176
|
+
http.get(`${BASE_URL}/api/project.all`, () => {
|
|
177
|
+
return HttpResponse.json(
|
|
178
|
+
{ message: 'Unauthorized' },
|
|
179
|
+
{ status: 401 },
|
|
180
|
+
);
|
|
181
|
+
}),
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
const result = await api.validateToken();
|
|
185
|
+
expect(result).toBe(false);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe('project endpoints', () => {
|
|
190
|
+
it('should list projects', async () => {
|
|
191
|
+
const projects = [
|
|
192
|
+
{ projectId: 'proj_1', name: 'Project 1', description: null },
|
|
193
|
+
{ projectId: 'proj_2', name: 'Project 2', description: 'Test' },
|
|
194
|
+
];
|
|
195
|
+
|
|
196
|
+
server.use(
|
|
197
|
+
http.get(`${BASE_URL}/api/project.all`, () => {
|
|
198
|
+
return HttpResponse.json(projects);
|
|
199
|
+
}),
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
const result = await api.listProjects();
|
|
203
|
+
expect(result).toEqual(projects);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('should get project by ID', async () => {
|
|
207
|
+
const project = {
|
|
208
|
+
projectId: 'proj_123',
|
|
209
|
+
name: 'My Project',
|
|
210
|
+
description: 'Test project',
|
|
211
|
+
environments: [{ environmentId: 'env_1', name: 'production' }],
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
server.use(
|
|
215
|
+
http.get(`${BASE_URL}/api/project.one`, ({ request }) => {
|
|
216
|
+
const url = new URL(request.url);
|
|
217
|
+
expect(url.searchParams.get('projectId')).toBe('proj_123');
|
|
218
|
+
return HttpResponse.json(project);
|
|
219
|
+
}),
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
const result = await api.getProject('proj_123');
|
|
223
|
+
expect(result).toEqual(project);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('should create project', async () => {
|
|
227
|
+
let capturedBody: unknown;
|
|
228
|
+
|
|
229
|
+
server.use(
|
|
230
|
+
http.post(`${BASE_URL}/api/project.create`, async ({ request }) => {
|
|
231
|
+
capturedBody = await request.json();
|
|
232
|
+
return HttpResponse.json({
|
|
233
|
+
project: {
|
|
234
|
+
projectId: 'proj_new',
|
|
235
|
+
name: 'New Project',
|
|
236
|
+
description: 'Custom description',
|
|
237
|
+
},
|
|
238
|
+
environment: {
|
|
239
|
+
environmentId: 'env_default',
|
|
240
|
+
name: 'production',
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
}),
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
const result = await api.createProject(
|
|
247
|
+
'New Project',
|
|
248
|
+
'Custom description',
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
expect(result.project.projectId).toBe('proj_new');
|
|
252
|
+
expect(result.environment.environmentId).toBe('env_default');
|
|
253
|
+
expect(capturedBody).toMatchObject({
|
|
254
|
+
name: 'New Project',
|
|
255
|
+
description: 'Custom description',
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it('should use default description when not provided', async () => {
|
|
260
|
+
let capturedBody: unknown;
|
|
261
|
+
|
|
262
|
+
server.use(
|
|
263
|
+
http.post(`${BASE_URL}/api/project.create`, async ({ request }) => {
|
|
264
|
+
capturedBody = await request.json();
|
|
265
|
+
return HttpResponse.json({
|
|
266
|
+
project: { projectId: 'proj_new', name: 'Test' },
|
|
267
|
+
environment: { environmentId: 'env_default', name: 'production' },
|
|
268
|
+
});
|
|
269
|
+
}),
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
await api.createProject('Test');
|
|
273
|
+
|
|
274
|
+
expect((capturedBody as { description: string }).description).toBe(
|
|
275
|
+
'Created by gkm CLI',
|
|
276
|
+
);
|
|
277
|
+
});
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
describe('application endpoints', () => {
|
|
281
|
+
it('should create application', async () => {
|
|
282
|
+
let capturedBody: unknown;
|
|
283
|
+
|
|
284
|
+
server.use(
|
|
285
|
+
http.post(`${BASE_URL}/api/application.create`, async ({ request }) => {
|
|
286
|
+
capturedBody = await request.json();
|
|
287
|
+
return HttpResponse.json({
|
|
288
|
+
applicationId: 'app_123',
|
|
289
|
+
name: 'My App',
|
|
290
|
+
appName: 'my-app',
|
|
291
|
+
});
|
|
292
|
+
}),
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
const result = await api.createApplication('My App', 'proj_1', 'env_1');
|
|
296
|
+
|
|
297
|
+
expect(result.applicationId).toBe('app_123');
|
|
298
|
+
expect(capturedBody).toMatchObject({
|
|
299
|
+
name: 'My App',
|
|
300
|
+
projectId: 'proj_1',
|
|
301
|
+
environmentId: 'env_1',
|
|
302
|
+
appName: 'my-app',
|
|
303
|
+
});
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
it('should sanitize appName', async () => {
|
|
307
|
+
let capturedBody: unknown;
|
|
308
|
+
|
|
309
|
+
server.use(
|
|
310
|
+
http.post(`${BASE_URL}/api/application.create`, async ({ request }) => {
|
|
311
|
+
capturedBody = await request.json();
|
|
312
|
+
return HttpResponse.json({ applicationId: 'app_123' });
|
|
313
|
+
}),
|
|
314
|
+
);
|
|
315
|
+
|
|
316
|
+
await api.createApplication('My App With Spaces!', 'proj_1', 'env_1');
|
|
317
|
+
|
|
318
|
+
expect((capturedBody as { appName: string }).appName).toBe(
|
|
319
|
+
'my-app-with-spaces-',
|
|
320
|
+
);
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
it('should save docker provider', async () => {
|
|
324
|
+
let capturedBody: unknown;
|
|
325
|
+
|
|
326
|
+
server.use(
|
|
327
|
+
http.post(
|
|
328
|
+
`${BASE_URL}/api/application.saveDockerProvider`,
|
|
329
|
+
async ({ request }) => {
|
|
330
|
+
capturedBody = await request.json();
|
|
331
|
+
return HttpResponse.json({ success: true });
|
|
332
|
+
},
|
|
333
|
+
),
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
await api.saveDockerProvider('app_123', 'ghcr.io/org/image:tag', {
|
|
337
|
+
registryId: 'reg_456',
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
expect(capturedBody).toMatchObject({
|
|
341
|
+
applicationId: 'app_123',
|
|
342
|
+
dockerImage: 'ghcr.io/org/image:tag',
|
|
343
|
+
registryId: 'reg_456',
|
|
344
|
+
});
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
it('should save docker provider with direct credentials', async () => {
|
|
348
|
+
let capturedBody: unknown;
|
|
349
|
+
|
|
350
|
+
server.use(
|
|
351
|
+
http.post(
|
|
352
|
+
`${BASE_URL}/api/application.saveDockerProvider`,
|
|
353
|
+
async ({ request }) => {
|
|
354
|
+
capturedBody = await request.json();
|
|
355
|
+
return HttpResponse.json({ success: true });
|
|
356
|
+
},
|
|
357
|
+
),
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
await api.saveDockerProvider('app_123', 'ghcr.io/org/image:tag', {
|
|
361
|
+
username: 'user',
|
|
362
|
+
password: 'pass',
|
|
363
|
+
registryUrl: 'ghcr.io',
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
expect(capturedBody).toMatchObject({
|
|
367
|
+
applicationId: 'app_123',
|
|
368
|
+
dockerImage: 'ghcr.io/org/image:tag',
|
|
369
|
+
username: 'user',
|
|
370
|
+
password: 'pass',
|
|
371
|
+
registryUrl: 'ghcr.io',
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it('should save application environment', async () => {
|
|
376
|
+
let capturedBody: unknown;
|
|
377
|
+
|
|
378
|
+
server.use(
|
|
379
|
+
http.post(
|
|
380
|
+
`${BASE_URL}/api/application.saveEnvironment`,
|
|
381
|
+
async ({ request }) => {
|
|
382
|
+
capturedBody = await request.json();
|
|
383
|
+
return HttpResponse.json({ success: true });
|
|
384
|
+
},
|
|
385
|
+
),
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
await api.saveApplicationEnv('app_123', 'KEY=value\nANOTHER=test');
|
|
389
|
+
|
|
390
|
+
expect(capturedBody).toMatchObject({
|
|
391
|
+
applicationId: 'app_123',
|
|
392
|
+
env: 'KEY=value\nANOTHER=test',
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
it('should deploy application', async () => {
|
|
397
|
+
let capturedBody: unknown;
|
|
398
|
+
|
|
399
|
+
server.use(
|
|
400
|
+
http.post(`${BASE_URL}/api/application.deploy`, async ({ request }) => {
|
|
401
|
+
capturedBody = await request.json();
|
|
402
|
+
return HttpResponse.json({ success: true });
|
|
403
|
+
}),
|
|
404
|
+
);
|
|
405
|
+
|
|
406
|
+
await api.deployApplication('app_123');
|
|
407
|
+
|
|
408
|
+
expect(capturedBody).toMatchObject({
|
|
409
|
+
applicationId: 'app_123',
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
describe('registry endpoints', () => {
|
|
415
|
+
it('should list registries', async () => {
|
|
416
|
+
const registries = [
|
|
417
|
+
{
|
|
418
|
+
registryId: 'reg_1',
|
|
419
|
+
registryName: 'GitHub',
|
|
420
|
+
registryUrl: 'ghcr.io',
|
|
421
|
+
username: 'user',
|
|
422
|
+
},
|
|
423
|
+
];
|
|
424
|
+
|
|
425
|
+
server.use(
|
|
426
|
+
http.get(`${BASE_URL}/api/registry.all`, () => {
|
|
427
|
+
return HttpResponse.json(registries);
|
|
428
|
+
}),
|
|
429
|
+
);
|
|
430
|
+
|
|
431
|
+
const result = await api.listRegistries();
|
|
432
|
+
expect(result).toEqual(registries);
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
it('should create registry', async () => {
|
|
436
|
+
let capturedBody: unknown;
|
|
437
|
+
|
|
438
|
+
server.use(
|
|
439
|
+
http.post(`${BASE_URL}/api/registry.create`, async ({ request }) => {
|
|
440
|
+
capturedBody = await request.json();
|
|
441
|
+
return HttpResponse.json({
|
|
442
|
+
registryId: 'reg_new',
|
|
443
|
+
registryName: 'GitHub',
|
|
444
|
+
registryUrl: 'ghcr.io',
|
|
445
|
+
username: 'user',
|
|
446
|
+
});
|
|
447
|
+
}),
|
|
448
|
+
);
|
|
449
|
+
|
|
450
|
+
const result = await api.createRegistry(
|
|
451
|
+
'GitHub',
|
|
452
|
+
'ghcr.io',
|
|
453
|
+
'user',
|
|
454
|
+
'token',
|
|
455
|
+
{ imagePrefix: 'org' },
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
expect(result.registryId).toBe('reg_new');
|
|
459
|
+
expect(capturedBody).toMatchObject({
|
|
460
|
+
registryName: 'GitHub',
|
|
461
|
+
registryUrl: 'ghcr.io',
|
|
462
|
+
username: 'user',
|
|
463
|
+
password: 'token',
|
|
464
|
+
imagePrefix: 'org',
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
it('should get registry by ID', async () => {
|
|
469
|
+
server.use(
|
|
470
|
+
http.get(`${BASE_URL}/api/registry.one`, ({ request }) => {
|
|
471
|
+
const url = new URL(request.url);
|
|
472
|
+
expect(url.searchParams.get('registryId')).toBe('reg_123');
|
|
473
|
+
return HttpResponse.json({
|
|
474
|
+
registryId: 'reg_123',
|
|
475
|
+
registryName: 'Test',
|
|
476
|
+
registryUrl: 'test.io',
|
|
477
|
+
username: 'user',
|
|
478
|
+
});
|
|
479
|
+
}),
|
|
480
|
+
);
|
|
481
|
+
|
|
482
|
+
const result = await api.getRegistry('reg_123');
|
|
483
|
+
expect(result.registryId).toBe('reg_123');
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
it('should update registry', async () => {
|
|
487
|
+
let capturedBody: unknown;
|
|
488
|
+
|
|
489
|
+
server.use(
|
|
490
|
+
http.post(`${BASE_URL}/api/registry.update`, async ({ request }) => {
|
|
491
|
+
capturedBody = await request.json();
|
|
492
|
+
return HttpResponse.json({ success: true });
|
|
493
|
+
}),
|
|
494
|
+
);
|
|
495
|
+
|
|
496
|
+
await api.updateRegistry('reg_123', { registryName: 'New Name' });
|
|
497
|
+
|
|
498
|
+
expect(capturedBody).toMatchObject({
|
|
499
|
+
registryId: 'reg_123',
|
|
500
|
+
registryName: 'New Name',
|
|
501
|
+
});
|
|
502
|
+
});
|
|
503
|
+
|
|
504
|
+
it('should delete registry', async () => {
|
|
505
|
+
let capturedBody: unknown;
|
|
506
|
+
|
|
507
|
+
server.use(
|
|
508
|
+
http.post(`${BASE_URL}/api/registry.remove`, async ({ request }) => {
|
|
509
|
+
capturedBody = await request.json();
|
|
510
|
+
return HttpResponse.json({ success: true });
|
|
511
|
+
}),
|
|
512
|
+
);
|
|
513
|
+
|
|
514
|
+
await api.deleteRegistry('reg_123');
|
|
515
|
+
|
|
516
|
+
expect(capturedBody).toMatchObject({
|
|
517
|
+
registryId: 'reg_123',
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
describe('postgres endpoints', () => {
|
|
523
|
+
it('should create postgres', async () => {
|
|
524
|
+
let capturedBody: unknown;
|
|
525
|
+
|
|
526
|
+
server.use(
|
|
527
|
+
http.post(`${BASE_URL}/api/postgres.create`, async ({ request }) => {
|
|
528
|
+
capturedBody = await request.json();
|
|
529
|
+
return HttpResponse.json({
|
|
530
|
+
postgresId: 'pg_123',
|
|
531
|
+
name: 'MyDB',
|
|
532
|
+
appName: 'mydb',
|
|
533
|
+
databaseName: 'app',
|
|
534
|
+
applicationStatus: 'idle',
|
|
535
|
+
});
|
|
536
|
+
}),
|
|
537
|
+
);
|
|
538
|
+
|
|
539
|
+
const result = await api.createPostgres('MyDB', 'proj_1', 'env_1');
|
|
540
|
+
|
|
541
|
+
expect(result.postgresId).toBe('pg_123');
|
|
542
|
+
expect(capturedBody).toMatchObject({
|
|
543
|
+
name: 'MyDB',
|
|
544
|
+
projectId: 'proj_1',
|
|
545
|
+
environmentId: 'env_1',
|
|
546
|
+
appName: 'mydb',
|
|
547
|
+
databaseName: 'app',
|
|
548
|
+
databaseUser: 'postgres',
|
|
549
|
+
dockerImage: 'postgres:16-alpine',
|
|
550
|
+
});
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
it('should create postgres with custom options', async () => {
|
|
554
|
+
let capturedBody: unknown;
|
|
555
|
+
|
|
556
|
+
server.use(
|
|
557
|
+
http.post(`${BASE_URL}/api/postgres.create`, async ({ request }) => {
|
|
558
|
+
capturedBody = await request.json();
|
|
559
|
+
return HttpResponse.json({ postgresId: 'pg_123' });
|
|
560
|
+
}),
|
|
561
|
+
);
|
|
562
|
+
|
|
563
|
+
await api.createPostgres('MyDB', 'proj_1', 'env_1', {
|
|
564
|
+
databaseName: 'customdb',
|
|
565
|
+
databaseUser: 'customuser',
|
|
566
|
+
databasePassword: 'secretpass',
|
|
567
|
+
dockerImage: 'postgres:15',
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
expect(capturedBody).toMatchObject({
|
|
571
|
+
databaseName: 'customdb',
|
|
572
|
+
databaseUser: 'customuser',
|
|
573
|
+
databasePassword: 'secretpass',
|
|
574
|
+
dockerImage: 'postgres:15',
|
|
575
|
+
});
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
it('should get postgres by ID', async () => {
|
|
579
|
+
server.use(
|
|
580
|
+
http.get(`${BASE_URL}/api/postgres.one`, ({ request }) => {
|
|
581
|
+
const url = new URL(request.url);
|
|
582
|
+
expect(url.searchParams.get('postgresId')).toBe('pg_123');
|
|
583
|
+
return HttpResponse.json({
|
|
584
|
+
postgresId: 'pg_123',
|
|
585
|
+
name: 'MyDB',
|
|
586
|
+
applicationStatus: 'running',
|
|
587
|
+
});
|
|
588
|
+
}),
|
|
589
|
+
);
|
|
590
|
+
|
|
591
|
+
const result = await api.getPostgres('pg_123');
|
|
592
|
+
expect(result.postgresId).toBe('pg_123');
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
it('should deploy postgres', async () => {
|
|
596
|
+
let capturedBody: unknown;
|
|
597
|
+
|
|
598
|
+
server.use(
|
|
599
|
+
http.post(`${BASE_URL}/api/postgres.deploy`, async ({ request }) => {
|
|
600
|
+
capturedBody = await request.json();
|
|
601
|
+
return HttpResponse.json({ success: true });
|
|
602
|
+
}),
|
|
603
|
+
);
|
|
604
|
+
|
|
605
|
+
await api.deployPostgres('pg_123');
|
|
606
|
+
|
|
607
|
+
expect(capturedBody).toMatchObject({ postgresId: 'pg_123' });
|
|
608
|
+
});
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
describe('redis endpoints', () => {
|
|
612
|
+
it('should create redis', async () => {
|
|
613
|
+
let capturedBody: unknown;
|
|
614
|
+
|
|
615
|
+
server.use(
|
|
616
|
+
http.post(`${BASE_URL}/api/redis.create`, async ({ request }) => {
|
|
617
|
+
capturedBody = await request.json();
|
|
618
|
+
return HttpResponse.json({
|
|
619
|
+
redisId: 'redis_123',
|
|
620
|
+
name: 'MyCache',
|
|
621
|
+
appName: 'mycache',
|
|
622
|
+
applicationStatus: 'idle',
|
|
623
|
+
});
|
|
624
|
+
}),
|
|
625
|
+
);
|
|
626
|
+
|
|
627
|
+
const result = await api.createRedis('MyCache', 'proj_1', 'env_1');
|
|
628
|
+
|
|
629
|
+
expect(result.redisId).toBe('redis_123');
|
|
630
|
+
expect(capturedBody).toMatchObject({
|
|
631
|
+
name: 'MyCache',
|
|
632
|
+
projectId: 'proj_1',
|
|
633
|
+
environmentId: 'env_1',
|
|
634
|
+
appName: 'mycache',
|
|
635
|
+
dockerImage: 'redis:7-alpine',
|
|
636
|
+
});
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
it('should create redis with custom options', async () => {
|
|
640
|
+
let capturedBody: unknown;
|
|
641
|
+
|
|
642
|
+
server.use(
|
|
643
|
+
http.post(`${BASE_URL}/api/redis.create`, async ({ request }) => {
|
|
644
|
+
capturedBody = await request.json();
|
|
645
|
+
return HttpResponse.json({
|
|
646
|
+
redisId: 'redis_123',
|
|
647
|
+
name: 'MyCache',
|
|
648
|
+
appName: 'mycache',
|
|
649
|
+
databasePassword: 'secretpass',
|
|
650
|
+
applicationStatus: 'idle',
|
|
651
|
+
});
|
|
652
|
+
}),
|
|
653
|
+
);
|
|
654
|
+
|
|
655
|
+
await api.createRedis('MyCache', 'proj_1', 'env_1', {
|
|
656
|
+
databasePassword: 'secretpass',
|
|
657
|
+
dockerImage: 'redis:6-alpine',
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
expect(capturedBody).toMatchObject({
|
|
661
|
+
databasePassword: 'secretpass',
|
|
662
|
+
dockerImage: 'redis:6-alpine',
|
|
663
|
+
});
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
it('should get redis by ID', async () => {
|
|
667
|
+
server.use(
|
|
668
|
+
http.get(`${BASE_URL}/api/redis.one`, ({ request }) => {
|
|
669
|
+
const url = new URL(request.url);
|
|
670
|
+
expect(url.searchParams.get('redisId')).toBe('redis_123');
|
|
671
|
+
return HttpResponse.json({
|
|
672
|
+
redisId: 'redis_123',
|
|
673
|
+
name: 'MyCache',
|
|
674
|
+
applicationStatus: 'running',
|
|
675
|
+
});
|
|
676
|
+
}),
|
|
677
|
+
);
|
|
678
|
+
|
|
679
|
+
const result = await api.getRedis('redis_123');
|
|
680
|
+
expect(result.redisId).toBe('redis_123');
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
it('should deploy redis', async () => {
|
|
684
|
+
let capturedBody: unknown;
|
|
685
|
+
|
|
686
|
+
server.use(
|
|
687
|
+
http.post(`${BASE_URL}/api/redis.deploy`, async ({ request }) => {
|
|
688
|
+
capturedBody = await request.json();
|
|
689
|
+
return HttpResponse.json({ success: true });
|
|
690
|
+
}),
|
|
691
|
+
);
|
|
692
|
+
|
|
693
|
+
await api.deployRedis('redis_123');
|
|
694
|
+
|
|
695
|
+
expect(capturedBody).toMatchObject({ redisId: 'redis_123' });
|
|
696
|
+
});
|
|
697
|
+
});
|
|
698
|
+
});
|