test: expand onebox integration coverage
This commit is contained in:
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user