feat(analytics): report consenting customers' activity to Brevo (#56) #317

Merged
bermudalamb merged 4 commits from feature/56-brevo-tracker into main 2026-09-08 12:41:51 -05:00
Showing only changes of commit e7196f440b - Show all commits
@@ -81,7 +81,7 @@ describe('POST /api/customers/register', () => {
}); });
expect(Object.keys(res.body).sort()).toEqual([ expect(Object.keys(res.body).sort()).toEqual([
'created_at', 'email', 'email_verified', 'favorite_alerts', 'analytics_consent', 'created_at', 'email', 'email_verified', 'favorite_alerts',
'first_name', 'id', 'last_name', 'marketing_consent' 'first_name', 'id', 'last_name', 'marketing_consent'
]); ]);
}); });
@@ -106,6 +106,44 @@ describe('POST /api/customers/register', () => {
expect(res.body.marketing_consent).toBe(true); expect(res.body.marketing_consent).toBe(true);
}); });
// Quebec's Law 25 s.8.1 requires profiling to be off until the person turns
// it on, so this is a compliance property rather than a default worth
// debating. Asserted end to end because the column default, the register
// route and the stored wording all have to agree for it to hold.
it('creates an account with analytics consent off by default', async () => {
const res = await request(app).post('/api/customers/register').send({ firstName: 'Test', lastName: 'Customer',
email: 'analytics-default@example.com',
password: 'supersecret123'
});
expect(res.body.analytics_consent).toBe(false);
});
// The two consents are separate purposes and must be separately refusable.
// Taking the emails must not opt anybody into being tracked — that bundling
// is what GDPR treats as invalid consent, and it is the mistake this branch
// made once before it was caught.
it('opting in to marketing alone does not opt in to analytics', async () => {
const res = await request(app).post('/api/customers/register').send({ firstName: 'Test', lastName: 'Customer',
email: 'marketing-only@example.com',
password: 'supersecret123',
marketingConsent: true
});
expect(res.body.marketing_consent).toBe(true);
expect(res.body.analytics_consent).toBe(false);
});
it('respects an explicit analytics opt-in, independently of marketing', async () => {
const res = await request(app).post('/api/customers/register').send({ firstName: 'Test', lastName: 'Customer',
email: 'analytics-only@example.com',
password: 'supersecret123',
analyticsConsent: true
});
expect(res.body.analytics_consent).toBe(true);
// Refusing the emails while accepting the tracking has to be possible too,
// or the consent is not granular in both directions.
expect(res.body.marketing_consent).toBe(false);
});
it('rejects a duplicate email', async () => { it('rejects a duplicate email', async () => {
await request(app).post('/api/customers/register').send({ firstName: 'Test', lastName: 'Customer', await request(app).post('/api/customers/register').send({ firstName: 'Test', lastName: 'Customer',
email: 'dupe@example.com', email: 'dupe@example.com',