test: expand onebox integration coverage

This commit is contained in:
2026-05-24 06:00:23 +00:00
parent 08ab7fea8e
commit 150e023cf9
6 changed files with 528 additions and 90 deletions
+137 -11
View File
@@ -1,6 +1,7 @@
import { dirname, fromFileUrl, join, resolve } from '@std/path';
import { Onebox } from '../../../onebox/ts/classes/onebox.ts';
import { disableManagedDcRouterForScenario } from '../onebox-test-helpers.ts';
const scenarioName = 'onebox-basic-lifecycle';
const smokeId = `onebox-basic-${Date.now().toString(36)}`;
@@ -9,6 +10,7 @@ const buildDir = join(testingDir, '.nogit', scenarioName, smokeId);
const serviceName = `app-${Date.now().toString(36)}`;
const dockerServiceName = `onebox-${serviceName}`;
const routeDomain = `${serviceName}.test`;
const volumeName = `onebox-${serviceName}-data`;
const delayFor = async (millisecondsArg: number) => {
await new Promise((resolveArg) => setTimeout(resolveArg, millisecondsArg));
@@ -69,6 +71,13 @@ const waitFor = async (checkFunctionArg: () => boolean | Promise<boolean>, messa
throw new Error(`Timed out waiting for ${messageArg}`);
};
const getFreeTcpPort = () => {
const listener = Deno.listen({ hostname: '127.0.0.1', port: 0 });
const port = (listener.addr as Deno.NetAddr).port;
listener.close();
return port;
};
const dockerServiceExists = async (serviceNameArg: string) => {
const output = await outputCommand('docker', ['service', 'inspect', serviceNameArg], {
stdout: 'null',
@@ -84,9 +93,35 @@ const removeDockerService = async (serviceNameArg: string) => {
}
};
const removeDockerVolume = async (volumeNameArg: string) => {
let lastErrorText = '';
const startTime = Date.now();
while (Date.now() - startTime < 30000) {
const output = await outputCommand(
'docker',
['volume', 'rm', '-f', volumeNameArg],
{
stdout: 'piped',
stderr: 'piped',
},
15000,
);
if (output.success) {
return;
}
lastErrorText = new TextDecoder().decode(output.stderr).trim();
await delayFor(1000);
}
console.log(
`[${scenarioName}] Failed to remove Docker volume ${volumeNameArg}: ${lastErrorText}`,
);
};
const assertNoPreexistingOneboxIngress = async () => {
if (await dockerServiceExists('onebox-smartproxy')) {
throw new Error('onebox-smartproxy already exists; refusing to overwrite a running Onebox ingress service');
throw new Error(
'onebox-smartproxy already exists; refusing to overwrite a running Onebox ingress service',
);
}
};
@@ -110,7 +145,27 @@ const waitForDockerServiceRunning = async (serviceNameArg: string) => {
};
const waitForDockerServiceRemoved = async (serviceNameArg: string) => {
await waitFor(async () => !(await dockerServiceExists(serviceNameArg)), `${serviceNameArg} removal`);
await waitFor(
async () => !(await dockerServiceExists(serviceNameArg)),
`${serviceNameArg} removal`,
);
};
const inspectDockerServiceJson = async <T>(serviceNameArg: string, formatArg: string) => {
const output = await outputCommand('docker', [
'service',
'inspect',
serviceNameArg,
'--format',
formatArg,
], {
stdout: 'piped',
stderr: 'piped',
}, 15000);
if (!output.success) {
throw new Error(`docker service inspect ${serviceNameArg} exited with ${output.code}`);
}
return JSON.parse(new TextDecoder().decode(output.stdout).trim()) as T;
};
const requestRoute = async (protocolArg: 'http' | 'https', portArg: number) => {
@@ -168,9 +223,13 @@ const waitForRoute = async (protocolArg: 'http' | 'https', portArg: number) => {
console.log(`[${scenarioName}] Last route error: ${lastError.message}`);
}
await run('docker', ['service', 'ps', 'onebox-smartproxy']).catch(() => null);
await run('docker', ['service', 'logs', '--raw', '--tail', '120', 'onebox-smartproxy']).catch(() => null);
await run('docker', ['service', 'logs', '--raw', '--tail', '120', 'onebox-smartproxy']).catch(
() => null,
);
await run('docker', ['service', 'ps', dockerServiceName]).catch(() => null);
await run('docker', ['service', 'logs', '--raw', '--tail', '120', dockerServiceName]).catch(() => null);
await run('docker', ['service', 'logs', '--raw', '--tail', '120', dockerServiceName]).catch(
() => null,
);
throw error;
}
};
@@ -205,13 +264,19 @@ const ensureDockerReady = async () => {
await run('docker', ['version']);
const { stdout } = await run('docker', ['info', '--format', '{{.Swarm.LocalNodeState}}']);
if (stdout.trim() !== 'active') {
throw new Error('Docker Swarm must be active. In Vagrant this is handled by scripts/provision-vm.sh.');
throw new Error(
'Docker Swarm must be active. In Vagrant this is handled by scripts/provision-vm.sh.',
);
}
};
const assertOneboxCoreReady = async (oneboxArg: Onebox) => {
const status = await oneboxArg.getSystemStatus();
const reverseProxy = status.reverseProxy as { backend: string; http: { running: boolean }; https: { running: boolean } };
const reverseProxy = status.reverseProxy as {
backend: string;
http: { running: boolean };
https: { running: boolean };
};
if (reverseProxy.backend !== 'smartproxy-docker') {
throw new Error(`Unexpected reverse proxy backend: ${JSON.stringify(reverseProxy)}`);
}
@@ -225,11 +290,59 @@ const assertOneboxCoreReady = async (oneboxArg: Onebox) => {
}
const platformServices = status.platformServices as Array<{ type: string; status: string }>;
if (!platformServices.some((serviceArg) => serviceArg.type === 'smartproxy' && serviceArg.status === 'running')) {
throw new Error(`SmartProxy platform service not reported as running: ${JSON.stringify(platformServices)}`);
if (
!platformServices.some((serviceArg) =>
serviceArg.type === 'smartproxy' && serviceArg.status === 'running'
)
) {
throw new Error(
`SmartProxy platform service not reported as running: ${JSON.stringify(platformServices)}`,
);
}
if (platformServices.some((serviceArg) => serviceArg.type === 'caddy')) {
throw new Error(`Unexpected legacy caddy platform service: ${JSON.stringify(platformServices)}`);
throw new Error(
`Unexpected legacy caddy platform service: ${JSON.stringify(platformServices)}`,
);
}
};
const assertDockerStorageAndPorts = async (publishedPortArg: number) => {
const mounts = await inspectDockerServiceJson<Array<Record<string, unknown>>>(
dockerServiceName,
'{{json .Spec.TaskTemplate.ContainerSpec.Mounts}}',
);
if (!mounts.some((mountArg) => mountArg.Source === volumeName && mountArg.Target === '/data')) {
throw new Error(`Onebox volume mount missing from Docker service: ${JSON.stringify(mounts)}`);
}
const ports = await inspectDockerServiceJson<Array<Record<string, unknown>>>(
dockerServiceName,
'{{json .Spec.EndpointSpec.Ports}}',
);
if (
!ports.some((portArg) =>
portArg.TargetPort === 80 && portArg.PublishedPort === publishedPortArg &&
portArg.Protocol === 'tcp'
)
) {
throw new Error(`Onebox published port missing from Docker service: ${JSON.stringify(ports)}`);
}
};
const assertServicePersistence = (oneboxArg: Onebox, publishedPortArg: number) => {
const service = oneboxArg.services.getService(serviceName);
if (!service) {
throw new Error(`Service was not persisted: ${serviceName}`);
}
if (!service.volumes?.some((volumeArg) => volumeArg.mountPath === '/data')) {
throw new Error(`Service volume was not persisted: ${JSON.stringify(service)}`);
}
if (
!service.publishedPorts?.some((portArg) =>
portArg.targetPort === 80 && portArg.publishedPort === publishedPortArg
)
) {
throw new Error(`Service published port was not persisted: ${JSON.stringify(service)}`);
}
};
@@ -246,12 +359,14 @@ const main = async () => {
console.log(`[${scenarioName}] Starting Onebox from ${buildDir}`);
onebox = new Onebox();
disableManagedDcRouterForScenario(onebox);
await onebox.init();
await waitForDockerServiceRunning('onebox-smartproxy');
await assertOneboxCoreReady(onebox);
console.log(`[${scenarioName}] Deploying workload ${serviceName}`);
const publishedPort = getFreeTcpPort();
const service = await onebox.services.deployService({
name: serviceName,
image: 'caddy:2-alpine',
@@ -259,6 +374,8 @@ const main = async () => {
domain: routeDomain,
autoDNS: false,
envVars: {},
volumes: [{ mountPath: '/data', backup: true }],
publishedPorts: [{ targetPort: 80, publishedPort, protocol: 'tcp' }],
});
deployedService = true;
@@ -270,10 +387,16 @@ const main = async () => {
}
await waitForDockerServiceRunning(dockerServiceName);
assertServicePersistence(onebox, publishedPort);
await assertDockerStorageAndPorts(publishedPort);
await waitForRoute('http', 8080);
const certificate = await createSelfSignedCertificate();
await onebox.reverseProxy.addCertificate(routeDomain, certificate.publicKey, certificate.privateKey);
await onebox.reverseProxy.addCertificate(
routeDomain,
certificate.publicKey,
certificate.privateKey,
);
await waitForRoute('https', 8443);
console.log(`[${scenarioName}] Removing workload ${serviceName}`);
@@ -288,7 +411,9 @@ const main = async () => {
} finally {
if (onebox && deployedService) {
await onebox.services.removeService(serviceName).catch((error) => {
console.log(`[${scenarioName}] Failed to remove Onebox service: ${(error as Error).message}`);
console.log(
`[${scenarioName}] Failed to remove Onebox service: ${(error as Error).message}`,
);
});
}
if (onebox) {
@@ -298,6 +423,7 @@ const main = async () => {
}
await removeDockerService(dockerServiceName);
await removeDockerService('onebox-smartproxy');
await removeDockerVolume(volumeName);
}
};