@bsv/auth-express-middleware
Version:
BSV Blockchain mutual-authentication express middleware
449 lines (417 loc) • 16 kB
text/typescript
/**
* tests/AuthFetch.test.ts
*/
import fs from 'fs'
import path from 'path'
import {
CompletedProtoWallet,
MasterCertificate,
PrivateKey,
RequestedCertificateTypeIDAndFieldList,
Utils,
VerifiableCertificate,
AuthFetch
} from '@bsv/sdk'
import { Server } from 'http'
import { startServer } from './testExpressServer'
import { MockWallet } from './MockWallet'
export interface RequestedCertificateSet {
certifiers: string[]
types: RequestedCertificateTypeIDAndFieldList
}
describe('AuthFetch and AuthExpress Integration Tests', () => {
const privKey = PrivateKey.fromRandom()
let server: Server
beforeAll((done) => {
// Start the Express server
server = startServer(3000)
server.on('listening', () => {
console.log('Test server is running on http://localhost:3000')
done()
})
})
afterAll((done) => {
// Close the server after tests
server.close(() => {
console.log('Test server stopped')
done()
})
})
// --------------------------------------------------------------------------
// Main Tests
// --------------------------------------------------------------------------
test('Test 1: Simple POST request with JSON', async () => {
const walletWithRequests = new MockWallet(privKey)
const authFetch = new AuthFetch(walletWithRequests)
const result = await authFetch.fetch(
'http://localhost:3000/other-endpoint',
{
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({ message: 'Hello from JSON!' })
}
)
expect(result.status).toBe(200)
const jsonResponse = await result.json()
console.log(jsonResponse)
expect(jsonResponse).toBeDefined()
}, 1500000)
test('Test 2: POST request with URL-encoded data', async () => {
const walletWithRequests = new CompletedProtoWallet(privKey)
const authFetch = new AuthFetch(walletWithRequests)
const result = await authFetch.fetch(
'http://localhost:3000/other-endpoint',
{
method: 'POST',
headers: {
'content-type': 'application/x-www-form-urlencoded',
'x-bsv-test': 'this is a test header',
},
body: new URLSearchParams({ message: 'hello!', type: 'form-data' }).toString(),
}
)
expect(result.status).toBe(200)
const textResponse = await result.text()
expect(textResponse).toBeDefined()
})
test('Test 3: POST request with plain text', async () => {
const walletWithRequests = new CompletedProtoWallet(privKey)
const authFetch = new AuthFetch(walletWithRequests)
const result = await authFetch.fetch(
'http://localhost:3000/other-endpoint',
{
method: 'POST',
headers: {
'content-type': 'text/plain',
'x-bsv-test': 'this is a test header',
},
body: 'Hello, this is a plain text message!',
}
)
expect(result.status).toBe(200)
const textResponse = await result.text()
expect(textResponse).toBeDefined()
})
test('Test 4: POST request with binary data', async () => {
const walletWithRequests = new CompletedProtoWallet(privKey)
const authFetch = new AuthFetch(walletWithRequests)
const result = await authFetch.fetch(
'http://localhost:3000/other-endpoint',
{
method: 'POST',
headers: {
'content-type': 'application/octet-stream',
'x-bsv-test': 'this is a test header',
},
body: Utils.toArray('Hello from binary!'),
}
)
expect(result.status).toBe(200)
const textResponse = await result.text()
expect(textResponse).toBeDefined()
})
test('Test 5: Simple GET request', async () => {
const walletWithRequests = new CompletedProtoWallet(privKey)
const authFetch = new AuthFetch(walletWithRequests)
const result = await authFetch.fetch('http://localhost:3000/')
expect(result.status).toBe(200)
const textResponse = await result.text()
expect(textResponse).toBeDefined()
})
// TODO: Requires modifying the test server to support this.
// test('Test 6: Fetch and save video', async () => {
// const walletWithRequests = new CompletedProtoWallet(privKey)
// const authFetch = new AuthFetch(walletWithRequests)
// const videoResponse = await authFetch.fetch('http://localhost:3000/video')
// expect(videoResponse.status).toBe(200)
// const arrayBuffer = await videoResponse.arrayBuffer()
// const buffer = Buffer.from(arrayBuffer)
// const outputPath = path.join(__dirname, 'downloaded_video.mp4')
// fs.writeFileSync(outputPath, buffer)
// expect(fs.existsSync(outputPath)).toBe(true)
// const stats = fs.statSync(outputPath)
// expect(stats.size).toBeGreaterThan(0)
// })
test('Test 7: PUT request with JSON', async () => {
const walletWithRequests = new CompletedProtoWallet(privKey)
const authFetch = new AuthFetch(walletWithRequests)
const result = await authFetch.fetch(
'http://localhost:3000/put-endpoint',
{
method: 'PUT',
headers: {
'content-type': 'application/json',
'x-bsv-test': 'this is a test header',
},
body: JSON.stringify({ key: 'value', action: 'update' }),
}
)
expect(result.status).toBe(200)
const textResponse = await result.text()
expect(textResponse).toBeDefined()
})
test('Test 8: DELETE request', async () => {
const walletWithRequests = new CompletedProtoWallet(privKey)
const authFetch = new AuthFetch(walletWithRequests)
const result = await authFetch.fetch(
'http://localhost:3000/delete-endpoint',
{
method: 'DELETE',
headers: {
'x-bsv-test': 'this is a test header',
}
}
)
expect(result.status).toBe(200)
const textResponse = await result.text()
expect(textResponse).toBeDefined()
})
test('Test 9: Large binary upload', async () => {
const walletWithRequests = new CompletedProtoWallet(privKey)
const authFetch = new AuthFetch(walletWithRequests)
const largeBuffer = Utils.toArray('Hello from a large upload test')
const result = await authFetch.fetch('http://localhost:3000/large-upload', {
method: 'POST',
headers: {
'content-type': 'application/octet-stream',
},
body: largeBuffer
})
expect(result.status).toBe(200)
const textResponse = await result.text()
expect(textResponse).toBeDefined()
})
test('Test 10: Query parameters', async () => {
const walletWithRequests = new CompletedProtoWallet(privKey)
const authFetch = new AuthFetch(walletWithRequests)
const result = await authFetch.fetch(
'http://localhost:3000/query-endpoint?param1=value1¶m2=value2'
)
expect(result.status).toBe(200)
const textResponse = await result.text()
expect(textResponse).toBeDefined()
})
test('Test 11: Custom headers', async () => {
const walletWithRequests = new CompletedProtoWallet(privKey)
const authFetch = new AuthFetch(walletWithRequests)
const result = await authFetch.fetch('http://localhost:3000/custom-headers', {
method: 'GET',
headers: {
'x-bsv-custom-header': 'CustomHeaderValue',
}
})
expect(result.status).toBe(200)
const textResponse = await result.text()
expect(textResponse).toBeDefined()
})
// test('Test 12: Certificate request', async () => {
// const requestedCertificates: RequestedCertificateSet = {
// certifiers: [
// '03caa1baafa05ecbf1a5b310a7a0b00bc1633f56267d9f67b1fd6bb23b3ef1abfa',
// ],
// types: {
// 'z40BOInXkI8m7f/wBrv4MJ09bZfzZbTj2fJqCtONqCY=': ['firstName'],
// }
// }
// const walletWithRequests = new MockWallet(privKey)
// const certifierPrivateKey = PrivateKey.fromHex(
// '5a4d867377bd44eba1cecd0806c16f24e293f7e218c162b1177571edaeeaecef'
// )
// const certifierWallet = new CompletedProtoWallet(certifierPrivateKey)
// const certificateType = 'z40BOInXkI8m7f/wBrv4MJ09bZfzZbTj2fJqCtONqCY='
// const certificateSerialNumber = Utils.toBase64(new Array(32).fill(2))
// const fields = { firstName: 'Alice', lastName: 'Doe' }
// const masterCert = await MasterCertificate.issueCertificateForSubject(
// certifierWallet,
// (await walletWithRequests.getPublicKey({ identityKey: true })).publicKey,
// fields,
// certificateType,
// undefined,
// certificateSerialNumber
// )
// walletWithRequests.addMasterCertificate(masterCert)
// const authWithCerts = new AuthFetch(walletWithRequests)
// const certRequests = [
// authWithCerts.sendCertificateRequest(
// 'http://localhost:3000',
// requestedCertificates
// ),
// authWithCerts.sendCertificateRequest(
// 'http://localhost:3000',
// requestedCertificates
// )
// ]
// const certs = await Promise.all(certRequests)
// expect(certs).toBeDefined()
// expect(certs.length).toBe(2)
// // Add further assertions based on expected certificates
// }, 15000)
// // NOTE: YOU MUST MODIFY THE SERVER SIDE TO REQUEST CERTIFICATES FOR THIS TEST TO PASS:
// test('Test 13: Simple GET request with certificate requests', async () => {
// const requestedCertificates: RequestedCertificateSet = {
// certifiers: [
// '03caa1baafa05ecbf1a5b310a7a0b00bc1633f56267d9f67b1fd6bb23b3ef1abfa'
// ],
// types: {
// 'z40BOInXkI8m7f/wBrv4MJ09bZfzZbTj2fJqCtONqCY=': ['firstName']
// }
// }
// const walletWithRequests = new MockWallet(privKey)
// const certifierPrivateKey = PrivateKey.fromHex(
// '5a4d867377bd44eba1cecd0806c16f24e293f7e218c162b1177571edaeeaecef'
// )
// const certifierWallet = new CompletedProtoWallet(certifierPrivateKey)
// const certificateType = 'z40BOInXkI8m7f/wBrv4MJ09bZfzZbTj2fJqCtONqCY='
// const fields = { firstName: 'Alice', lastName: 'Doe' }
// const masterCert = await MasterCertificate.issueCertificateForSubject(
// certifierWallet,
// (await walletWithRequests.getPublicKey({ identityKey: true })).publicKey,
// fields,
// certificateType
// )
// walletWithRequests.addMasterCertificate(masterCert)
// const authFetchWithRequests = new AuthFetch(
// walletWithRequests,
// requestedCertificates
// )
// const result = await authFetchWithRequests.fetch('http://localhost:3000/')
// expect(result.status).toBe(200)
// const responseText = await result.text()
// expect(responseText).toBeDefined()
// const certs = authFetchWithRequests.consumeReceivedCertificates()
// expect(certs).toBeDefined()
// if (certs.length === 0) {
// console.log('No certificates received.')
// } else {
// const cert = certs[0]
// const verifiableCertificate = new VerifiableCertificate(
// cert.type,
// cert.serialNumber,
// cert.subject,
// cert.certifier,
// cert.revocationOutpoint,
// cert.fields,
// cert.keyring,
// cert.signature
// )
// const decryptedFields = await verifiableCertificate.decryptFields(walletWithRequests)
// console.log(decryptedFields)
// expect(Object.keys(cert.keyring) === Object.keys(decryptedFields))
// }
// }, 300000)
// --------------------------------------------------------------------------
// Edge-Case Tests
// --------------------------------------------------------------------------
test('Edge Case A: No Content-Type', async () => {
const walletWithRequests = new CompletedProtoWallet(privKey)
const authFetch = new AuthFetch(walletWithRequests)
await expect(
authFetch.fetch('http://localhost:3000/no-content-type-endpoint', {
method: 'POST',
// Intentionally no 'content-type' header
body: 'This should fail if your code requires Content-Type for POST.',
})
).rejects.toThrow()
})
test('Edge Case B: application json content with undefined body', async () => {
const walletWithRequests = new MockWallet(privKey)
const authFetch = new AuthFetch(walletWithRequests)
const result = await authFetch.fetch(
'http://localhost:3000/other-endpoint',
{
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: undefined
}
)
expect(result.status).toBe(200)
const jsonResponse = await result.json()
console.log(jsonResponse)
expect(jsonResponse).toBeDefined()
}, 1500000)
test('Edge Case C: application json content with body of type object', async () => {
const walletWithRequests = new MockWallet(privKey)
const authFetch = new AuthFetch(walletWithRequests)
const result = await authFetch.fetch(
'http://localhost:3000/other-endpoint',
{
method: 'POST',
headers: {
'content-type': 'application/json'
},
body: {}
}
)
expect(result.status).toBe(200)
const jsonResponse = await result.json()
console.log(jsonResponse)
expect(jsonResponse).toBeDefined()
}, 1500000)
// --------------------------------------------------------------------------
// New Test for Restarting Server Mid-Test with Two AuthFetch Instances
// --------------------------------------------------------------------------
test('Test 14: Two AuthFetch instances from the same identity key (restart server mid-test)', async () => {
// Use separate wallet instances with the same identity key.
const wallet1 = new MockWallet(privKey)
const authFetch1 = new AuthFetch(wallet1)
const resp1 = await authFetch1.fetch('http://localhost:3000/custom-headers', {
method: 'GET',
headers: { 'x-bsv-custom-header': 'CustomHeaderValue' }
})
expect(resp1.status).toBe(200)
const data1 = await resp1.json()
console.log('Data from first AuthFetch instance (before server restart):', data1)
expect(data1).toBeDefined()
// Close the server and wait for it to shut down.
await new Promise<void>((resolve) => {
server.close(() => {
console.log('Server closed mid-test')
resolve()
})
})
// Restart the server and assign it back to the 'server' variable.
server = startServer(3000)
await new Promise<void>((resolve, reject) => {
server.once('listening', () => {
console.log('Server restarted for second half of the test...')
resolve()
})
server.once('error', (err) => {
reject(err)
})
})
// Add a short delay to ensure the server is fully ready.
await new Promise((resolve) => setTimeout(resolve, 200))
// Create a fresh AuthFetch instance using a new wallet instance (same identity key).
const resp2 = await authFetch1.fetch('http://localhost:3000/custom-headers', {
method: 'GET',
headers: { 'x-bsv-custom-header': 'CustomHeaderValue' }
})
expect(resp2.status).toBe(200)
const data2 = await resp2.json()
console.log('Data from second AuthFetch instance (after server restart):', data2)
expect(data2).toBeDefined()
}, 150000)
test('Test 15: POST request with JSON header containing charset injection', async () => {
const walletWithRequests = new CompletedProtoWallet(privKey)
const authFetch = new AuthFetch(walletWithRequests)
const result = await authFetch.fetch(
'http://localhost:3000/other-endpoint',
{
method: 'POST',
headers: {
'content-type': 'application/json; charset=utf-8'
},
body: JSON.stringify({ message: 'Testing charset injection normalization!' })
}
)
expect(result.status).toBe(200)
const jsonResponse = await result.json()
console.log(jsonResponse)
expect(jsonResponse).toBeDefined()
}, 15000)
})