From 87f26b7b6379a45db97b1ab8a6b226ec7c928478 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Mon, 18 Aug 2025 23:41:16 +0000 Subject: [PATCH] feat(tests): Add comprehensive tests for Docker image export and streaming functionality --- .../document_symbols_cache_v23-06-25.pkl | Bin 172309 -> 232272 bytes test-stream.js | 40 +++++++++++++++ test-stream.mjs | 46 ++++++++++++++++++ test/test.nonci.node.ts | 8 +-- ts/classes.host.ts | 16 ++++-- ts/classes.image.ts | 20 ++++++-- ts/classes.service.ts | 5 ++ 7 files changed, 124 insertions(+), 11 deletions(-) create mode 100644 test-stream.js create mode 100644 test-stream.mjs diff --git a/.serena/cache/typescript/document_symbols_cache_v23-06-25.pkl b/.serena/cache/typescript/document_symbols_cache_v23-06-25.pkl index c2f387e4273ae6202d19b07741fd69e1589cceaa..c4186c2a96ad386fab73231fca95e40151428015 100644 GIT binary patch literal 232272 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 Ck0oUQ delta 26 icmcaGjc@89u7(!IElf;|%-jnYwzD!a@hxB~)dK)_NC#8^ diff --git a/test-stream.js b/test-stream.js new file mode 100644 index 0000000..db1e8ed --- /dev/null +++ b/test-stream.js @@ -0,0 +1,40 @@ +const { SmartRequest } = require('@push.rocks/smartrequest'); + +async function test() { + try { + const response = await SmartRequest.create() + .url('http://unix:/run/user/1000/docker.sock:/images/hello-world:latest/get') + .header('Host', 'docker.sock') + .get(); + + console.log('Response status:', response.status); + console.log('Response type:', typeof response); + + const stream = response.streamNode(); + console.log('Stream type:', typeof stream); + console.log('Has on method:', typeof stream.on); + + if (stream) { + let chunks = 0; + stream.on('data', (chunk) => { + chunks++; + if (chunks <= 3) console.log('Got chunk', chunks, chunk.length); + }); + stream.on('end', () => { + console.log('Stream ended, total chunks:', chunks); + process.exit(0); + }); + stream.on('error', (err) => { + console.error('Stream error:', err); + process.exit(1); + }); + } else { + console.log('No stream available'); + } + } catch (error) { + console.error('Error:', error); + process.exit(1); + } +} + +test(); diff --git a/test-stream.mjs b/test-stream.mjs new file mode 100644 index 0000000..c5ece52 --- /dev/null +++ b/test-stream.mjs @@ -0,0 +1,46 @@ +import { SmartRequest } from '@push.rocks/smartrequest'; + +async function test() { + try { + const response = await SmartRequest.create() + .url('http://unix:/run/user/1000/docker.sock:/images/hello-world:latest/get') + .header('Host', 'docker.sock') + .get(); + + console.log('Response status:', response.status); + console.log('Response type:', typeof response); + + const stream = response.streamNode(); + console.log('Stream type:', typeof stream); + console.log('Has on method:', typeof stream.on); + + if (stream) { + let chunks = 0; + stream.on('data', (chunk) => { + chunks++; + if (chunks <= 3) console.log('Got chunk', chunks, chunk.length); + }); + stream.on('end', () => { + console.log('Stream ended, total chunks:', chunks); + process.exit(0); + }); + stream.on('error', (err) => { + console.error('Stream error:', err); + process.exit(1); + }); + + // Set a timeout in case stream doesn't end + setTimeout(() => { + console.log('Timeout after 5 seconds'); + process.exit(1); + }, 5000); + } else { + console.log('No stream available'); + } + } catch (error) { + console.error('Error:', error); + process.exit(1); + } +} + +test(); diff --git a/test/test.nonci.node.ts b/test/test.nonci.node.ts index a5e5e1a..49238d7 100644 --- a/test/test.nonci.node.ts +++ b/test/test.nonci.node.ts @@ -139,17 +139,17 @@ tap.test('should export images', async (toolsArg) => { await done.promise; }); -tap.test('should import images', async (toolsArg) => { - const done = toolsArg.defer(); +tap.test('should import images', async () => { const fsReadStream = plugins.smartfile.fsStream.createReadStream( plugins.path.join(paths.nogitDir, 'testimage.tar') ); - await docker.DockerImage.createFromTarStream(testDockerHost, { + const importedImage = await docker.DockerImage.createFromTarStream(testDockerHost, { tarStream: fsReadStream, creationObject: { imageUrl: 'code.foss.global/host.today/ht-docker-node:latest', } - }) + }); + expect(importedImage).toBeInstanceOf(docker.DockerImage); }); tap.test('should expose a working DockerImageStore', async () => { diff --git a/ts/classes.host.ts b/ts/classes.host.ts index b137c86..9f329a7 100644 --- a/ts/classes.host.ts +++ b/ts/classes.host.ts @@ -262,12 +262,19 @@ export class DockerHost { // Parse the response body based on content type let body; const contentType = response.headers['content-type'] || ''; - if (contentType.includes('application/json')) { + + // Docker's streaming endpoints (like /images/create) return newline-delimited JSON + // which can't be parsed as a single JSON object + const isStreamingEndpoint = routeArg.includes('/images/create') || + routeArg.includes('/images/load') || + routeArg.includes('/build'); + + if (contentType.includes('application/json') && !isStreamingEndpoint) { body = await response.json(); } else { body = await response.text(); - // Try to parse as JSON if it looks like JSON - if (body && (body.startsWith('{') || body.startsWith('['))) { + // Try to parse as JSON if it looks like JSON and is not a streaming response + if (!isStreamingEndpoint && body && (body.startsWith('{') || body.startsWith('['))) { try { body = JSON.parse(body); } catch { @@ -299,7 +306,8 @@ export class DockerHost { .header('Content-Type', 'application/json') .header('X-Registry-Auth', this.registryToken) .header('Host', 'docker.sock') - .options({ keepAlive: false }); + .timeout(600000) // Set 10 minute timeout for streaming operations + .options({ keepAlive: false, autoDrain: false }); // Disable auto-drain for streaming // If we have a readStream, use the new stream method with logging if (readStream) { diff --git a/ts/classes.image.ts b/ts/classes.image.ts index 5859319..3dce81e 100644 --- a/ts/classes.image.ts +++ b/ts/classes.image.ts @@ -250,6 +250,12 @@ export class DockerImage { public async exportToTarStream(): Promise { logger.log('info', `Exporting image ${this.RepoTags[0]} to tar stream.`); const response = await this.dockerHost.requestStreaming('GET', `/images/${encodeURIComponent(this.RepoTags[0])}/get`); + + // Check if response is a Node.js stream + if (!response || typeof response.on !== 'function') { + throw new Error('Failed to get streaming response for image export'); + } + let counter = 0; const webduplexStream = new plugins.smartstream.SmartDuplex({ writeFunction: async (chunk, tools) => { @@ -259,17 +265,25 @@ export class DockerImage { return chunk; } }); + response.on('data', (chunk) => { if (!webduplexStream.write(chunk)) { response.pause(); webduplexStream.once('drain', () => { response.resume(); - }) - }; + }); + } }); + response.on('end', () => { webduplexStream.end(); - }) + }); + + response.on('error', (error) => { + logger.log('error', `Error during image export: ${error.message}`); + webduplexStream.destroy(error); + }); + return webduplexStream; } } diff --git a/ts/classes.service.ts b/ts/classes.service.ts index 2f5e3d7..3078225 100644 --- a/ts/classes.service.ts +++ b/ts/classes.service.ts @@ -89,6 +89,11 @@ export class DockerService { }> = []; for (const network of serviceCreationDescriptor.networks) { + // Skip null networks (can happen if network creation fails) + if (!network) { + logger.log('warn', 'Skipping null network in service creation'); + continue; + } networkArray.push({ Target: network.Name, Aliases: [serviceCreationDescriptor.networkAlias],