2025-08-18 21:47:31 +00:00
|
|
|
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
2024-06-10 00:15:01 +02:00
|
|
|
import { Qenv } from '@push.rocks/qenv';
|
|
|
|
|
|
|
|
|
|
const testQenv = new Qenv('./', './.nogit/');
|
2024-06-05 14:10:44 +02:00
|
|
|
|
|
|
|
|
import * as plugins from '../ts/plugins.js';
|
|
|
|
|
import * as paths from '../ts/paths.js';
|
|
|
|
|
|
2022-10-17 09:36:35 +02:00
|
|
|
import * as docker from '../ts/index.js';
|
2020-10-01 14:59:37 +00:00
|
|
|
|
|
|
|
|
let testDockerHost: docker.DockerHost;
|
|
|
|
|
|
|
|
|
|
tap.test('should create a new Dockersock instance', async () => {
|
2024-06-05 23:56:02 +02:00
|
|
|
testDockerHost = new docker.DockerHost({});
|
2024-06-08 15:03:19 +02:00
|
|
|
await testDockerHost.start();
|
2022-10-17 09:36:35 +02:00
|
|
|
return expect(testDockerHost).toBeInstanceOf(docker.DockerHost);
|
2020-10-01 14:59:37 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
tap.test('should create a docker swarm', async () => {
|
|
|
|
|
await testDockerHost.activateSwarm();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Containers
|
|
|
|
|
tap.test('should list containers', async () => {
|
2025-11-24 13:27:10 +00:00
|
|
|
const containers = await testDockerHost.listContainers();
|
2020-10-01 14:59:37 +00:00
|
|
|
console.log(containers);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Networks
|
|
|
|
|
tap.test('should list networks', async () => {
|
2025-11-24 13:27:10 +00:00
|
|
|
const networks = await testDockerHost.listNetworks();
|
2020-10-01 14:59:37 +00:00
|
|
|
console.log(networks);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
tap.test('should create a network', async () => {
|
2025-11-24 12:20:30 +00:00
|
|
|
const newNetwork = await testDockerHost.createNetwork({
|
2020-10-01 14:59:37 +00:00
|
|
|
Name: 'webgateway',
|
|
|
|
|
});
|
2022-10-17 09:36:35 +02:00
|
|
|
expect(newNetwork).toBeInstanceOf(docker.DockerNetwork);
|
|
|
|
|
expect(newNetwork.Name).toEqual('webgateway');
|
2020-10-01 14:59:37 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
tap.test('should remove a network', async () => {
|
2025-11-24 12:20:30 +00:00
|
|
|
const webgateway = await testDockerHost.getNetworkByName('webgateway');
|
2020-10-01 14:59:37 +00:00
|
|
|
await webgateway.remove();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Images
|
|
|
|
|
tap.test('should pull an image from imagetag', async () => {
|
2025-11-24 12:20:30 +00:00
|
|
|
const image = await testDockerHost.createImageFromRegistry({
|
|
|
|
|
imageUrl: 'hosttoday/ht-docker-node',
|
|
|
|
|
imageTag: 'alpine',
|
2020-10-01 14:59:37 +00:00
|
|
|
});
|
2022-10-17 09:36:35 +02:00
|
|
|
expect(image).toBeInstanceOf(docker.DockerImage);
|
2020-10-01 14:59:37 +00:00
|
|
|
console.log(image);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
tap.test('should return a change Observable', async (tools) => {
|
|
|
|
|
const testObservable = await testDockerHost.getEventObservable();
|
|
|
|
|
const subscription = testObservable.subscribe((changeObject) => {
|
|
|
|
|
console.log(changeObject);
|
|
|
|
|
});
|
|
|
|
|
await tools.delayFor(2000);
|
|
|
|
|
subscription.unsubscribe();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// SECRETS
|
|
|
|
|
tap.test('should create a secret', async () => {
|
2025-11-24 12:20:30 +00:00
|
|
|
const mySecret = await testDockerHost.createSecret({
|
2020-10-01 14:59:37 +00:00
|
|
|
name: 'testSecret',
|
|
|
|
|
version: '1.0.3',
|
|
|
|
|
contentArg: `{ "hi": "wow"}`,
|
|
|
|
|
labels: {},
|
|
|
|
|
});
|
|
|
|
|
console.log(mySecret);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
tap.test('should remove a secret by name', async () => {
|
2025-11-24 12:20:30 +00:00
|
|
|
const mySecret = await testDockerHost.getSecretByName('testSecret');
|
2020-10-01 14:59:37 +00:00
|
|
|
await mySecret.remove();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// SERVICES
|
|
|
|
|
tap.test('should activate swarm mode', async () => {
|
|
|
|
|
await testDockerHost.activateSwarm();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
tap.test('should list all services', async (tools) => {
|
2025-11-24 13:27:10 +00:00
|
|
|
const services = await testDockerHost.listServices();
|
2020-10-01 14:59:37 +00:00
|
|
|
console.log(services);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
tap.test('should create a service', async () => {
|
2025-11-24 12:20:30 +00:00
|
|
|
const testNetwork = await testDockerHost.createNetwork({
|
2020-10-01 14:59:37 +00:00
|
|
|
Name: 'testNetwork',
|
|
|
|
|
});
|
2025-11-24 12:20:30 +00:00
|
|
|
const testSecret = await testDockerHost.createSecret({
|
2020-10-01 14:59:37 +00:00
|
|
|
name: 'testSecret',
|
|
|
|
|
version: '0.0.1',
|
|
|
|
|
labels: {},
|
|
|
|
|
contentArg: '{"hi": "wow"}',
|
|
|
|
|
});
|
2025-11-24 12:20:30 +00:00
|
|
|
const testImage = await testDockerHost.createImageFromRegistry({
|
|
|
|
|
imageUrl: 'code.foss.global/host.today/ht-docker-node:latest',
|
|
|
|
|
});
|
|
|
|
|
const testService = await testDockerHost.createService({
|
2020-10-01 14:59:37 +00:00
|
|
|
image: testImage,
|
|
|
|
|
labels: {},
|
|
|
|
|
name: 'testService',
|
|
|
|
|
networks: [testNetwork],
|
|
|
|
|
networkAlias: 'testService',
|
|
|
|
|
secrets: [testSecret],
|
|
|
|
|
ports: ['3000:80'],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await testService.remove();
|
|
|
|
|
await testNetwork.remove();
|
|
|
|
|
await testSecret.remove();
|
|
|
|
|
});
|
|
|
|
|
|
2024-08-21 16:04:42 +02:00
|
|
|
tap.test('should export images', async (toolsArg) => {
|
2024-06-05 14:10:44 +02:00
|
|
|
const done = toolsArg.defer();
|
2025-11-24 12:20:30 +00:00
|
|
|
const testImage = await testDockerHost.createImageFromRegistry({
|
|
|
|
|
imageUrl: 'code.foss.global/host.today/ht-docker-node:latest',
|
|
|
|
|
});
|
2024-06-05 14:10:44 +02:00
|
|
|
const fsWriteStream = plugins.smartfile.fsStream.createWriteStream(
|
2025-08-19 01:46:37 +00:00
|
|
|
plugins.path.join(paths.nogitDir, 'testimage.tar'),
|
2024-06-05 14:10:44 +02:00
|
|
|
);
|
|
|
|
|
const exportStream = await testImage.exportToTarStream();
|
|
|
|
|
exportStream.pipe(fsWriteStream).on('finish', () => {
|
|
|
|
|
done.resolve();
|
|
|
|
|
});
|
|
|
|
|
await done.promise;
|
|
|
|
|
});
|
|
|
|
|
|
2025-08-18 23:41:16 +00:00
|
|
|
tap.test('should import images', async () => {
|
2024-06-05 14:10:44 +02:00
|
|
|
const fsReadStream = plugins.smartfile.fsStream.createReadStream(
|
2025-08-19 01:46:37 +00:00
|
|
|
plugins.path.join(paths.nogitDir, 'testimage.tar'),
|
|
|
|
|
);
|
2025-11-24 12:20:30 +00:00
|
|
|
const importedImage = await testDockerHost.createImageFromTarStream(
|
|
|
|
|
fsReadStream,
|
2025-08-19 01:46:37 +00:00
|
|
|
{
|
2025-11-24 12:20:30 +00:00
|
|
|
imageUrl: 'code.foss.global/host.today/ht-docker-node:latest',
|
2025-08-19 01:46:37 +00:00
|
|
|
},
|
2024-06-05 14:10:44 +02:00
|
|
|
);
|
2025-08-18 23:41:16 +00:00
|
|
|
expect(importedImage).toBeInstanceOf(docker.DockerImage);
|
2024-06-08 15:03:19 +02:00
|
|
|
});
|
|
|
|
|
|
2025-08-19 01:42:02 +00:00
|
|
|
tap.test('should expose a working DockerImageStore', async () => {
|
2024-06-10 00:15:01 +02:00
|
|
|
// lets first add am s3 target
|
|
|
|
|
const s3Descriptor = {
|
|
|
|
|
endpoint: await testQenv.getEnvVarOnDemand('S3_ENDPOINT'),
|
|
|
|
|
accessKey: await testQenv.getEnvVarOnDemand('S3_ACCESSKEY'),
|
|
|
|
|
accessSecret: await testQenv.getEnvVarOnDemand('S3_ACCESSSECRET'),
|
|
|
|
|
bucketName: await testQenv.getEnvVarOnDemand('S3_BUCKET'),
|
|
|
|
|
};
|
|
|
|
|
await testDockerHost.addS3Storage(s3Descriptor);
|
|
|
|
|
|
2025-11-24 12:20:30 +00:00
|
|
|
// Use the new public API instead of direct imageStore access
|
|
|
|
|
await testDockerHost.storeImage(
|
2025-08-19 01:46:37 +00:00
|
|
|
'hello2',
|
|
|
|
|
plugins.smartfile.fsStream.createReadStream(
|
|
|
|
|
plugins.path.join(paths.nogitDir, 'testimage.tar'),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
});
|
2024-06-05 14:10:44 +02:00
|
|
|
|
2025-11-24 12:20:30 +00:00
|
|
|
// CONTAINER STREAMING FEATURES
|
|
|
|
|
let testContainer: docker.DockerContainer;
|
|
|
|
|
|
|
|
|
|
tap.test('should get an existing container for streaming tests', async () => {
|
2025-11-24 13:27:10 +00:00
|
|
|
const containers = await testDockerHost.listContainers();
|
2025-11-24 12:20:30 +00:00
|
|
|
|
|
|
|
|
// Use the first running container we find
|
|
|
|
|
testContainer = containers.find((c) => c.State === 'running');
|
|
|
|
|
|
|
|
|
|
if (!testContainer) {
|
|
|
|
|
throw new Error('No running containers found for streaming tests');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
expect(testContainer).toBeInstanceOf(docker.DockerContainer);
|
|
|
|
|
console.log('Using existing container for tests:', testContainer.Names[0], testContainer.Id);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
tap.test('should stream container logs', async (tools) => {
|
|
|
|
|
const done = tools.defer();
|
|
|
|
|
const logStream = await testContainer.streamLogs({
|
|
|
|
|
stdout: true,
|
|
|
|
|
stderr: true,
|
|
|
|
|
timestamps: true,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let receivedData = false;
|
|
|
|
|
|
|
|
|
|
logStream.on('data', (chunk) => {
|
|
|
|
|
console.log('Received log chunk:', chunk.toString().slice(0, 100));
|
|
|
|
|
receivedData = true;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
logStream.on('error', (error) => {
|
|
|
|
|
console.error('Stream error:', error);
|
|
|
|
|
done.resolve();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Wait for 2 seconds to collect logs, then close
|
|
|
|
|
await tools.delayFor(2000);
|
|
|
|
|
logStream.destroy();
|
|
|
|
|
done.resolve();
|
|
|
|
|
|
|
|
|
|
await done.promise;
|
|
|
|
|
console.log('Log streaming test completed. Received data:', receivedData);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
tap.test('should get container logs (one-shot)', async () => {
|
|
|
|
|
const logs = await testContainer.logs({
|
|
|
|
|
stdout: true,
|
|
|
|
|
stderr: true,
|
|
|
|
|
tail: 10,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(typeof logs).toEqual('string');
|
|
|
|
|
console.log('Container logs (last 10 lines):', logs.slice(0, 200));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
tap.test('should execute command in container', async (tools) => {
|
|
|
|
|
const done = tools.defer();
|
|
|
|
|
const { stream, close } = await testContainer.exec('echo "Hello from exec"', {
|
|
|
|
|
tty: false,
|
|
|
|
|
attachStdout: true,
|
|
|
|
|
attachStderr: true,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let output = '';
|
|
|
|
|
|
|
|
|
|
stream.on('data', (chunk) => {
|
|
|
|
|
output += chunk.toString();
|
|
|
|
|
console.log('Exec output:', chunk.toString());
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
stream.on('end', async () => {
|
|
|
|
|
await close();
|
|
|
|
|
console.log('Exec completed. Full output:', output);
|
|
|
|
|
done.resolve();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
stream.on('error', async (error) => {
|
|
|
|
|
console.error('Exec error:', error);
|
|
|
|
|
await close();
|
|
|
|
|
done.resolve();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await done.promise;
|
|
|
|
|
expect(output.length).toBeGreaterThan(0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
tap.test('should attach to container', async (tools) => {
|
|
|
|
|
const done = tools.defer();
|
|
|
|
|
const { stream, close } = await testContainer.attach({
|
|
|
|
|
stream: true,
|
|
|
|
|
stdout: true,
|
|
|
|
|
stderr: true,
|
|
|
|
|
stdin: false,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let receivedData = false;
|
|
|
|
|
|
|
|
|
|
stream.on('data', (chunk) => {
|
|
|
|
|
console.log('Attach received:', chunk.toString().slice(0, 100));
|
|
|
|
|
receivedData = true;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
stream.on('error', async (error) => {
|
|
|
|
|
console.error('Attach error:', error);
|
|
|
|
|
await close();
|
|
|
|
|
done.resolve();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Monitor for 2 seconds then detach
|
|
|
|
|
await tools.delayFor(2000);
|
|
|
|
|
await close();
|
|
|
|
|
done.resolve();
|
|
|
|
|
|
|
|
|
|
await done.promise;
|
|
|
|
|
console.log('Attach test completed. Received data:', receivedData);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
tap.test('should get container stats', async () => {
|
|
|
|
|
const stats = await testContainer.stats({ stream: false });
|
|
|
|
|
expect(stats).toBeInstanceOf(Object);
|
|
|
|
|
console.log('Container stats keys:', Object.keys(stats));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
tap.test('should inspect container', async () => {
|
|
|
|
|
const inspection = await testContainer.inspect();
|
|
|
|
|
expect(inspection).toBeInstanceOf(Object);
|
|
|
|
|
expect(inspection.Id).toEqual(testContainer.Id);
|
|
|
|
|
console.log('Container state:', inspection.State?.Status);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
tap.test('should complete container tests', async () => {
|
|
|
|
|
// Using existing container, no cleanup needed
|
|
|
|
|
console.log('Container streaming tests completed');
|
|
|
|
|
});
|
|
|
|
|
|
2025-08-19 01:19:14 +00:00
|
|
|
tap.test('cleanup', async () => {
|
|
|
|
|
await testDockerHost.stop();
|
2025-08-19 01:46:37 +00:00
|
|
|
});
|
2025-08-19 01:19:14 +00:00
|
|
|
|
2024-06-05 14:10:44 +02:00
|
|
|
export default tap.start();
|