Skip to content

Commit d0a3b6b

Browse files
authored
fix: gateway conformance tests (#81)
* fix: fixture loading * fix: bug with parsing certain content * chore: apply review suggestions * chore: re-enable 'default' tests but skip ininite looping ones * fix: default test failure expectation * chore: expected successes for default tests
1 parent ef5b6a5 commit d0a3b6b

9 files changed

+156
-130
lines changed

packages/gateway-conformance/.aegir.js

+6-10
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,13 @@ export default {
1010
throw new Error('Only node runner is supported')
1111
}
1212

13-
const { GWC_IMAGE } = await import('./dist/src/constants.js')
14-
const { loadKuboFixtures, kuboRepoDir } = await import('./dist/src/fixtures/kubo-mgmt.js')
15-
const IPFS_NS_MAP = await loadKuboFixtures()
16-
1713
const { createKuboNode } = await import('./dist/src/fixtures/create-kubo.js')
18-
const controller = await createKuboNode(await getPort(3440))
14+
const KUBO_PORT = await getPort(3440)
15+
const { node: controller, gatewayUrl, repoPath } = await createKuboNode(KUBO_PORT)
1916
await controller.start()
20-
const kuboGateway = `http://${controller.api.gatewayHost}:${controller.api.gatewayPort}`
17+
const { loadKuboFixtures } = await import('./dist/src/fixtures/kubo-mgmt.js')
18+
const IPFS_NS_MAP = await loadKuboFixtures(repoPath)
19+
const kuboGateway = gatewayUrl
2120

2221
const { startBasicServer } = await import('./dist/src/fixtures/basic-server.js')
2322
const SERVER_PORT = await getPort(3441)
@@ -28,7 +27,6 @@ export default {
2827

2928
const { startReverseProxy } = await import('./dist/src/fixtures/reverse-proxy.js')
3029
const PROXY_PORT = await getPort(3442)
31-
const KUBO_PORT = controller.api.gatewayPort
3230
const stopReverseProxy = await startReverseProxy({
3331
backendPort: SERVER_PORT,
3432
targetHost: 'localhost',
@@ -43,13 +41,11 @@ export default {
4341
stopBasicServer,
4442
env: {
4543
IPFS_NS_MAP,
46-
GWC_IMAGE,
4744
CONFORMANCE_HOST,
4845
KUBO_PORT: `${KUBO_PORT}`,
4946
PROXY_PORT: `${PROXY_PORT}`,
5047
SERVER_PORT: `${SERVER_PORT}`,
51-
KUBO_GATEWAY: kuboGateway,
52-
KUBO_REPO: process.env.KUBO_REPO || kuboRepoDir
48+
KUBO_GATEWAY: kuboGateway
5349
}
5450
}
5551
},

packages/gateway-conformance/package.json

+4-3
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,16 @@
5252
"test": "aegir test -t node"
5353
},
5454
"dependencies": {
55+
"@helia/interface": "^4.3.0",
5556
"@helia/verified-fetch": "1.4.1",
5657
"@libp2p/logger": "^4.0.11",
5758
"@sgtpooki/file-type": "^1.0.1",
5859
"aegir": "^42.2.5",
5960
"execa": "^8.0.1",
60-
"glob": "^10.3.12",
61-
"ipfsd-ctl": "^13.0.0",
61+
"fast-glob": "^3.3.2",
62+
"ipfsd-ctl": "^14.1.0",
6263
"kubo": "^0.27.0",
63-
"kubo-rpc-client": "^3.0.4",
64+
"kubo-rpc-client": "^4.1.1",
6465
"undici": "^6.15.0"
6566
},
6667
"browser": {

packages/gateway-conformance/src/conformance.spec.ts

+57-36
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ interface TestConfig {
2020
}
2121

2222
function getGatewayConformanceBinaryPath (): string {
23+
if (process.env.GATEWAY_CONFORMANCE_BINARY != null) {
24+
return process.env.GATEWAY_CONFORMANCE_BINARY
25+
}
2326
const goPath = process.env.GOPATH ?? join(homedir(), 'go', 'bin')
2427
return join(goPath, 'gateway-conformance')
2528
}
@@ -56,13 +59,13 @@ const tests: TestConfig[] = [
5659
{
5760
name: 'TestPlainCodec',
5861
run: ['TestPlainCodec'],
59-
maxFailures: 44,
62+
maxFailures: 83,
6063
minimumSuccesses: 15
6164
},
6265
{
6366
name: 'TestPathing',
6467
run: ['TestPathing'],
65-
maxFailures: 5,
68+
maxFailures: 13,
6669
minimumSuccesses: 0
6770
},
6871
{
@@ -83,12 +86,13 @@ const tests: TestConfig[] = [
8386
maxFailures: 9,
8487
minimumSuccesses: 0
8588
},
86-
{
87-
name: 'TestNativeDag',
88-
run: ['TestNativeDag'],
89-
maxFailures: 2,
90-
minimumSuccesses: 0
91-
},
89+
// currently results in an infinite loop without verified-fetch stopping the request whether sessions are enabled or not.
90+
// {
91+
// name: 'TestNativeDag',
92+
// run: ['TestNativeDag'],
93+
// maxFailures: 2,
94+
// minimumSuccesses: 0
95+
// },
9296
{
9397
name: 'TestGatewayJSONCborAndIPNS',
9498
run: ['TestGatewayJSONCborAndIPNS'],
@@ -137,12 +141,13 @@ const tests: TestConfig[] = [
137141
maxFailures: 26,
138142
minimumSuccesses: 3
139143
},
140-
{
141-
name: 'TestTrustlessCarEntityBytes',
142-
run: ['TestTrustlessCarEntityBytes'],
143-
maxFailures: 122,
144-
minimumSuccesses: 55
145-
},
144+
// times out
145+
// {
146+
// name: 'TestTrustlessCarEntityBytes',
147+
// run: ['TestTrustlessCarEntityBytes'],
148+
// maxFailures: 122,
149+
// minimumSuccesses: 55
150+
// },
146151
{
147152
name: 'TestTrustlessCarDagScopeAll',
148153
run: ['TestTrustlessCarDagScopeAll'],
@@ -185,12 +190,13 @@ const tests: TestConfig[] = [
185190
maxFailures: 279,
186191
minimumSuccesses: 0
187192
},
188-
{
189-
name: 'TestUnixFSDirectoryListingOnSubdomainGateway',
190-
run: ['TestUnixFSDirectoryListingOnSubdomainGateway'],
191-
maxFailures: 39,
192-
minimumSuccesses: 0
193-
},
193+
// times out
194+
// {
195+
// name: 'TestUnixFSDirectoryListingOnSubdomainGateway',
196+
// run: ['TestUnixFSDirectoryListingOnSubdomainGateway'],
197+
// maxFailures: 39,
198+
// minimumSuccesses: 0
199+
// },
194200
{
195201
name: 'TestRedirectsFileWithIfNoneMatchHeader',
196202
run: ['TestRedirectsFileWithIfNoneMatchHeader'],
@@ -233,18 +239,20 @@ const tests: TestConfig[] = [
233239
maxFailures: 27,
234240
minimumSuccesses: 15
235241
},
236-
{
237-
name: 'TestGatewayCache',
238-
run: ['TestGatewayCache'],
239-
maxFailures: 71,
240-
minimumSuccesses: 23
241-
},
242-
{
243-
name: 'TestUnixFSDirectoryListing',
244-
run: ['TestUnixFSDirectoryListing'],
245-
maxFailures: 50,
246-
minimumSuccesses: 0
247-
},
242+
// times out
243+
// {
244+
// name: 'TestGatewayCache',
245+
// run: ['TestGatewayCache'],
246+
// maxFailures: 71,
247+
// minimumSuccesses: 23
248+
// },
249+
// times out
250+
// {
251+
// name: 'TestUnixFSDirectoryListing',
252+
// run: ['TestUnixFSDirectoryListing'],
253+
// maxFailures: 50,
254+
// minimumSuccesses: 0
255+
// },
248256
{
249257
name: 'TestTar',
250258
run: ['TestTar'],
@@ -298,6 +306,10 @@ describe('@helia/verified-fetch - gateway conformance', function () {
298306
const binaryPath = getGatewayConformanceBinaryPath()
299307
before(async () => {
300308
const log = logger.forComponent('before')
309+
if (process.env.GATEWAY_CONFORMANCE_BINARY != null) {
310+
log('Using custom gateway-conformance binary at %s', binaryPath)
311+
return
312+
}
301313
const { stdout, stderr } = await execa('go', ['install', 'github.com/ipfs/gateway-conformance/cmd/gateway-conformance@latest'], { reject: true })
302314
log(stdout)
303315
log.error(stderr)
@@ -357,7 +369,16 @@ describe('@helia/verified-fetch - gateway conformance', function () {
357369
it('has expected total failures and successes', async function () {
358370
const log = logger.forComponent('all')
359371

360-
const { stderr, stdout } = await execa(binaryPath, getConformanceTestArgs('all'), { reject: false })
372+
// TODO: unskip when verified-fetch is no longer infinitely looping on requests.
373+
const toSkip = [
374+
'TestNativeDag',
375+
'TestTrustlessCarEntityBytes',
376+
'TestUnixFSDirectoryListingOnSubdomainGateway',
377+
'TestGatewayCache',
378+
'TestUnixFSDirectoryListing'
379+
]
380+
381+
const { stderr, stdout } = await execa(binaryPath, getConformanceTestArgs('all', [], ['-skip', toSkip.join('|')]), { reject: false })
361382

362383
log(stdout)
363384
log.error(stderr)
@@ -374,9 +395,9 @@ describe('@helia/verified-fetch - gateway conformance', function () {
374395
successCount++
375396
}
376397
}
377-
378-
expect(failureCount).to.be.lessThanOrEqual(135)
379-
expect(successCount).to.be.greaterThanOrEqual(30)
398+
// CI has 1134 failures, but I get 1129 locally.
399+
expect(failureCount).to.be.lessThanOrEqual(1134)
400+
expect(successCount).to.be.greaterThanOrEqual(262)
380401
})
381402
})
382403
})
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,39 @@
11
/**
22
* Basically copies what .aegir.js does, but without all the env vars and setup.. just so you can run `node src/demo-server.ts` and test queries manually.
33
*/
4+
import { logger } from '@libp2p/logger'
45
import getPort from 'aegir/get-port'
6+
import { startBasicServer } from './fixtures/basic-server.js'
7+
import { createKuboNode } from './fixtures/create-kubo.js'
8+
import { loadKuboFixtures } from './fixtures/kubo-mgmt.js'
9+
import { startReverseProxy } from './fixtures/reverse-proxy.js'
510

6-
const { loadKuboFixtures } = await import('./fixtures/kubo-mgmt.js')
7-
await loadKuboFixtures()
11+
const log = logger('demo-server')
812

9-
const { createKuboNode } = await import('./fixtures/create-kubo.js')
10-
const controller = await createKuboNode(await getPort(3440))
13+
const { node: controller, gatewayUrl, repoPath } = await createKuboNode(await getPort(3440))
14+
15+
const kuboGateway = gatewayUrl
1116
await controller.start()
12-
const kuboGateway = `http://${controller.api.gatewayHost}:${controller.api.gatewayPort}`
17+
await loadKuboFixtures(repoPath)
1318

14-
const { startBasicServer } = await import('./fixtures/basic-server.js')
1519
const SERVER_PORT = await getPort(3441)
1620
await startBasicServer({
1721
serverPort: SERVER_PORT,
1822
kuboGateway
1923
})
2024

21-
const { startReverseProxy } = await import('./fixtures/reverse-proxy.js')
2225
const PROXY_PORT = await getPort(3442)
2326
await startReverseProxy({
2427
backendPort: SERVER_PORT,
2528
targetHost: 'localhost',
2629
proxyPort: PROXY_PORT
2730
})
2831

32+
process.on('exit', () => {
33+
controller.stop().catch((err) => {
34+
log.error('Failed to stop controller', err)
35+
process.exit(1)
36+
})
37+
})
38+
2939
export {}

packages/gateway-conformance/src/fixtures/basic-server.ts

+14-6
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,18 @@ export interface BasicServerOptions {
1717

1818
export async function startBasicServer ({ kuboGateway, serverPort }: BasicServerOptions): Promise<() => Promise<void>> {
1919
kuboGateway = kuboGateway ?? process.env.KUBO_GATEWAY
20+
const useSessions = process.env.USE_SESSIONS !== 'false'
21+
22+
log('Starting basic server wrapper for verified-fetch %s', useSessions ? 'with sessions' : 'without sessions')
23+
2024
if (kuboGateway == null) {
2125
throw new Error('options.kuboGateway or KUBO_GATEWAY env var is required')
2226
}
23-
2427
const verifiedFetch = await createVerifiedFetch({
2528
gateways: [kuboGateway],
26-
routers: [kuboGateway]
29+
routers: [],
30+
allowInsecure: true,
31+
allowLocal: true
2732
}, {
2833
contentTypeParser
2934
})
@@ -42,7 +47,7 @@ export async function startBasicServer ({ kuboGateway, serverPort }: BasicServer
4247
return
4348
}
4449

45-
log('req.headers: %O', req.headers)
50+
log.trace('req.headers: %O', req.headers)
4651
const hostname = req.headers.host?.split(':')[0]
4752
const host = req.headers['x-forwarded-for'] ?? `${hostname}:${serverPort}`
4853

@@ -56,7 +61,7 @@ export async function startBasicServer ({ kuboGateway, serverPort }: BasicServer
5661
requestController.abort()
5762
})
5863

59-
void verifiedFetch(fullUrlHref, { redirect: 'manual', signal: requestController.signal }).then(async (resp) => {
64+
void verifiedFetch(fullUrlHref, { redirect: 'manual', signal: requestController.signal, session: useSessions, allowInsecure: true, allowLocal: true }).then(async (resp) => {
6065
// loop over headers and set them on the response
6166
const headers: Record<string, string> = {}
6267
for (const [key, value] of resp.headers.entries()) {
@@ -65,7 +70,8 @@ export async function startBasicServer ({ kuboGateway, serverPort }: BasicServer
6570

6671
res.writeHead(resp.status, headers)
6772
if (resp.body == null) {
68-
res.write(await resp.arrayBuffer())
73+
// need to convert ArrayBuffer to Buffer or Uint8Array
74+
res.write(Buffer.from(await resp.arrayBuffer()))
6975
} else {
7076
// read the body of the response and write it to the response from the server
7177
const reader = resp.body.getReader()
@@ -74,12 +80,14 @@ export async function startBasicServer ({ kuboGateway, serverPort }: BasicServer
7480
if (done) {
7581
break
7682
}
83+
log('typeof value: %s', typeof value)
84+
7785
res.write(Buffer.from(value))
7886
}
7987
}
8088
res.end()
8189
}).catch((e) => {
82-
log.error('Problem with request: %s', e.message)
90+
log.error('Problem with request: %s', e.message, e)
8391
if (!res.headersSent) {
8492
res.writeHead(500)
8593
}

packages/gateway-conformance/src/fixtures/content-type-parser.ts

+7
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
1+
import { logger } from '@libp2p/logger'
12
import { fileTypeFromBuffer } from '@sgtpooki/file-type'
23

4+
const log = logger('content-type-parser')
5+
36
// default from verified-fetch is application/octect-stream, which forces a download. This is not what we want for MANY file types.
47
const defaultMimeType = 'text/html; charset=utf-8'
58
function checkForSvg (bytes: Uint8Array): string {
9+
log('checking for svg')
610
return /^(<\?xml[^>]+>)?[^<^\w]+<svg/ig.test(
711
new TextDecoder().decode(bytes.slice(0, 64)))
812
? 'image/svg+xml'
913
: defaultMimeType
1014
}
1115

1216
export async function contentTypeParser (bytes: Uint8Array, fileName?: string): Promise<string> {
17+
log('contentTypeParser called for fileName: %s', fileName)
1318
const detectedType = (await fileTypeFromBuffer(bytes))?.mime
1419
if (detectedType != null) {
20+
log('detectedType: %s', detectedType)
1521
return detectedType
1622
}
23+
log('no detectedType')
1724

1825
if (fileName == null) {
1926
// no other way to determine file-type.

0 commit comments

Comments
 (0)