Add dataset and identity contract foundations
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
import { describe, expect, test } from 'bun:test';
|
||||
import {
|
||||
CAPABILITY_DEFINITIONS,
|
||||
CAPABILITY_REQUIREMENTS,
|
||||
ESI_ASSETS_SCOPE,
|
||||
ESI_WALLET_SCOPE,
|
||||
gateCapabilityWithAuthorization,
|
||||
validateCapabilityScopes,
|
||||
validateResourceCapabilityScopes,
|
||||
validateConsentCallback,
|
||||
validateDatasetJobFence,
|
||||
validateCompleteReportPins,
|
||||
} from '../src/lib/domain/capabilities';
|
||||
import { afterEndInventoryCursorState, afterEndInventoryResult, ambiguousType, appendDatasetState, authorization, consent, datasetState, inventoryCursor, inventoryCursorState, inventoryQuery, inventoryResult, approvedMoneyPolicy, singletonRows, syntheticResource, systemJob, walletResult } from '../fixtures/contracts/t02';
|
||||
|
||||
describe('T02 typed contract fixtures', () => {
|
||||
test('separates snapshot generations from append checkpoints and unknown upstream time', () => {
|
||||
expect(datasetState.coverage).toEqual({ kind: 'snapshot', generation: 'assets-g7', complete: true });
|
||||
expect(datasetState.fetchedAt).toBeGreaterThan(0);
|
||||
expect(datasetState.upstreamEffectiveAt).toBeNull();
|
||||
expect(appendDatasetState.coverage.kind).toBe('append');
|
||||
if (appendDatasetState.coverage.kind === 'append') {
|
||||
expect(appendDatasetState.coverage.checkpoint).toBe('journal-from-90000000000000001');
|
||||
}
|
||||
expect(typeof authorization.grantRevision).toBe('number');
|
||||
expect(typeof authorization.credentialVersion).toBe('number');
|
||||
});
|
||||
|
||||
test('binds consent to one session, capability, scopes and selected resources', () => {
|
||||
expect(consent.state).toBe('pending');
|
||||
expect(consent.sessionId).toBe('session-fixture-1');
|
||||
expect(consent.resourceRequests[0]?.resource.authorizationRevision).toBe(7);
|
||||
expect(consent.resourceRequests[0]?.requestedScopes).toEqual([ESI_ASSETS_SCOPE]);
|
||||
});
|
||||
|
||||
test('keeps system job authority distinct from user resources', () => {
|
||||
expect(systemJob.authority).toBe('system');
|
||||
expect(systemJob.owner).toEqual({ kind: 'system', service: 'reference' });
|
||||
expect(systemJob.publicationFence.resources).toHaveLength(0);
|
||||
expect(validateDatasetJobFence(systemJob)).toEqual({ valid: true });
|
||||
});
|
||||
|
||||
test('pins inventory cursors and reports to authorized resources and all matching rows', () => {
|
||||
expect(inventoryQuery.cursor).toBe(inventoryCursor);
|
||||
expect(inventoryCursorState.token).toBe(inventoryCursor.token);
|
||||
expect(inventoryCursorState.status).toBe('active');
|
||||
expect(inventoryCursorState.position).toEqual({ offset: 0, lastItemId: null });
|
||||
expect(inventoryQuery.sort.tieBreaker).toBe('item_id');
|
||||
expect(inventoryQuery.authorizationRevisions['character:90000000000000001']).toBe(7);
|
||||
expect(inventoryCursorState.pin.callerUserId).toBe('fixture-user-a');
|
||||
expect(inventoryCursorState.pin.authorizationEpochs['character:90000000000000001']).toBe(1);
|
||||
expect(inventoryResult.status).toBe('ready');
|
||||
if (inventoryResult.status === 'ready') {
|
||||
expect(inventoryResult.summary.scope).toBe('all_matching_rows');
|
||||
expect(inventoryResult.pageState).toBe('initial');
|
||||
expect(inventoryResult.provenance.input.snapshotGenerations.assets).toBe('assets-g7');
|
||||
expect(inventoryResult.provenance.input.referenceVersion).toBe('reference-g1');
|
||||
expect(validateCompleteReportPins(inventoryResult.provenance.input)).toEqual({ valid: true });
|
||||
expect(validateCompleteReportPins({ ...inventoryResult.provenance.input, datasetPins: [{ ...inventoryResult.provenance.input.datasetPins[0]!, authorizationRevision: 8 }] })).toEqual({ valid: false, reason: 'revision_mismatch' });
|
||||
}
|
||||
});
|
||||
|
||||
test('after-end pages keep all-matching summaries and terminate the cursor', () => {
|
||||
expect(afterEndInventoryResult.status).toBe('ready');
|
||||
if (afterEndInventoryResult.status === 'ready') {
|
||||
expect(afterEndInventoryResult.pageState).toBe('after_end');
|
||||
expect(afterEndInventoryResult.rows).toHaveLength(0);
|
||||
expect(afterEndInventoryResult.summary.matchingRows).toBe(2);
|
||||
expect(afterEndInventoryResult.nextCursor).toBeNull();
|
||||
expect(afterEndInventoryCursorState.status).toBe('exhausted');
|
||||
expect(afterEndInventoryCursorState.position.lastItemId).toBe('90000000000000042');
|
||||
}
|
||||
});
|
||||
|
||||
test('singleton facts preserve true, false and legacy unknown states', () => {
|
||||
expect(singletonRows.map((row) => row.isSingleton)).toEqual([true, false, null]);
|
||||
});
|
||||
|
||||
test('ambiguous entity matches remain explicit instead of selecting by name', () => {
|
||||
expect(ambiguousType.status).toBe('ambiguous');
|
||||
if (ambiguousType.status === 'ambiguous') expect(ambiguousType.candidates).toHaveLength(2);
|
||||
});
|
||||
|
||||
test('wallet is a separately labelled component and preserves signed cents', () => {
|
||||
expect(walletResult.status).toBe('ready');
|
||||
if (walletResult.status === 'ready') {
|
||||
expect(walletResult.components[0]?.component).toBe('wallet_balance');
|
||||
expect(walletResult.components[0]?.amount).toBe('-125');
|
||||
expect(walletResult.components[0]?.credentialVersion).toBe(2);
|
||||
expect(walletResult.provenance.input.datasetPins[0]?.dataset).toBe('wallet_balance');
|
||||
}
|
||||
});
|
||||
|
||||
test('capability metadata is allowlisted and wallet consent is opt-in', () => {
|
||||
expect(CAPABILITY_REQUIREMENTS.wallet_balance).toEqual(['wallet_balance']);
|
||||
expect(CAPABILITY_DEFINITIONS.wallet_balance.requiredScopes).toEqual([ESI_WALLET_SCOPE]);
|
||||
expect(CAPABILITY_DEFINITIONS.inventory_query.requiredScopes).toEqual([ESI_ASSETS_SCOPE]);
|
||||
expect(validateCapabilityScopes('wallet_balance', [ESI_WALLET_SCOPE]).valid).toBe(true);
|
||||
expect(validateCapabilityScopes('wallet_balance', []).missingScopes).toEqual([ESI_WALLET_SCOPE]);
|
||||
expect(validateCapabilityScopes('wallet_balance', [ESI_WALLET_SCOPE, ESI_ASSETS_SCOPE], [ESI_ASSETS_SCOPE]).preservedScopes).toEqual([ESI_ASSETS_SCOPE]);
|
||||
expect(validateResourceCapabilityScopes('wallet_balance', consent.resourceRequests[0]!).principal).toEqual(syntheticResource.principal);
|
||||
expect(validateResourceCapabilityScopes('inventory_query', {
|
||||
...consent.resourceRequests[0]!,
|
||||
resource: { ...syntheticResource, requiredScopes: [ESI_WALLET_SCOPE] },
|
||||
}).missingScopes).toEqual([ESI_WALLET_SCOPE]);
|
||||
const accepted = {
|
||||
status: 'accepted',
|
||||
bindingId: consent.bindingId,
|
||||
resourceGrants: [{
|
||||
request: consent.resourceRequests[0]!,
|
||||
grantedScopes: [ESI_ASSETS_SCOPE],
|
||||
grantRevision: 7,
|
||||
credentialVersion: 3,
|
||||
authorizationEpoch: 1,
|
||||
}],
|
||||
} as const;
|
||||
expect(validateConsentCallback(consent, accepted, 1_700_000_100_000)).toEqual({ valid: true });
|
||||
expect(validateConsentCallback(consent, {
|
||||
...accepted,
|
||||
resourceGrants: [{ ...accepted.resourceGrants[0]!, request: { ...accepted.resourceGrants[0]!.request, resource: { ...syntheticResource, principal: { kind: 'character', id: '90000000000000002', ownerUserId: 'fixture-user-a' } } } }],
|
||||
}, 1_700_000_100_000)).toEqual({ valid: false, reason: 'resource_mismatch' });
|
||||
expect(validateConsentCallback(consent, accepted, consent.expiresAt)).toEqual({ valid: false, reason: 'binding_mismatch' });
|
||||
expect(validateConsentCallback(consent, { ...accepted, resourceGrants: [{ ...accepted.resourceGrants[0]!, grantRevision: Number.NaN }] }, 1_700_000_100_000)).toEqual({ valid: false, reason: 'revision_mismatch' });
|
||||
expect(validateCapabilityScopes('wallet_balance', ['unallowlisted.scope']).valid).toBe(false);
|
||||
expect(gateCapabilityWithAuthorization('wallet_balance', {
|
||||
userId: 'fixture-user-a', resourceAuthorizations: [], selectedResources: [], datasets: [],
|
||||
}).status).toBe('requirements_missing');
|
||||
expect(gateCapabilityWithAuthorization('wallet_balance', {
|
||||
userId: 'fixture-user-a', resourceAuthorizations: [{
|
||||
...authorization, principal: { kind: 'corporation', id: '99000000000000001', ownerUserId: 'fixture-user-a' }, grantedScopes: [ESI_WALLET_SCOPE],
|
||||
}], selectedResources: [{
|
||||
principal: { kind: 'corporation', id: '99000000000000001', ownerUserId: 'fixture-user-a' },
|
||||
requiredScopes: [ESI_WALLET_SCOPE], authorizationRevision: 1, authorizationEpoch: 1,
|
||||
}], datasets: [],
|
||||
}).status).toBe('requirements_missing');
|
||||
expect(gateCapabilityWithAuthorization('inventory_query', {
|
||||
userId: 'fixture-user-a', resourceAuthorizations: [authorization], selectedResources: [
|
||||
{ principal: { kind: 'character', id: '90000000000000001', ownerUserId: 'fixture-user-a' }, requiredScopes: [ESI_ASSETS_SCOPE], authorizationRevision: 7, authorizationEpoch: 1 },
|
||||
], datasets: [datasetState, { ...datasetState, principal: { kind: 'system', id: 'reference-public' }, dataset: 'reference_data' }],
|
||||
}).status).toBe('available');
|
||||
expect(gateCapabilityWithAuthorization('inventory_query', {
|
||||
userId: 'fixture-user-a', resourceAuthorizations: [authorization], selectedResources: [syntheticResource], datasets: [
|
||||
datasetState,
|
||||
{ ...datasetState, dataset: 'reference_data' },
|
||||
],
|
||||
}).status).toBe('requirements_missing');
|
||||
expect(gateCapabilityWithAuthorization('inventory_query', {
|
||||
userId: 'fixture-user-a', resourceAuthorizations: [authorization], selectedResources: [syntheticResource], datasets: [
|
||||
{ ...datasetState, authorizationEpoch: 2 },
|
||||
{ ...datasetState, principal: { kind: 'system', id: 'reference-public' }, dataset: 'reference_data' },
|
||||
],
|
||||
}).status).toBe('requirements_missing');
|
||||
expect(gateCapabilityWithAuthorization('inventory_query', {
|
||||
userId: 'fixture-user-a', resourceAuthorizations: [authorization], selectedResources: [
|
||||
syntheticResource,
|
||||
{ principal: { kind: 'character', id: '90000000000000002', ownerUserId: 'fixture-user-a' }, requiredScopes: [ESI_ASSETS_SCOPE], authorizationRevision: 7, authorizationEpoch: 1 },
|
||||
], datasets: [datasetState, { ...datasetState, principal: { kind: 'system', id: 'reference-public' }, dataset: 'reference_data' }],
|
||||
}).status).toBe('requirements_missing');
|
||||
expect(gateCapabilityWithAuthorization('inventory_query', {
|
||||
userId: 'fixture-user-a', resourceAuthorizations: [
|
||||
authorization,
|
||||
{ ...authorization, principal: { kind: 'character', id: '90000000000000002', ownerUserId: 'fixture-user-a' }, grantedScopes: [] },
|
||||
], selectedResources: [
|
||||
syntheticResource,
|
||||
{ principal: { kind: 'character', id: '90000000000000002', ownerUserId: 'fixture-user-a' }, requiredScopes: [ESI_ASSETS_SCOPE], authorizationRevision: 7, authorizationEpoch: 1 },
|
||||
], datasets: [datasetState, { ...datasetState, principal: { kind: 'character', id: '90000000000000002', ownerUserId: 'fixture-user-a' }, dataset: 'assets' }, { ...datasetState, principal: { kind: 'system', id: 'reference-public' }, dataset: 'reference_data' }],
|
||||
}).status).toBe('requirements_missing');
|
||||
});
|
||||
|
||||
test('approved money policy fixes grammar, bounds and signed rounding', () => {
|
||||
expect(approvedMoneyPolicy.input).toBe('decimal_text');
|
||||
expect(approvedMoneyPolicy.rounding).toBe('half_away_from_zero');
|
||||
expect(approvedMoneyPolicy.grammar).toBe('json_number_ascii_decimal');
|
||||
expect(approvedMoneyPolicy.maxRawInputCharacters).toBe(8192);
|
||||
expect(approvedMoneyPolicy.maxCoefficientDigits).toBe(4096);
|
||||
expect(approvedMoneyPolicy.exponentRange).toEqual([-1000, 1000]);
|
||||
expect(approvedMoneyPolicy.centsShiftRange).toEqual([-1000, 1000]);
|
||||
expect(approvedMoneyPolicy.canonicalizeNegativeZero).toBe(true);
|
||||
expect(authorization.staleAuthority.maximumWindowMs).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user