import test from 'brittle'
import {
MAX_REPUTATION_LEADERBOARD_ENTRIES,
buildReputationLeaderboardPayload,
buildReputationLeaderboardRoutePayload,
buildReputationRecordPayload,
buildReputationRecordRoutePayload,
isReputationRecordRoute,
reputationRecordPubkeyFromPath,
resolveReputationLeaderboardRoute,
resolveReputationRecordRoute,
sanitizeReputationRecord
} from 'p2p-hiverelay/core/relay-node/api-reputation-read.js'
function record (overrides = {}) {
return {
score: 12.345,
totalChallenges: 4,
passedChallenges: 3,
failedChallenges: 1,
avgLatencyMs: 25.7,
totalBytesServed: 512.5,
totalUptimeHours: 8.25,
region: ' EU ',
geoBonus: true,
firstSeen: 10,
lastActivity: 20,
secretToken: 'do-not-leak',
...overrides
}
}
test('api reputation read: missing reputation returns empty public payloads', (t) => {
t.alike(buildReputationLeaderboardPayload().payload, [])
const recordOut = buildReputationRecordPayload({ pubkey: 'a'.repeat(64) })
t.is(recordOut.status, undefined)
t.is(recordOut.payload, null)
t.alike(recordOut.headers, { 'Cache-Control': 'public, max-age=30' })
})
test('api reputation read: leaderboard route helper maps exact public read', (t) => {
t.alike(resolveReputationLeaderboardRoute('GET', '/api/reputation'), {
kind: 'reputation-leaderboard'
})
t.is(resolveReputationLeaderboardRoute('POST', '/api/reputation'), null)
t.is(resolveReputationLeaderboardRoute('GET', '/api/reputation/extra'), null)
t.is(resolveReputationLeaderboardRoute('GET', '/api/reputations'), null)
})
test('api reputation read: leaderboard route payload helper dispatches public read', (t) => {
let requestedLimit = null
const result = buildReputationLeaderboardRoutePayload({
route: resolveReputationLeaderboardRoute('GET', '/api/reputation'),
maxEntries: 1,
reputation: {
getLeaderboard (limit) {
requestedLimit = limit
return [{
relay: 'A'.repeat(64),
score: 3.456,
totalChallenges: 5,
passedChallenges: 4,
failedChallenges: 1,
secretToken: 'do-not-leak'
}]
}
}
})
t.is(requestedLimit, 1)
t.is(result.status, undefined)
t.alike(result.headers, { 'Cache-Control': 'public, max-age=30' })
t.alike(result.payload, [{
relay: 'a'.repeat(64),
score: 3.46,
reliability: '80%',
avgLatencyMs: 0,
uptimeHours: 0,
bytesServed: 0,
totalChallenges: 5,
passedChallenges: 4,
failedChallenges: 1,
region: 'unknown',
lastActivity: null,
firstSeen: null
}])
t.absent(JSON.stringify(result.payload).includes('do-not-leak'))
const unknown = buildReputationLeaderboardRoutePayload({
route: resolveReputationRecordRoute('GET', '/api/reputation/' + 'a'.repeat(64)),
reputation: {
getLeaderboard () {
throw new Error('record routes are handled by buildReputationRecordRoutePayload')
}
}
})
t.is(unknown.status, 404)
t.alike(unknown.payload, { error: 'unknown reputation leaderboard route' })
})
test('api reputation read: invalid pubkey rejects before store lookup', (t) => {
let called = false
const out = buildReputationRecordPayload({
pubkey: 'not-hex',
reputation: {
getRecord () {
called = true
return record()
}
}
})
t.is(out.status, 400)
t.alike(out.payload, { error: 'Invalid pubkey' })
t.absent(called, 'store lookup is skipped for invalid pubkey')
})
test('api reputation read: record route helper isolates pubkey path parsing', (t) => {
const pubkey = 'A'.repeat(64)
const path = `/api/reputation/${pubkey}`
let calledWith = null
t.ok(isReputationRecordRoute(path))
t.ok(isReputationRecordRoute('/api/reputation/not-hex'))
t.absent(isReputationRecordRoute('/api/reputation'))
t.alike(resolveReputationRecordRoute('GET', path), { kind: 'reputation-record' })
t.is(resolveReputationRecordRoute('POST', path), null)
t.is(resolveReputationRecordRoute('GET', '/api/reputation'), null)
t.is(reputationRecordPubkeyFromPath(path), pubkey)
t.is(reputationRecordPubkeyFromPath('/api/reputation/not-hex'), 'not-hex')
t.is(reputationRecordPubkeyFromPath('/api/reputation'), '')
const out = buildReputationRecordRoutePayload({
path,
reputation: {
getRecord (key) {
calledWith = key
return record({ region: 'NA' })
}
}
})
t.is(calledWith, 'a'.repeat(64))
t.is(out.payload.pubkey, 'a'.repeat(64))
t.is(out.payload.region, 'NA')
t.alike(out.headers, { 'Cache-Control': 'public, max-age=30' })
let invalidLookup = false
const invalid = buildReputationRecordRoutePayload({
path: '/api/reputation/not-hex',
reputation: {
getRecord () {
invalidLookup = true
return record()
}
}
})
t.absent(invalidLookup, 'malformed route pubkey does not reach reputation store')
t.is(invalid.status, 400)
t.alike(invalid.payload, { error: 'Invalid pubkey' })
})
test('api reputation read: sanitizes direct records without raw store fields', (t) => {
const pubkey = 'A'.repeat(64)
const out = buildReputationRecordPayload({
pubkey,
reputation: {
getRecord (key) {
t.is(key, 'a'.repeat(64), 'lookup key is canonicalized')
return record({
passedChallenges: 9,
failedChallenges: 9,
region: 'NA',
privateNote: 'hidden'
})
}
}
})
t.alike(out.payload, {
score: 12.35,
totalChallenges: 4,
passedChallenges: 4,
failedChallenges: 0,
avgLatencyMs: 25,
totalBytesServed: 512.5,
totalUptimeHours: 8.25,
region: 'NA',
geoBonus: true,
firstSeen: 10,
lastActivity: 20,
pubkey: 'a'.repeat(64)
})
t.absent(JSON.stringify(out.payload).includes('secretToken'))
t.absent(JSON.stringify(out.payload).includes('privateNote'))
t.absent(JSON.stringify(out.payload).includes('do-not-leak'))
})
test('api reputation read: caps and sanitizes leaderboard rows', (t) => {
t.is(MAX_REPUTATION_LEADERBOARD_ENTRIES, 100)
let requestedLimit = null
const rows = []
for (let i = 0; i < 105; i++) {
rows.push({
relay: i === 0 ? ' relay-a ' : String(i % 10).repeat(64),
score: 2.345,
reliability: i === 0 ? 'bad' : '80%',
avgLatencyMs: 31.9,
uptimeHours: 6.8,
bytesServed: 1024,
totalChallenges: 10,
passedChallenges: 8,
failedChallenges: 2,
region: i === 0 ? ' Mars ' : 'APAC',
lastActivity: 200,
firstSeen: 100,
secretToken: 'do-not-leak'
})
}
const out = buildReputationLeaderboardPayload({
reputation: {
getLeaderboard (limit) {
requestedLimit = limit
return rows
}
}
})
t.is(requestedLimit, MAX_REPUTATION_LEADERBOARD_ENTRIES)
t.is(out.payload.length, MAX_REPUTATION_LEADERBOARD_ENTRIES)
t.alike(out.payload[0], {
relay: 'relay-a',
score: 2.35,
reliability: '80%',
avgLatencyMs: 31,
uptimeHours: 6,
bytesServed: 1024,
totalChallenges: 10,
passedChallenges: 8,
failedChallenges: 2,
region: 'Mars',
lastActivity: 200,
firstSeen: 100
})
t.alike(out.headers, { 'Cache-Control': 'public, max-age=30' })
t.absent(JSON.stringify(out.payload).includes('secretToken'))
t.absent(JSON.stringify(out.payload).includes('do-not-leak'))
})
test('api reputation read: shared record sanitizer clamps malformed counters', (t) => {
t.alike(sanitizeReputationRecord(record({
score: Infinity,
totalChallenges: 2,
passedChallenges: 7,
failedChallenges: 7,
avgLatencyMs: -1,
totalBytesServed: -1,
totalUptimeHours: -1,
region: 'bad\nregion',
geoBonus: 'yes',
firstSeen: -1,
lastActivity: Number.NaN
})), {
score: 0,
totalChallenges: 2,
passedChallenges: 2,
failedChallenges: 0,
avgLatencyMs: 0,
totalBytesServed: 0,
totalUptimeHours: 0,
region: null,
geoBonus: false,
firstSeen: null,
lastActivity: null
})
})