Merge branch 'main' into feature/202-sql-invariant
Linting / lint (pull_request) Successful in 2m27s
SonarQube Analysis / sonarqube (pull_request) Failing after 18m3s

This commit is contained in:
2026-08-28 14:04:10 -05:00
7 changed files with 148 additions and 27 deletions
@@ -7,6 +7,7 @@ jest.mock('../../src/mailer', () => ({
sendMail: jest.fn().mockResolvedValue(undefined)
}));
import { sendMail } from '../../src/mailer';
import { notifyFavoritersOfSale } from '../../src/favoriteAlerts';
const sentMail = sendMail as jest.MockedFunction<typeof sendMail>;
beforeEach(async () => {
@@ -168,36 +169,69 @@ describe('notifying when a favorited item sells', () => {
expect(purchase.status).toBeLessThan(400);
}
// The demo route deliberately notifies nobody (#206) and the PayPal path has
// no integration coverage, so the tests below call the notifier the way the
// PayPal capture and webhook routes do — after the purchase, with the sold
// ids and the buyer. That keeps them about *who* gets told, which is what
// they were testing all along; whether the demo route itself notifies is
// asserted separately, above.
async function buyThenNotify(
agent: ReturnType<typeof request.agent>,
itemId: number,
buyerId: number | null
) {
await buyViaDemo(agent, itemId);
await notifyFavoritersOfSale([itemId], buyerId);
}
it('emails a favoriter who opted in', async () => {
const itemId = await createItem('Wanted item');
const { agent: watcher } = await register('watcher@example.com');
await watcher.post(`/api/customers/me/favorites/${itemId}`);
await watcher.put('/api/customers/me/favorite-alerts').send({ enabled: true });
const { agent: buyer } = await register('buyer@example.com');
await buyViaDemo(buyer, itemId);
const { agent: buyer, id: buyerId } = await register('buyer@example.com');
await buyThenNotify(buyer, itemId, buyerId);
expect(soldNotificationsTo()).toEqual(['watcher@example.com']);
});
// A demo purchase is not a sale. The item really is marked sold, so the
// storefront is telling the truth about availability, but nobody bought
// anything and nobody is shipping anything — and while production runs the
// demo interim (#191) this mail reaches real favoriters through real SMTP,
// telling them an item "has been sold to another customer" and "will not be
// restocked". Both are false. See #206.
it('sends nothing when the purchase was a demo', async () => {
const itemId = await createItem('Demo bought');
const { agent: watcher } = await register('watcher-demo@example.com');
await watcher.post(`/api/customers/me/favorites/${itemId}`);
await watcher.put('/api/customers/me/favorite-alerts').send({ enabled: true });
const { agent: buyer } = await register('demo-buyer@example.com');
await buyViaDemo(buyer, itemId);
expect(soldNotificationsTo()).toEqual([]);
});
it('does not email a favoriter who never opted in', async () => {
const itemId = await createItem('Wanted item');
const { agent: watcher } = await register('silent@example.com');
await watcher.post(`/api/customers/me/favorites/${itemId}`);
const { agent: buyer } = await register('buyer2@example.com');
await buyViaDemo(buyer, itemId);
const { agent: buyer, id: buyerId } = await register('buyer2@example.com');
await buyThenNotify(buyer, itemId, buyerId);
expect(soldNotificationsTo()).toEqual([]);
});
it('does not tell the buyer their own purchase is unavailable', async () => {
const itemId = await createItem('Self bought');
const { agent: buyer } = await register('selfbuy@example.com');
const { agent: buyer, id: buyerId } = await register('selfbuy@example.com');
await buyer.post(`/api/customers/me/favorites/${itemId}`);
await buyer.put('/api/customers/me/favorite-alerts').send({ enabled: true });
await buyViaDemo(buyer, itemId);
await buyThenNotify(buyer, itemId, buyerId);
expect(soldNotificationsTo()).toEqual([]);
});
@@ -210,10 +244,10 @@ describe('notifying when a favorited item sells', () => {
await agent.put('/api/customers/me/favorite-alerts').send({ enabled: true });
}
const { agent: buyer } = await register('c@example.com');
const { agent: buyer, id: buyerId } = await register('c@example.com');
await buyer.post(`/api/customers/me/favorites/${itemId}`);
await buyer.put('/api/customers/me/favorite-alerts').send({ enabled: true });
await buyViaDemo(buyer, itemId);
await buyThenNotify(buyer, itemId, buyerId);
expect(soldNotificationsTo().sort()).toEqual(['a@example.com', 'b@example.com']);
});