Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"test": "jest",
"scripts": {
"test-unit": "jest ./src/tests/unit_tests",
"test": "ava",
"test": "ts-node ./testing/check-integration-env.ts && ava",
"docker-dev": "nodemon ./app.js",
"build": "tsc",
"start-ts": "ts-node ./app.ts",
Expand Down
1 change: 1 addition & 0 deletions src/tests/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ export interface Headers {
export interface RequestArgs {
headers: Headers
body: RequestBody
timeout?: number
}
12 changes: 10 additions & 2 deletions src/tests/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import { NodeConfig, RequestArgs, RequestBody } from '../types'
import { config } from '../config'
import * as hmac from '../../crypto/hmac'

export const TEST_HTTP_TIMEOUT_MS = Number(
process.env.SPHINX_TEST_HTTP_TIMEOUT_MS || 10000
)

export const makeArgs = (
node: NodeConfig,
body: RequestBody = {},
Expand All @@ -27,7 +31,7 @@ export const makeArgs = (
} else {
headers['x-user-token'] = node.authToken
}
return { body, headers }
return { body, headers, timeout: TEST_HTTP_TIMEOUT_MS }
}

export const makeRelayRequest = async (
Expand Down Expand Up @@ -100,7 +104,9 @@ export async function getToken(t, node) {

const protocol = memeProtocol(config.memeHost)
//get authentication challenge from meme server
const r = await http.get(`${protocol}://${config.memeHost}/ask`)
const r = await http.get(`${protocol}://${config.memeHost}/ask`, {
timeout: TEST_HTTP_TIMEOUT_MS,
})
t.truthy(r, 'r should exist')
t.truthy(r.challenge, 'r.challenge should exist')

Expand All @@ -115,6 +121,7 @@ export async function getToken(t, node) {
//get server token
const r3 = await http.post(`${protocol}://${config.memeHost}/verify`, {
form: { id: r.id, sig: r2.response.sig, pubkey: node.pubkey },
timeout: TEST_HTTP_TIMEOUT_MS,
})
t.truthy(r3, 'r3 should exist')
t.truthy(r3.token, 'r3.token should exist')
Expand All @@ -141,6 +148,7 @@ export function makeJwtArgs(jwt, body) {
return {
headers: { 'x-jwt': jwt },
body,
timeout: TEST_HTTP_TIMEOUT_MS,
}
}

Expand Down
6 changes: 6 additions & 0 deletions testing/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,11 @@ Once you've done the above, you can run the tests with [ava](https://github.com/
npm run test
```

`npm run test` first checks that every relay in `src/tests/configs/nodes.json`
responds to `/contacts`. If the stack is not ready, it fails before starting
AVA and prints the unreachable node aliases. Use `SPHINX_TEST_ENV_TIMEOUT_MS`
to adjust the readiness timeout and `SPHINX_TEST_HTTP_TIMEOUT_MS` to adjust
the per-request timeout used by test helpers.

The tests expect both the relay server and sphinx-stack from the setup steps above
to be running.
122 changes: 122 additions & 0 deletions testing/check-integration-env.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import * as fs from 'fs'
import * as http from 'http'
import * as https from 'https'
import * as path from 'path'

interface TestNode {
alias?: string
external_ip?: string
authToken?: string
}

const nodesPath = path.join(process.cwd(), 'src/tests/configs/nodes.json')
const timeoutMs = Number(process.env.SPHINX_TEST_ENV_TIMEOUT_MS || 5000)

function readNodes(): TestNode[] {
if (!fs.existsSync(nodesPath)) {
throw new Error(
`Missing ${nodesPath}. Start the sphinx-stack test environment and copy relay/NODES.json into src/tests/configs/nodes.json before running integration tests.`
)
}

const nodes = JSON.parse(fs.readFileSync(nodesPath, 'utf8'))
if (!Array.isArray(nodes) || nodes.length === 0) {
throw new Error(`${nodesPath} must contain at least one relay node config.`)
}

return nodes
}

function checkNode(node: TestNode): Promise<void> {
if (!node.external_ip) {
return Promise.reject(new Error('missing external_ip'))
}

const url = new URL('/contacts', node.external_ip)
const client = url.protocol === 'https:' ? https : http

return new Promise((resolve, reject) => {
const req = client.request(
url,
{
method: 'GET',
headers: {
'x-user-token': node.authToken || '',
},
timeout: timeoutMs,
},
(res) => {
res.resume()
res.on('end', () => {
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
resolve()
} else {
reject(new Error(`HTTP ${res.statusCode}`))
}
})
}
)

req.on('timeout', () => {
req.destroy(new Error(`timed out after ${timeoutMs}ms`))
})
req.on('error', reject)
req.end()
})
}

function formatError(error: unknown): string {
if (error && typeof error === 'object') {
const err = error as {
address?: string
code?: string
message?: string
port?: number
}
const details = [err.code, err.message, err.address, err.port]
.filter((detail) => detail !== undefined && detail !== '')
.join(' ')

if (details) return details
}

return error instanceof Error ? error.message : String(error)
}

async function main() {
const nodes = readNodes()
const failures: string[] = []

await Promise.all(
nodes.map(async (node, index) => {
const label = node.alias || `node ${index + 1}`
try {
await checkNode(node)
} catch (error) {
const message = formatError(error)
failures.push(
`${label} (${node.external_ip || 'missing external_ip'}): ${message}`
)
}
})
)

if (failures.length > 0) {
throw new Error(
[
'Sphinx integration test environment is not ready.',
...failures.map((failure) => `- ${failure}`),
'Start sphinx-stack/relay test services or refresh src/tests/configs/nodes.json, then rerun npm test.',
].join('\n')
)
}

console.log(
`Sphinx integration test environment ready: ${nodes.length} relay node(s) responded within ${timeoutMs}ms.`
)
}

main().catch((error) => {
console.error(error instanceof Error ? error.message : error)
process.exit(1)
})