Skip to content

Commit c9b431f

Browse files
authored
Improve error handling in publish plugin (#2526)
1 parent f3e99ba commit c9b431f

File tree

8 files changed

+111
-171
lines changed

8 files changed

+111
-171
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
88
Please see [CONTRIBUTING.md](./CONTRIBUTING.md) on how to contribute to Cucumber.
99

1010
## [Unreleased]
11+
### Changed
12+
- Improve error handling in publish plugin ([#2526](https://github.com/cucumber/cucumber-js/pull/2526))
1113

1214
## [11.2.0] - 2025-01-09
1315
### Added

features/publish.feature

+28-2
Original file line numberDiff line numberDiff line change
@@ -86,14 +86,40 @@ Feature: Publish reports
8686
"""
8787

8888
@spawn
89-
Scenario: when results are not published due to an error raised by the server, the banner is displayed
89+
Scenario: results are not published due to a client error
9090
When I run cucumber-js with env `CUCUMBER_PUBLISH_TOKEN=keyboardcat`
9191
Then it passes
9292
And the error output contains the text:
9393
"""
9494
┌─────────────────────┐
9595
│ Error invalid token │
9696
└─────────────────────┘
97+
"""
9798

98-
Unexpected http status 401 from GET http://localhost:9987
99+
@spawn
100+
Scenario: results are not published due to a service error
101+
Given report publishing is not working
102+
When I run cucumber-js with arguments `--publish` and env ``
103+
Then it passes
104+
And the error output does not contain the text:
105+
"""
106+
Not a useful error message
107+
"""
108+
And the error output contains the text:
109+
"""
110+
Failed to publish report to http://localhost:9987 with status 500
111+
"""
112+
113+
@spawn
114+
Scenario: results are not published due to an error on uploading
115+
Given report uploads are not working
116+
When I run cucumber-js with arguments `--publish` and env ``
117+
Then it passes
118+
And the error output does not contain the text:
119+
"""
120+
View your Cucumber Report at:
121+
"""
122+
And the error output contains the text:
123+
"""
124+
Failed to upload report to http://localhost:9987 with status 500
99125
"""

features/step_definitions/report_server_steps.ts

+8
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,14 @@ Given(
1414
}
1515
)
1616

17+
Given('report publishing is not working', async function (this: World) {
18+
this.reportServer.failOnTouch = true
19+
})
20+
21+
Given('report uploads are not working', async function (this: World) {
22+
this.reportServer.failOnUpload = true
23+
})
24+
1725
Then(
1826
'the server should receive the following message types:',
1927
async function (this: World, expectedMessageTypesTable: DataTable) {

package-lock.json

+2-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,6 @@
251251
"semver": "7.7.1",
252252
"string-argv": "0.3.1",
253253
"supports-color": "^8.1.1",
254-
"tmp": "0.2.3",
255254
"type-fest": "^4.8.3",
256255
"util-arity": "^1.1.0",
257256
"yaml": "^2.2.2",
@@ -307,6 +306,7 @@
307306
"sinon": "15.0.1",
308307
"sinon-chai": "3.7.0",
309308
"stream-to-string": "1.2.1",
309+
"tmp": "0.2.3",
310310
"ts-node": "10.9.2",
311311
"tsd": "0.31.2",
312312
"typedoc": "^0.26.11",

src/publish/http_stream.ts

-150
This file was deleted.

src/publish/publish_plugin.ts

+58-17
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import { Writable } from 'node:stream'
22
import { stripVTControlCharacters } from 'node:util'
3+
import { mkdtemp, stat } from 'node:fs/promises'
4+
import path from 'node:path'
5+
import { tmpdir } from 'node:os'
6+
import { createReadStream, createWriteStream } from 'node:fs'
37
import { supportsColor } from 'supports-color'
48
import hasAnsi from 'has-ansi'
59
import { InternalPlugin } from '../plugin'
610
import { IPublishConfig } from './types'
7-
import HttpStream from './http_stream'
811

912
const DEFAULT_CUCUMBER_PUBLISH_URL = 'https://messages.cucumber.io/api/reports'
1013

@@ -19,24 +22,62 @@ export const publishPlugin: InternalPlugin<IPublishConfig | false> = {
1922
if (token !== undefined) {
2023
headers.Authorization = `Bearer ${token}`
2124
}
22-
const stream = new HttpStream(url, 'GET', headers)
23-
const readerStream = new Writable({
24-
objectMode: true,
25-
write: function (responseBody: string, encoding, writeCallback) {
26-
environment.stderr.write(
27-
sanitisePublishOutput(responseBody, environment.stderr) + '\n'
28-
)
29-
writeCallback()
30-
},
25+
const touchResponse = await fetch(url, { headers })
26+
const banner = await touchResponse.text()
27+
28+
if (!touchResponse.ok) {
29+
return () => {
30+
if (touchResponse.status < 500) {
31+
environment.stderr.write(
32+
sanitisePublishOutput(banner, environment.stderr) + '\n'
33+
)
34+
} else {
35+
logger.error(
36+
`Failed to publish report to ${new URL(url).origin} with status ${
37+
touchResponse.status
38+
}`
39+
)
40+
logger.debug(touchResponse)
41+
}
42+
}
43+
}
44+
45+
const uploadUrl = touchResponse.headers.get('Location')
46+
const tempDir = await mkdtemp(path.join(tmpdir(), `cucumber-js-publish-`))
47+
const tempFilePath = path.join(tempDir, 'envelopes.ndjson')
48+
const tempFileStream = createWriteStream(tempFilePath, {
49+
encoding: 'utf-8',
3150
})
32-
stream.pipe(readerStream)
33-
stream.on('error', (error: Error) => logger.error(error.message))
34-
on('message', (value) => stream.write(JSON.stringify(value) + '\n'))
35-
return () =>
36-
new Promise<void>((resolve) => {
37-
stream.on('finish', () => resolve())
38-
stream.end()
51+
on('message', (value) => tempFileStream.write(JSON.stringify(value) + '\n'))
52+
53+
return () => {
54+
return new Promise<void>((resolve) => {
55+
tempFileStream.end(async () => {
56+
const stats = await stat(tempFilePath)
57+
const uploadResponse = await fetch(uploadUrl, {
58+
method: 'PUT',
59+
headers: {
60+
'Content-Length': stats.size.toString(),
61+
},
62+
body: createReadStream(tempFilePath, { encoding: 'utf-8' }),
63+
duplex: 'half',
64+
})
65+
if (uploadResponse.ok) {
66+
environment.stderr.write(
67+
sanitisePublishOutput(banner, environment.stderr) + '\n'
68+
)
69+
} else {
70+
logger.error(
71+
`Failed to upload report to ${
72+
new URL(uploadUrl).origin
73+
} with status ${uploadResponse.status}`
74+
)
75+
logger.debug(uploadResponse)
76+
}
77+
resolve()
78+
})
3979
})
80+
}
4081
},
4182
}
4283

test/fake_report_server.ts

+12
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,18 @@ export default class FakeReportServer {
1414
private readonly server: Server
1515
public receivedBodies = Buffer.alloc(0)
1616
public receivedHeaders: http.IncomingHttpHeaders = {}
17+
public failOnTouch = false
18+
public failOnUpload = false
1719

1820
constructor(private readonly port: number) {
1921
const app = express()
2022

2123
app.put('/s3', (req, res) => {
24+
if (this.failOnUpload) {
25+
res.status(500).end()
26+
return
27+
}
28+
2229
this.receivedHeaders = { ...this.receivedHeaders, ...req.headers }
2330

2431
const captureBodyStream = new Writable({
@@ -39,6 +46,11 @@ export default class FakeReportServer {
3946
})
4047

4148
app.get('/api/reports', (req, res) => {
49+
if (this.failOnTouch) {
50+
res.status(500).end('Not a useful error message')
51+
return
52+
}
53+
4254
this.receivedHeaders = { ...this.receivedHeaders, ...req.headers }
4355
const token = extractAuthorizationToken(req.headers.authorization)
4456
if (token && !isValidUUID(token)) {

0 commit comments

Comments
 (0)