@framers/agentos-ext-cloud-digitalocean 0.1.0 → 0.1.2
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/manifest.json +4 -4
- package/package.json +15 -7
- package/src/DigitalOceanService.ts +0 -514
- package/src/index.ts +0 -130
- package/src/tools/createApp.ts +0 -69
- package/src/tools/createDroplet.ts +0 -74
- package/src/tools/deleteResource.ts +0 -64
- package/src/tools/deployApp.ts +0 -41
- package/src/tools/listResources.ts +0 -66
- package/src/tools/manageDns.ts +0 -131
- package/test/DigitalOceanService.test.ts +0 -435
- package/test/index.test.ts +0 -58
- package/test/tools.test.ts +0 -478
- package/tsconfig.json +0 -22
|
@@ -1,435 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
3
|
-
import { DigitalOceanService } from '../src/DigitalOceanService.js';
|
|
4
|
-
|
|
5
|
-
// ---------------------------------------------------------------------------
|
|
6
|
-
// Helpers
|
|
7
|
-
// ---------------------------------------------------------------------------
|
|
8
|
-
|
|
9
|
-
function mockResponse(data: unknown, status = 200): Response {
|
|
10
|
-
return {
|
|
11
|
-
ok: status >= 200 && status < 300,
|
|
12
|
-
status,
|
|
13
|
-
text: () => Promise.resolve(typeof data === 'string' ? data : JSON.stringify(data)),
|
|
14
|
-
json: () => Promise.resolve(data),
|
|
15
|
-
headers: new Headers(),
|
|
16
|
-
} as unknown as Response;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const TOKEN = 'test-do-token';
|
|
20
|
-
|
|
21
|
-
function createService() {
|
|
22
|
-
return new DigitalOceanService({
|
|
23
|
-
token: TOKEN,
|
|
24
|
-
baseUrl: 'https://api.digitalocean.com/v2',
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// ---------------------------------------------------------------------------
|
|
29
|
-
// Tests
|
|
30
|
-
// ---------------------------------------------------------------------------
|
|
31
|
-
|
|
32
|
-
describe('DigitalOceanService', () => {
|
|
33
|
-
let mockFetch: ReturnType<typeof vi.fn>;
|
|
34
|
-
let service: DigitalOceanService;
|
|
35
|
-
|
|
36
|
-
beforeEach(() => {
|
|
37
|
-
mockFetch = vi.fn();
|
|
38
|
-
vi.stubGlobal('fetch', mockFetch);
|
|
39
|
-
service = createService();
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
afterEach(() => {
|
|
43
|
-
vi.restoreAllMocks();
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
// ── initialize / shutdown ─────────────────────────────────────────────
|
|
47
|
-
|
|
48
|
-
describe('initialize', () => {
|
|
49
|
-
it('should set isRunning on valid token', async () => {
|
|
50
|
-
mockFetch.mockResolvedValueOnce(mockResponse({ account: { status: 'active' } }));
|
|
51
|
-
|
|
52
|
-
await service.initialize();
|
|
53
|
-
|
|
54
|
-
expect(service.isRunning).toBe(true);
|
|
55
|
-
expect(mockFetch).toHaveBeenCalledWith(
|
|
56
|
-
'https://api.digitalocean.com/v2/account',
|
|
57
|
-
expect.objectContaining({
|
|
58
|
-
headers: expect.objectContaining({
|
|
59
|
-
Authorization: `Bearer ${TOKEN}`,
|
|
60
|
-
}),
|
|
61
|
-
}),
|
|
62
|
-
);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
it('should throw on auth failure', async () => {
|
|
66
|
-
mockFetch.mockResolvedValueOnce(mockResponse('Unauthorized', 401));
|
|
67
|
-
|
|
68
|
-
await expect(service.initialize()).rejects.toThrow('DigitalOcean auth failed: 401');
|
|
69
|
-
expect(service.isRunning).toBe(false);
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
describe('shutdown', () => {
|
|
74
|
-
it('should set isRunning to false', async () => {
|
|
75
|
-
mockFetch.mockResolvedValueOnce(mockResponse({ account: {} }));
|
|
76
|
-
await service.initialize();
|
|
77
|
-
await service.shutdown();
|
|
78
|
-
expect(service.isRunning).toBe(false);
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
// ── Apps (App Platform) ───────────────────────────────────────────────
|
|
83
|
-
|
|
84
|
-
describe('createApp', () => {
|
|
85
|
-
it('should create a service-type app', async () => {
|
|
86
|
-
mockFetch.mockResolvedValueOnce(mockResponse({
|
|
87
|
-
app: {
|
|
88
|
-
id: 'app-1',
|
|
89
|
-
default_ingress: 'https://app-1.ondigitalocean.app',
|
|
90
|
-
live_url: 'https://app-1.ondigitalocean.app',
|
|
91
|
-
spec: {
|
|
92
|
-
name: 'my-service',
|
|
93
|
-
region: 'nyc',
|
|
94
|
-
services: [{
|
|
95
|
-
name: 'my-service',
|
|
96
|
-
git: { repo_clone_url: 'https://github.com/acme/app', branch: 'main' },
|
|
97
|
-
build_command: 'npm run build',
|
|
98
|
-
run_command: 'npm start',
|
|
99
|
-
environment_slug: 'node-js',
|
|
100
|
-
instance_count: 1,
|
|
101
|
-
instance_size_slug: 'basic-xxs',
|
|
102
|
-
}],
|
|
103
|
-
},
|
|
104
|
-
created_at: '2024-01-01',
|
|
105
|
-
updated_at: '2024-01-01',
|
|
106
|
-
},
|
|
107
|
-
}));
|
|
108
|
-
|
|
109
|
-
const app = await service.createApp({
|
|
110
|
-
name: 'my-service',
|
|
111
|
-
gitUrl: 'https://github.com/acme/app',
|
|
112
|
-
buildCommand: 'npm run build',
|
|
113
|
-
runCommand: 'npm start',
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
expect(app.id).toBe('app-1');
|
|
117
|
-
expect(app.spec.name).toBe('my-service');
|
|
118
|
-
|
|
119
|
-
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
|
|
120
|
-
expect(body.spec.services).toBeDefined();
|
|
121
|
-
expect(body.spec.services[0].instance_size_slug).toBe('basic-xxs');
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
it('should create a static-site app when isStatic is true', async () => {
|
|
125
|
-
mockFetch.mockResolvedValueOnce(mockResponse({
|
|
126
|
-
app: {
|
|
127
|
-
id: 'app-2',
|
|
128
|
-
default_ingress: '',
|
|
129
|
-
live_url: '',
|
|
130
|
-
spec: { name: 'static-site', static_sites: [{ name: 'static-site' }] },
|
|
131
|
-
created_at: '2024-01-01',
|
|
132
|
-
updated_at: '2024-01-01',
|
|
133
|
-
},
|
|
134
|
-
}));
|
|
135
|
-
|
|
136
|
-
await service.createApp({
|
|
137
|
-
name: 'static-site',
|
|
138
|
-
gitUrl: 'https://github.com/acme/static',
|
|
139
|
-
isStatic: true,
|
|
140
|
-
outputDir: 'dist',
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
|
|
144
|
-
expect(body.spec.static_sites).toBeDefined();
|
|
145
|
-
expect(body.spec.static_sites[0].output_dir).toBe('dist');
|
|
146
|
-
expect(body.spec.services).toBeUndefined();
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
it('should include env vars when provided', async () => {
|
|
150
|
-
mockFetch.mockResolvedValueOnce(mockResponse({
|
|
151
|
-
app: {
|
|
152
|
-
id: 'app-3', default_ingress: '', live_url: '',
|
|
153
|
-
spec: { name: 'env-app', services: [] },
|
|
154
|
-
created_at: '', updated_at: '',
|
|
155
|
-
},
|
|
156
|
-
}));
|
|
157
|
-
|
|
158
|
-
await service.createApp({
|
|
159
|
-
name: 'env-app',
|
|
160
|
-
gitUrl: 'https://github.com/acme/env-app',
|
|
161
|
-
envVars: { API_KEY: 'abc' },
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
|
|
165
|
-
const envs = body.spec.services[0].envs;
|
|
166
|
-
expect(envs).toEqual([
|
|
167
|
-
{ key: 'API_KEY', value: 'abc', scope: 'RUN_AND_BUILD_TIME', type: 'GENERAL' },
|
|
168
|
-
]);
|
|
169
|
-
});
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
describe('listApps', () => {
|
|
173
|
-
it('should return mapped apps', async () => {
|
|
174
|
-
mockFetch.mockResolvedValueOnce(mockResponse({
|
|
175
|
-
apps: [
|
|
176
|
-
{ id: 'a1', default_ingress: '', live_url: '', spec: { name: 'app1' }, created_at: '', updated_at: '' },
|
|
177
|
-
{ id: 'a2', default_ingress: '', live_url: '', spec: { name: 'app2' }, created_at: '', updated_at: '' },
|
|
178
|
-
],
|
|
179
|
-
}));
|
|
180
|
-
|
|
181
|
-
const apps = await service.listApps();
|
|
182
|
-
expect(apps).toHaveLength(2);
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
it('should pass page and perPage', async () => {
|
|
186
|
-
mockFetch.mockResolvedValueOnce(mockResponse({ apps: [] }));
|
|
187
|
-
|
|
188
|
-
await service.listApps(2, 10);
|
|
189
|
-
|
|
190
|
-
const url = mockFetch.mock.calls[0][0] as string;
|
|
191
|
-
expect(url).toContain('page=2');
|
|
192
|
-
expect(url).toContain('per_page=10');
|
|
193
|
-
});
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
describe('getApp', () => {
|
|
197
|
-
it('should return a single mapped app', async () => {
|
|
198
|
-
mockFetch.mockResolvedValueOnce(mockResponse({
|
|
199
|
-
app: { id: 'a1', default_ingress: 'https://a1.do.app', live_url: '', spec: { name: 'my-app' }, created_at: '', updated_at: '' },
|
|
200
|
-
}));
|
|
201
|
-
|
|
202
|
-
const app = await service.getApp('a1');
|
|
203
|
-
expect(app.id).toBe('a1');
|
|
204
|
-
expect(app.defaultIngress).toBe('https://a1.do.app');
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
it('should throw on error response', async () => {
|
|
208
|
-
mockFetch.mockResolvedValueOnce(mockResponse('Not Found', 404));
|
|
209
|
-
|
|
210
|
-
await expect(service.getApp('bad')).rejects.toThrow('DigitalOcean API 404');
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
describe('deleteApp', () => {
|
|
215
|
-
it('should send DELETE request', async () => {
|
|
216
|
-
mockFetch.mockResolvedValueOnce(mockResponse({}, 204));
|
|
217
|
-
|
|
218
|
-
await service.deleteApp('a1');
|
|
219
|
-
|
|
220
|
-
expect(mockFetch.mock.calls[0][1].method).toBe('DELETE');
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
it('should throw on non-OK response', async () => {
|
|
224
|
-
mockFetch.mockResolvedValueOnce(mockResponse('Forbidden', 403));
|
|
225
|
-
|
|
226
|
-
await expect(service.deleteApp('a1')).rejects.toThrow('DigitalOcean API 403');
|
|
227
|
-
});
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
describe('createDeployment', () => {
|
|
231
|
-
it('should trigger a deployment', async () => {
|
|
232
|
-
mockFetch.mockResolvedValueOnce(mockResponse({
|
|
233
|
-
deployment: { id: 'dep-1', phase: 'PENDING_BUILD', created_at: '2024-01-01', updated_at: '2024-01-01', cause: 'manual' },
|
|
234
|
-
}));
|
|
235
|
-
|
|
236
|
-
const dep = await service.createDeployment('a1');
|
|
237
|
-
|
|
238
|
-
expect(dep.id).toBe('dep-1');
|
|
239
|
-
expect(dep.phase).toBe('PENDING_BUILD');
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it('should include force_build when requested', async () => {
|
|
243
|
-
mockFetch.mockResolvedValueOnce(mockResponse({
|
|
244
|
-
deployment: { id: 'dep-2', phase: 'BUILDING', created_at: '', updated_at: '', cause: 'force' },
|
|
245
|
-
}));
|
|
246
|
-
|
|
247
|
-
await service.createDeployment('a1', true);
|
|
248
|
-
|
|
249
|
-
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
|
|
250
|
-
expect(body.force_build).toBe(true);
|
|
251
|
-
});
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
// ── Droplets ──────────────────────────────────────────────────────────
|
|
255
|
-
|
|
256
|
-
describe('createDroplet', () => {
|
|
257
|
-
it('should create a droplet with required fields', async () => {
|
|
258
|
-
mockFetch.mockResolvedValueOnce(mockResponse({
|
|
259
|
-
droplet: {
|
|
260
|
-
id: 12345, name: 'web-1', status: 'new', memory: 1024, vcpus: 1, disk: 25,
|
|
261
|
-
region: { slug: 'nyc1', name: 'New York 1' },
|
|
262
|
-
image: { id: 100, slug: 'ubuntu-24-04-x64', name: 'Ubuntu 24.04' },
|
|
263
|
-
size_slug: 's-1vcpu-1gb',
|
|
264
|
-
networks: { v4: [{ ip_address: '1.2.3.4', type: 'public' }], v6: [] },
|
|
265
|
-
tags: [], created_at: '2024-01-01',
|
|
266
|
-
},
|
|
267
|
-
}));
|
|
268
|
-
|
|
269
|
-
const droplet = await service.createDroplet({
|
|
270
|
-
name: 'web-1',
|
|
271
|
-
region: 'nyc1',
|
|
272
|
-
size: 's-1vcpu-1gb',
|
|
273
|
-
image: 'ubuntu-24-04-x64',
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
expect(droplet.id).toBe(12345);
|
|
277
|
-
expect(droplet.name).toBe('web-1');
|
|
278
|
-
expect(droplet.networks.v4[0].ipAddress).toBe('1.2.3.4');
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
it('should include optional fields', async () => {
|
|
282
|
-
mockFetch.mockResolvedValueOnce(mockResponse({
|
|
283
|
-
droplet: {
|
|
284
|
-
id: 12346, name: 'web-2', status: 'new', memory: 2048, vcpus: 2, disk: 50,
|
|
285
|
-
region: { slug: 'sfo3', name: 'San Francisco 3' },
|
|
286
|
-
image: { id: 101, slug: 'docker-20-04', name: 'Docker' },
|
|
287
|
-
size_slug: 's-2vcpu-4gb',
|
|
288
|
-
networks: { v4: [], v6: [] },
|
|
289
|
-
tags: ['web'], created_at: '2024-01-01',
|
|
290
|
-
},
|
|
291
|
-
}));
|
|
292
|
-
|
|
293
|
-
await service.createDroplet({
|
|
294
|
-
name: 'web-2',
|
|
295
|
-
region: 'sfo3',
|
|
296
|
-
size: 's-2vcpu-4gb',
|
|
297
|
-
image: 'docker-20-04',
|
|
298
|
-
sshKeys: ['ssh-key-1'],
|
|
299
|
-
backups: true,
|
|
300
|
-
ipv6: true,
|
|
301
|
-
tags: ['web'],
|
|
302
|
-
userData: '#!/bin/bash\necho hello',
|
|
303
|
-
});
|
|
304
|
-
|
|
305
|
-
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
|
|
306
|
-
expect(body.ssh_keys).toEqual(['ssh-key-1']);
|
|
307
|
-
expect(body.backups).toBe(true);
|
|
308
|
-
expect(body.ipv6).toBe(true);
|
|
309
|
-
expect(body.tags).toEqual(['web']);
|
|
310
|
-
expect(body.user_data).toBe('#!/bin/bash\necho hello');
|
|
311
|
-
});
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
describe('listDroplets', () => {
|
|
315
|
-
it('should return mapped droplets', async () => {
|
|
316
|
-
mockFetch.mockResolvedValueOnce(mockResponse({
|
|
317
|
-
droplets: [
|
|
318
|
-
{
|
|
319
|
-
id: 1, name: 'd1', status: 'active', memory: 512, vcpus: 1, disk: 25,
|
|
320
|
-
region: { slug: 'nyc1', name: 'NYC' }, image: { id: 1, slug: 'ubuntu', name: 'Ubuntu' },
|
|
321
|
-
size_slug: 's-1vcpu-1gb', networks: { v4: [], v6: [] }, tags: [], created_at: '',
|
|
322
|
-
},
|
|
323
|
-
],
|
|
324
|
-
}));
|
|
325
|
-
|
|
326
|
-
const droplets = await service.listDroplets();
|
|
327
|
-
expect(droplets).toHaveLength(1);
|
|
328
|
-
expect(droplets[0].status).toBe('active');
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
it('should filter by tag when provided', async () => {
|
|
332
|
-
mockFetch.mockResolvedValueOnce(mockResponse({ droplets: [] }));
|
|
333
|
-
|
|
334
|
-
await service.listDroplets(1, 20, 'web');
|
|
335
|
-
|
|
336
|
-
const url = mockFetch.mock.calls[0][0] as string;
|
|
337
|
-
expect(url).toContain('tag_name=web');
|
|
338
|
-
});
|
|
339
|
-
});
|
|
340
|
-
|
|
341
|
-
describe('deleteDroplet', () => {
|
|
342
|
-
it('should send DELETE and succeed on 204', async () => {
|
|
343
|
-
mockFetch.mockResolvedValueOnce(mockResponse({}, 204));
|
|
344
|
-
|
|
345
|
-
await service.deleteDroplet(12345);
|
|
346
|
-
|
|
347
|
-
expect(mockFetch.mock.calls[0][1].method).toBe('DELETE');
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
it('should throw on non-204 error', async () => {
|
|
351
|
-
mockFetch.mockResolvedValueOnce(mockResponse('Server Error', 500));
|
|
352
|
-
|
|
353
|
-
await expect(service.deleteDroplet(12345)).rejects.toThrow('DigitalOcean API 500');
|
|
354
|
-
});
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
// ── DNS ───────────────────────────────────────────────────────────────
|
|
358
|
-
|
|
359
|
-
describe('listDomains', () => {
|
|
360
|
-
it('should return mapped domains', async () => {
|
|
361
|
-
mockFetch.mockResolvedValueOnce(mockResponse({
|
|
362
|
-
domains: [
|
|
363
|
-
{ name: 'example.com', ttl: 1800, zone_file: '$ORIGIN...' },
|
|
364
|
-
],
|
|
365
|
-
}));
|
|
366
|
-
|
|
367
|
-
const domains = await service.listDomains();
|
|
368
|
-
expect(domains).toHaveLength(1);
|
|
369
|
-
expect(domains[0].name).toBe('example.com');
|
|
370
|
-
expect(domains[0].ttl).toBe(1800);
|
|
371
|
-
});
|
|
372
|
-
});
|
|
373
|
-
|
|
374
|
-
describe('addDomain', () => {
|
|
375
|
-
it('should add a domain and return it', async () => {
|
|
376
|
-
mockFetch.mockResolvedValueOnce(mockResponse({
|
|
377
|
-
domain: { name: 'newdomain.com', ttl: 1800, zone_file: '' },
|
|
378
|
-
}));
|
|
379
|
-
|
|
380
|
-
const domain = await service.addDomain('newdomain.com');
|
|
381
|
-
expect(domain.name).toBe('newdomain.com');
|
|
382
|
-
});
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
describe('createDomainRecord', () => {
|
|
386
|
-
it('should create a DNS record', async () => {
|
|
387
|
-
mockFetch.mockResolvedValueOnce(mockResponse({
|
|
388
|
-
domain_record: { id: 100, type: 'A', name: 'api', data: '1.2.3.4', ttl: 1800 },
|
|
389
|
-
}));
|
|
390
|
-
|
|
391
|
-
const record = await service.createDomainRecord('example.com', {
|
|
392
|
-
type: 'A', name: 'api', data: '1.2.3.4',
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
expect(record.id).toBe(100);
|
|
396
|
-
expect(record.type).toBe('A');
|
|
397
|
-
expect(record.data).toBe('1.2.3.4');
|
|
398
|
-
});
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
describe('updateDomainRecord', () => {
|
|
402
|
-
it('should update a DNS record', async () => {
|
|
403
|
-
mockFetch.mockResolvedValueOnce(mockResponse({
|
|
404
|
-
domain_record: { id: 100, type: 'A', name: 'api', data: '5.6.7.8', ttl: 3600 },
|
|
405
|
-
}));
|
|
406
|
-
|
|
407
|
-
const record = await service.updateDomainRecord('example.com', 100, {
|
|
408
|
-
data: '5.6.7.8', ttl: 3600,
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
expect(record.data).toBe('5.6.7.8');
|
|
412
|
-
expect(mockFetch.mock.calls[0][1].method).toBe('PUT');
|
|
413
|
-
});
|
|
414
|
-
});
|
|
415
|
-
|
|
416
|
-
describe('deleteDomainRecord', () => {
|
|
417
|
-
it('should send DELETE and succeed on 204', async () => {
|
|
418
|
-
mockFetch.mockResolvedValueOnce(mockResponse({}, 204));
|
|
419
|
-
|
|
420
|
-
await service.deleteDomainRecord('example.com', 100);
|
|
421
|
-
|
|
422
|
-
const url = mockFetch.mock.calls[0][0] as string;
|
|
423
|
-
expect(url).toContain('/domains/example.com/records/100');
|
|
424
|
-
expect(mockFetch.mock.calls[0][1].method).toBe('DELETE');
|
|
425
|
-
});
|
|
426
|
-
|
|
427
|
-
it('should throw on error', async () => {
|
|
428
|
-
mockFetch.mockResolvedValueOnce(mockResponse('Bad Request', 400));
|
|
429
|
-
|
|
430
|
-
await expect(service.deleteDomainRecord('example.com', 999)).rejects.toThrow(
|
|
431
|
-
'DigitalOcean API 400',
|
|
432
|
-
);
|
|
433
|
-
});
|
|
434
|
-
});
|
|
435
|
-
});
|
package/test/index.test.ts
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
3
|
-
import { createExtensionPack } from '../src/index.js';
|
|
4
|
-
|
|
5
|
-
const mockFetch = vi.fn();
|
|
6
|
-
vi.stubGlobal('fetch', mockFetch);
|
|
7
|
-
|
|
8
|
-
function okResponse(data: any = {}) {
|
|
9
|
-
return { ok: true, status: 200, text: async () => JSON.stringify(data), json: async () => data } as Response;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
describe('createExtensionPack (DigitalOcean)', () => {
|
|
13
|
-
beforeEach(() => {
|
|
14
|
-
vi.clearAllMocks();
|
|
15
|
-
mockFetch.mockResolvedValue(okResponse({ account: { status: 'active' } }));
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
it('should return a valid ExtensionPack', () => {
|
|
19
|
-
const pack = createExtensionPack({ secrets: { 'digitalocean.token': 'tok' } });
|
|
20
|
-
expect(pack.name).toBe('@framers/agentos-ext-cloud-digitalocean');
|
|
21
|
-
expect(pack.version).toBe('0.1.0');
|
|
22
|
-
expect(pack.descriptors).toHaveLength(6);
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('should register correct tool IDs', () => {
|
|
26
|
-
const pack = createExtensionPack({ secrets: { 'digitalocean.token': 'tok' } });
|
|
27
|
-
const ids = pack.descriptors.map((d) => d.id);
|
|
28
|
-
expect(ids).toContain('doCreateApp');
|
|
29
|
-
expect(ids).toContain('doCreateDroplet');
|
|
30
|
-
expect(ids).toContain('doListResources');
|
|
31
|
-
expect(ids).toContain('doDeployApp');
|
|
32
|
-
expect(ids).toContain('doManageDns');
|
|
33
|
-
expect(ids).toContain('doDeleteResource');
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('should set all descriptors to kind "tool"', () => {
|
|
37
|
-
const pack = createExtensionPack({ secrets: { 'digitalocean.token': 'tok' } });
|
|
38
|
-
for (const d of pack.descriptors) {
|
|
39
|
-
expect(d.kind).toBe('tool');
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('onActivate should throw without token', async () => {
|
|
44
|
-
const pack = createExtensionPack({ secrets: {} });
|
|
45
|
-
await expect(pack.onActivate!()).rejects.toThrow();
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('onActivate should succeed with valid token', async () => {
|
|
49
|
-
const pack = createExtensionPack({ secrets: { 'digitalocean.token': 'valid' } });
|
|
50
|
-
await expect(pack.onActivate!()).resolves.toBeUndefined();
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('onDeactivate should complete', async () => {
|
|
54
|
-
const pack = createExtensionPack({ secrets: { 'digitalocean.token': 'valid' } });
|
|
55
|
-
await pack.onActivate!();
|
|
56
|
-
await expect(pack.onDeactivate!()).resolves.toBeUndefined();
|
|
57
|
-
});
|
|
58
|
-
});
|