const { test } = require('node:test'); const assert = require('node:assert'); function withMockFetch(mockFetch, fn) { const original = global.fetch; global.fetch = mockFetch; return fn().finally(() => { global.fetch = original; }); } test('trpcGet wraps params in {json:} and unwraps result.data.json', async () => { process.env.DOKPLOY_URL = 'https://dokploy.test'; process.env.DOKPLOY_API_KEY = 'test-key'; const { trpcGet } = require('../src/dokployClient'); await withMockFetch(async (url, options) => { const parsed = new URL(url); assert.strictEqual(parsed.pathname, '/api/trpc/environment.one'); const input = JSON.parse(parsed.searchParams.get('input')); assert.deepStrictEqual(input, { json: { environmentId: 'env-1' } }); assert.strictEqual(options.headers['x-api-key'], 'test-key'); return { ok: true, json: async () => ({ result: { data: { json: { hello: 'world' } } } }), }; }, async () => { const result = await trpcGet('environment.one', { environmentId: 'env-1' }); assert.deepStrictEqual(result, { hello: 'world' }); }); }); test('trpcPost wraps data in {json:} in the request body', async () => { process.env.DOKPLOY_URL = 'https://dokploy.test'; process.env.DOKPLOY_API_KEY = 'test-key'; const { trpcPost } = require('../src/dokployClient'); await withMockFetch(async (url, options) => { const parsed = new URL(url); assert.strictEqual(parsed.pathname, '/api/trpc/compose.start'); assert.deepStrictEqual(JSON.parse(options.body), { json: { composeId: 'c1' } }); assert.strictEqual(options.method, 'POST'); return { ok: true, json: async () => ({ result: { data: { json: { success: true } } } }), }; }, async () => { const result = await trpcPost('compose.start', { composeId: 'c1' }); assert.deepStrictEqual(result, { success: true }); }); }); test('trpcGet throws when the response is not ok', async () => { process.env.DOKPLOY_URL = 'https://dokploy.test'; process.env.DOKPLOY_API_KEY = 'test-key'; const { trpcGet } = require('../src/dokployClient'); await withMockFetch(async () => ({ ok: false, status: 400 }), async () => { await assert.rejects(() => trpcGet('compose.one', { composeId: 'c1' }), /400/); }); }); test('listComposes returns the compose array from environment.one', async () => { process.env.DOKPLOY_URL = 'https://dokploy.test'; process.env.DOKPLOY_API_KEY = 'test-key'; const { listComposes } = require('../src/dokployClient'); await withMockFetch(async () => ({ ok: true, json: async () => ({ result: { data: { json: { compose: [{ composeId: 'c1', name: 'auth-redirect', composeStatus: 'done' }] } } }, }), }), async () => { const composes = await listComposes('env-1'); assert.deepStrictEqual(composes, [{ composeId: 'c1', name: 'auth-redirect', composeStatus: 'done' }]); }); }); test('getComposeEnv only returns env and appName, never leaks gitea/github credentials', async () => { process.env.DOKPLOY_URL = 'https://dokploy.test'; process.env.DOKPLOY_API_KEY = 'test-key'; const { getComposeEnv } = require('../src/dokployClient'); await withMockFetch(async () => ({ ok: true, json: async () => ({ result: { data: { json: { composeId: 'c1', appName: 'auth-redirect-uzt5hx', env: 'PORTAL_APP_TYPE=web\nPORTAL_APP_URL=https://x.apps.next-hd.net', gitea: { clientSecret: 'SHOULD_NOT_LEAK', accessToken: 'SHOULD_NOT_LEAK_EITHER' }, deployments: [{ title: 'irrelevant' }], }, }, }, }), }), async () => { const result = await getComposeEnv('c1'); assert.deepStrictEqual(Object.keys(result).sort(), ['appName', 'env']); assert.strictEqual(result.appName, 'auth-redirect-uzt5hx'); assert.ok(result.env.includes('PORTAL_APP_TYPE=web')); assert.ok(!JSON.stringify(result).includes('SHOULD_NOT_LEAK')); }); }); test('getContainers calls docker.getContainersByAppNameMatch with appType docker-compose', async () => { process.env.DOKPLOY_URL = 'https://dokploy.test'; process.env.DOKPLOY_API_KEY = 'test-key'; const { getContainers } = require('../src/dokployClient'); await withMockFetch(async (url) => { const parsed = new URL(url); assert.strictEqual(parsed.pathname, '/api/trpc/docker.getContainersByAppNameMatch'); const input = JSON.parse(parsed.searchParams.get('input')); assert.deepStrictEqual(input, { json: { appName: 'auth-redirect-uzt5hx', appType: 'docker-compose' } }); return { ok: true, json: async () => ({ result: { data: { json: [{ containerId: 'abc123', state: 'running' }] } } }), }; }, async () => { const containers = await getContainers('auth-redirect-uzt5hx'); assert.deepStrictEqual(containers, [{ containerId: 'abc123', state: 'running' }]); }); }); test('readLogs passes composeId, containerId and tail through', async () => { process.env.DOKPLOY_URL = 'https://dokploy.test'; process.env.DOKPLOY_API_KEY = 'test-key'; const { readLogs } = require('../src/dokployClient'); await withMockFetch(async (url) => { const parsed = new URL(url); const input = JSON.parse(parsed.searchParams.get('input')); assert.deepStrictEqual(input, { json: { composeId: 'c1', containerId: 'abc123', tail: 200 } }); return { ok: true, json: async () => ({ result: { data: { json: 'log line\n' } } }) }; }, async () => { const logs = await readLogs('c1', 'abc123', 200); assert.strictEqual(logs, 'log line\n'); }); }); test('startCompose/stopCompose/restartContainer POST to the right endpoints', async () => { process.env.DOKPLOY_URL = 'https://dokploy.test'; process.env.DOKPLOY_API_KEY = 'test-key'; const { startCompose, stopCompose, restartContainer } = require('../src/dokployClient'); const calledEndpoints = []; await withMockFetch(async (url) => { calledEndpoints.push(new URL(url).pathname); return { ok: true, json: async () => ({ result: { data: { json: { success: true } } } }) }; }, async () => { await startCompose('c1'); await stopCompose('c1'); await restartContainer('abc123'); }); assert.deepStrictEqual(calledEndpoints, [ '/api/trpc/compose.start', '/api/trpc/compose.stop', '/api/trpc/docker.restartContainer', ]); });