Skip to content

Commit 20e1143

Browse files
authored
fix: STRF-13624 Fix intermintment failing for webdav content (#1324)
1 parent e6d4f79 commit 20e1143

File tree

4 files changed

+95
-7
lines changed

4 files changed

+95
-7
lines changed

lib/utils/asyncUtils.js

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
/**
2-
* @module Contains helpers functions for working with async stuff and streams
2+
* @module Contains helpers functions for working with streams
33
*/
4+
5+
import { PassThrough } from 'stream';
6+
47
/**
58
* WARNING! Can be used with text content only. Binary data (e.g. images) will get broken!
69
*
@@ -14,7 +17,38 @@ async function readFromStream(stream) {
1417
}
1518
return result;
1619
}
17-
export { readFromStream };
20+
21+
function readStream(stream) {
22+
return new Promise((resolve, reject) => {
23+
let data = '';
24+
stream.on('data', (chunk) => {
25+
data += chunk;
26+
});
27+
stream.on('end', () => {
28+
resolve(data);
29+
});
30+
stream.on('error', reject);
31+
});
32+
}
33+
34+
function tapStream(originalStream, onData) {
35+
const tee = new PassThrough();
36+
originalStream.pipe(tee);
37+
38+
let data = '';
39+
tee.on('data', (chunk) => {
40+
data += chunk;
41+
});
42+
tee.on('end', () => {
43+
onData(data);
44+
});
45+
46+
return tee; // Use this stream for downstream consumers
47+
}
48+
49+
export { readFromStream, tapStream, readStream };
1850
export default {
1951
readFromStream,
52+
tapStream,
53+
readStream,
2054
};

lib/utils/asyncUtils.spec.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Readable } from 'stream';
2+
import { readStream, tapStream } from './asyncUtils';
3+
4+
describe('readStream', () => {
5+
it('should read all data from a stream and resolve with the result', async () => {
6+
const readable = Readable.from(['hello', ' ', 'world']);
7+
const result = await readStream(readable);
8+
expect(result).toBe('hello world');
9+
});
10+
});
11+
12+
describe('tapStream', () => {
13+
it('should tap into a stream and call onData with the full data', () => {
14+
const readable = Readable.from(['foo', 'bar']);
15+
return new Promise((resolve, reject) => {
16+
const tapped = tapStream(readable, (data) => {
17+
try {
18+
expect(data).toBe('foobar');
19+
resolve();
20+
} catch (err) {
21+
reject(err);
22+
}
23+
});
24+
// Consume the tapped stream to trigger data flow
25+
tapped.resume();
26+
});
27+
});
28+
});

server/plugins/renderer/renderer.module.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import langAssembler from '../../../lib/lang-assembler.js';
99
import { RawResponse, RedirectResponse, PencilResponse } from './responses/index.js';
1010
import templateAssembler from '../../../lib/template-assembler.js';
1111
import { int2uuid, stripDomainFromCookies, normalizeRedirectUrl } from '../../lib/utils.js';
12-
import { readFromStream } from '../../../lib/utils/asyncUtils.js';
12+
import { readFromStream, readStream, tapStream } from '../../../lib/utils/asyncUtils.js';
1313
import NetworkUtils from '../../../lib/utils/NetworkUtils.js';
1414
import contentApiClient from '../../../lib/content-api-client.js';
1515
import { getPageType } from '../../lib/page-type-util.js';
@@ -114,9 +114,17 @@ internals.getResponse = async (request) => {
114114
// (5xx will be just thrown by axios)
115115
const contentType = response.headers['content-type'] || '';
116116
const isResponseJson = contentType.toLowerCase().includes('application/json');
117-
const bcAppData = isResponseJson
118-
? JSON.parse(await readFromStream(response.data))
119-
: response.data;
117+
const isWebDavRequest = request.path.startsWith('/content');
118+
let bcAppData = response.data;
119+
120+
if (isResponseJson) {
121+
bcAppData = JSON.parse(await readFromStream(response.data));
122+
} else if (isWebDavRequest) {
123+
const tappedStream = tapStream(response.data, (body) => {
124+
return body;
125+
});
126+
bcAppData = await readStream(tappedStream);
127+
}
120128
// cache response
121129
cache.put(
122130
requestSignature,

server/plugins/renderer/renderer.module.spec.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import MockAdapter from 'axios-mock-adapter';
33
import path from 'path';
44
import fs from 'fs';
55
import { jest } from '@jest/globals';
6+
import { PassThrough } from 'stream';
67
import Server from '../../index.js';
78
import ThemeConfig from '../../../lib/theme-config.js';
89
import { readFromStream } from '../../../lib/utils/asyncUtils.js';
@@ -242,7 +243,7 @@ describe('Renderer Plugin', () => {
242243
describe('when the storefront server response is Success and content-type is "image"', () => {
243244
const browserRequest = {
244245
method: 'get',
245-
url: '/content/cat_and_dog.jpeg',
246+
url: '/images/cat_and_dog.jpeg',
246247
};
247248
const testImage = fs.readFileSync('./test/assets/cat_and_dog.jpeg');
248249
const storefrontResponseHeaders = {
@@ -275,4 +276,21 @@ describe('Renderer Plugin', () => {
275276
expect(localServerResponse.rawPayload).toEqual(testImage);
276277
});
277278
});
279+
280+
describe('WebDav /content requests', () => {
281+
it('should return plain text response for /content path', async () => {
282+
const browserRequest = {
283+
method: 'GET',
284+
url: '/content/test.txt',
285+
};
286+
const testString = 'webdav test content';
287+
// Simulate a stream response for /content path
288+
const stream = new PassThrough();
289+
stream.end(testString);
290+
axiosMock.onGet().reply(200, stream, { 'content-type': 'text/plain' });
291+
const localServerResponse = await server.inject(browserRequest);
292+
expect(localServerResponse.statusCode).toEqual(200);
293+
expect(localServerResponse.payload).toEqual(testString);
294+
});
295+
});
278296
});

0 commit comments

Comments
 (0)