morphit/apps/indexer/test/handlers/operatorBlockGate.test.ts

163 lines
4.7 KiB
TypeScript

/**
* B3 regression suite — operator-block + operator-payment-method
* gates use operatorAccountName (per-instance), not
* officialAccountName (federation-wide release-signer).
*
* Pre-fix: a community operator running their own instance had to
* choose between accepting Morphit-canonical releases (gate=morphit)
* or curating their own block list (gate=their own account).
*
* Post-fix: officialAccountName stays as the release trust anchor;
* operatorAccountName is the per-instance moderation gate. Both
* default to the same value for back-compat with the canonical
* morphit.io deployment.
*/
import { describe, expect, it } from 'vitest';
import operatorBlockHandler from '$indexer/handlers/operatorBlock';
import operatorPaymentMethodHandler from '$indexer/handlers/operatorPaymentMethod';
import { makeCtx } from '../testutils/context';
import { fakeConfig } from '../testutils/context';
import { makeMockClient } from '../testutils/mockClient';
describe('B3 regression — operator-block + payment-method gates', () => {
it('B3: community operator can curate own instance with operatorAccountName=bob', async () => {
// Bob runs his own Morphit instance. He sets:
// officialAccountName = morphit (release verification anchor)
// operatorAccountName = bob (his per-instance moderation gate)
// He signs a block op against alice from his own account.
const config = fakeConfig({
officialAccountName: 'morphit',
operatorAccountName: 'bob'
});
const mock = makeMockClient([
{ match: 'SELECT state FROM operator_blocks', rows: [] },
{ match: 'INSERT INTO operator_blocks', rowCount: 1 }
]);
const r = await operatorBlockHandler(
makeCtx({
signer: 'bob',
config,
payload: {
v: 1,
blocked: 'alice',
action: 'block',
reason: 'spamming the orderbook',
ts: 1730000000
}
}),
mock.client
);
expect(r).toEqual({ ok: true });
});
it("B3: morphit (the release-signer) is rejected on bob's instance", async () => {
// Inverse case: morphit signs a block op, but Bob's instance
// has operatorAccountName=bob, so morphit's signature is
// rejected as not_operator. Releases signed by morphit still
// pass through release.ts (different trust anchor).
const config = fakeConfig({
officialAccountName: 'morphit',
operatorAccountName: 'bob'
});
const mock = makeMockClient();
const r = await operatorBlockHandler(
makeCtx({
signer: 'morphit',
config,
payload: {
v: 1,
blocked: 'alice',
action: 'block',
reason: 'spamming',
ts: 1730000000
}
}),
mock.client
);
expect(r).toEqual({ ok: false, reason: 'not_operator' });
// No DB query attempted.
expect(mock.queries).toHaveLength(0);
});
it('B3: canonical morphit instance (both knobs = morphit) still works', async () => {
// Back-compat case. The canonical morphit.io deployment leaves
// operatorAccountName empty in env (= falls back to
// officialAccountName='morphit'). Both gates allow morphit.
const config = fakeConfig({
officialAccountName: 'morphit',
operatorAccountName: 'morphit'
});
const mock = makeMockClient([
{ match: 'SELECT state FROM operator_blocks', rows: [] },
{ match: 'INSERT INTO operator_blocks', rowCount: 1 }
]);
const r = await operatorBlockHandler(
makeCtx({
signer: 'morphit',
config,
payload: {
v: 1,
blocked: 'alice',
action: 'block',
reason: 'spam',
ts: 1730000000
}
}),
mock.client
);
expect(r).toEqual({ ok: true });
});
it('B3: operatorPaymentMethod uses the same operatorAccountName gate', async () => {
const config = fakeConfig({
officialAccountName: 'morphit',
operatorAccountName: 'bob'
});
// bob is allowed.
const mockBob = makeMockClient([
{ match: 'SELECT state', rows: [] },
{ match: 'INSERT INTO instance_payment_methods', rowCount: 1 }
]);
const rBob = await operatorPaymentMethodHandler(
makeCtx({
signer: 'bob',
config,
payload: {
v: 1,
action: 'add',
key: 'wechat',
name: 'WeChat Pay',
description: 'Available for clients in PRC',
category: 'online',
url: 'https://pay.weixin.qq.com',
ts: 1730000000
}
}),
mockBob.client
);
expect(rBob).toEqual({ ok: true });
// morphit is NOT allowed on bob's instance.
const mockMorphit = makeMockClient();
const rMorphit = await operatorPaymentMethodHandler(
makeCtx({
signer: 'morphit',
config,
payload: {
v: 1,
action: 'add',
key: 'wechat',
name: 'WeChat Pay',
description: '...',
category: 'online',
url: null,
ts: 1730000000
}
}),
mockMorphit.client
);
expect(rMorphit).toEqual({ ok: false, reason: 'not_operator' });
expect(mockMorphit.queries).toHaveLength(0);
});
});