Files
picpeak/backend/__tests__/services/usageIngressCompatibility.test.js
T
Paul Nothaft 5c1e38d921 feat(usage): distinguish real edits and template delivery with v5 consent (#1339)
* feat(usage): distinguish real edits and template delivery with v5 consent

* fix(usage): exclude queued test messages and count reorders as edits

- queueEmail carries usageEligible: false into email_data and the queue
  processor passes it on, so the dev tools' send-test-email no longer
  records email_template_delivery once the worker sends it.
- event-types/reorder and categories/reorder-global compare the persisted
  order before and after and record the v5 edit markers only when it
  changed, matching the display_order edit already counted on PUT.
- normalized() builds arrays with Array.from so a row array from the sqlite
  binding compares equal under Jest's separate realm.

* fix(usage): cover per-gallery category order and workflow test runs

- categories/reorder records category_editing when an event's override
  changes; reorder/:eventId records it when an override was actually
  removed.
- send_email and the collections handoff pass usageEligible: false for a
  workflow test run (engine.testRun sets __test), so a non-dry test send is
  not counted as template delivery.

---------

Co-authored-by: Paul Nothaft <paul@MacStudio-von-Paul.local>
2026-09-07 20:03:15 +02:00

42 lines
2.2 KiB
JavaScript

const crypto = require('crypto');
const p = require('../../src/usage/protocol.cjs');
const now = Date.parse('2026-09-06T12:00:00.000Z');
const signHistorical = (packet, id) => {
const signed = { packet, public_key: id.public_key, issued_at: new Date(now).toISOString(), nonce: crypto.randomUUID() };
return { ...signed, signature: crypto.sign(null, Buffer.from(p.canonical(signed)), id.private_key).toString('base64url') };
};
describe.each(['usage.v1', 'usage.v2', 'usage.v3', 'usage.v4', 'usage.v5'])('%s receiver compatibility never loosens the PicPeak sender', version => {
test('complete original reports still sign and verify', () => {
const id = p.generateIdentity();
const packet = p.makePacket(id, 'report', 1, {
picpeak_version: '1.0.0', report_date: '2026-09-06', generated_at: new Date(now).toISOString(),
features: p.emptyFeatures(version), gallery_layouts: [],
...(['usage.v3', 'usage.v4', 'usage.v5'].includes(version) ? { inventory: { galleries: 0, photos: 0 } } : {}),
}, version);
const envelope = p.signPacket(packet, id, new Date(now));
expect(p.verifyEnvelope(envelope, now)).toEqual(packet);
expect(p.verifyReceivedEnvelope(envelope, now)).toEqual(packet);
});
test.each([undefined, null, {}, { crm: { used: true, configured: null } }])('accepts partial incoming measurements %p without rewriting them', features => {
const id = p.generateIdentity();
const packet = p.makePacket(id, 'report', 1, {
report_date: '2020-01-02', generated_at: '2020-01-02T12:00:00.000Z',
...(features === undefined ? {} : { features }),
}, version);
expect(() => p.signPacket(packet, id, new Date(now))).toThrow('INVALID_PACKET');
const envelope = signHistorical(packet, id), original = JSON.stringify(envelope);
expect(() => p.verifyEnvelope(envelope, now)).toThrow('INVALID_PACKET');
expect(p.verifyReceivedEnvelope(envelope, now)).toEqual(packet);
expect(JSON.stringify(envelope)).toBe(original);
});
test('registration still requires exact explicit consent', () => {
const id = p.generateIdentity();
const packet = p.makePacket(id, 'register', 0, {}, version);
expect(() => p.verifyReceivedEnvelope(signHistorical(packet, id), now)).toThrow('INVALID_PACKET');
});
});