src/orders/agent.test.ts
v0.3.0 · 4.1 KB
import { describe, expect, it } from 'vitest';
import type { InfoCallOptions, InfoRequest, InfoRequester } from '../transport/types.js';
import { assertAgentActive, checkAgent } from './agent.js';
import { HlOrderError } from './errors.js';
const MASTER = `0x${'Aa'.repeat(20)}`;
const AGENT = `0x${'Bb'.repeat(20)}`;
const NOW = 1_800_000_000_000;
function infoAnswer(answer: unknown | Error) {
const calls: { body: InfoRequest; opts: InfoCallOptions | undefined }[] = [];
const info = (async (body: InfoRequest, opts?: InfoCallOptions) => {
calls.push({ body, opts });
if (answer instanceof Error) throw answer;
return answer;
}) as InfoRequester;
return { info, calls };
}
describe('checkAgent', () => {
it('queries extraAgents of the master (weight 20) and matches case-insensitively', async () => {
const { info, calls } = infoAnswer([
{ address: `0x${'cc'.repeat(20)}`, name: 'other', validUntil: null },
{ address: AGENT.toLowerCase(), name: 'bot-1', validUntil: NOW + 1000 },
]);
expect(await checkAgent(info, MASTER, AGENT, { now: NOW })).toEqual({
status: 'active',
agent: AGENT.toLowerCase(),
master: MASTER.toLowerCase(),
validUntil: NOW + 1000,
name: 'bot-1',
});
expect(calls).toEqual([{ body: { type: 'extraAgents', user: MASTER.toLowerCase() }, opts: { weight: 20 } }]);
});
it('validUntil in the past is expired; null, 0 and missing mean no expiry; numeric strings are read', async () => {
const at = async (validUntil: unknown) =>
(await checkAgent(infoAnswer([{ address: AGENT, name: 'x', validUntil }]).info, MASTER, AGENT, { now: NOW })).status;
expect(await at(NOW)).toBe('expired');
expect(await at(NOW - 1)).toBe('expired');
expect(await at(null)).toBe('active');
expect(await at(0)).toBe('active');
expect(await at(undefined)).toBe('active');
expect(await at(String(NOW + 5))).toBe('active');
await expect(at('soon')).rejects.toMatchObject({ code: 'INVALID_RESPONSE' });
});
it('absent agent is not_listed (may be an unnamed agent)', async () => {
const r = await checkAgent(infoAnswer([]).info, MASTER, AGENT);
expect(r).toMatchObject({ status: 'not_listed', validUntil: null, name: null });
});
it('rejects a non-array answer and malformed addresses', async () => {
await expect(checkAgent(infoAnswer({ agents: [] }).info, MASTER, AGENT)).rejects.toMatchObject({ code: 'INVALID_RESPONSE' });
await expect(checkAgent(infoAnswer([]).info, '0x123', AGENT)).rejects.toMatchObject({ code: 'INVALID_ARGUMENT' });
await expect(checkAgent(infoAnswer([]).info, MASTER, 'agent')).rejects.toMatchObject({ code: 'INVALID_ARGUMENT' });
});
});
describe('assertAgentActive', () => {
it('returns the check for an active agent', async () => {
const { info } = infoAnswer([{ address: AGENT, name: 'bot', validUntil: null }]);
expect((await assertAgentActive(info, MASTER, AGENT)).status).toBe('active');
});
it('throws AGENT_EXPIRED / AGENT_NOT_LISTED; allowUnlisted lets an unnamed agent through', async () => {
await expect(assertAgentActive(infoAnswer([{ address: AGENT, validUntil: NOW - 1 }]).info, MASTER, AGENT, { now: NOW })).rejects.toMatchObject({
code: 'AGENT_EXPIRED',
});
await expect(assertAgentActive(infoAnswer([]).info, MASTER, AGENT)).rejects.toMatchObject({ code: 'AGENT_NOT_LISTED' });
expect((await assertAgentActive(infoAnswer([]).info, MASTER, AGENT, { allowUnlisted: true })).status).toBe('not_listed');
// allowUnlisted never accepts an expired agent.
await expect(
assertAgentActive(infoAnswer([{ address: AGENT, validUntil: NOW - 1 }]).info, MASTER, AGENT, { now: NOW, allowUnlisted: true }),
).rejects.toMatchObject({ code: 'AGENT_EXPIRED' });
});
it('a failed read is AGENT_QUERY_FAILED with the cause: approval is unproven, fail closed', async () => {
const cause = new Error('HL info HTTP 502');
const err = await assertAgentActive(infoAnswer(cause).info, MASTER, AGENT).catch((e: unknown) => e);
expect(err).toBeInstanceOf(HlOrderError);
expect(err).toMatchObject({ code: 'AGENT_QUERY_FAILED', cause });
});
});