104 lines
3.2 KiB
TypeScript
104 lines
3.2 KiB
TypeScript
import { randomUUID } from 'node:crypto';
|
|
import { identityApp, type IdentityApp } from './helpers/identity-app';
|
|
import { checkoutFixture, couponInput } from './helpers/checkout';
|
|
import { secondActor } from './helpers/commerce';
|
|
|
|
describe('coupon administration', () => {
|
|
let ctx: IdentityApp;
|
|
beforeAll(async () => {
|
|
ctx = await identityApp();
|
|
}, 60000);
|
|
afterAll(async () => {
|
|
await ctx.close();
|
|
});
|
|
beforeEach(async () => {
|
|
await ctx.clearLimits();
|
|
});
|
|
it('requires permissions, scopes reads and deactivates immutable coupon rules', async () => {
|
|
const customer = await secondActor(ctx);
|
|
await ctx
|
|
.api()
|
|
.post('/api/v1/coupons')
|
|
.auth(customer.token, { type: 'bearer' })
|
|
.send(couponInput())
|
|
.expect(403);
|
|
const created = await ctx
|
|
.api()
|
|
.post('/api/v1/coupons')
|
|
.auth(ctx.token, { type: 'bearer' })
|
|
.send(couponInput())
|
|
.expect(201);
|
|
const list = await ctx
|
|
.api()
|
|
.get('/api/v1/coupons')
|
|
.auth(ctx.token, { type: 'bearer' })
|
|
.expect(200);
|
|
expect(
|
|
list.body.some((row: { id: string }) => row.id === created.body.id),
|
|
).toBe(true);
|
|
const foreign = await secondActor(ctx, false, ['coupons.manage']);
|
|
const empty = await ctx
|
|
.api()
|
|
.get('/api/v1/coupons')
|
|
.auth(foreign.token, { type: 'bearer' })
|
|
.expect(200);
|
|
expect(empty.body).toEqual([]);
|
|
await ctx
|
|
.api()
|
|
.patch('/api/v1/coupons/' + created.body.id + '/status')
|
|
.auth(foreign.token, { type: 'bearer' })
|
|
.send({ active: false })
|
|
.expect(404);
|
|
await ctx
|
|
.api()
|
|
.patch('/api/v1/coupons/' + created.body.id + '/status')
|
|
.auth(ctx.token, { type: 'bearer' })
|
|
.send({ active: false })
|
|
.expect(200);
|
|
await expect(
|
|
ctx.executeSql(
|
|
`UPDATE coupons SET percent_bps = 5000 WHERE id = '${created.body.id}'`,
|
|
),
|
|
).rejects.toThrow();
|
|
const f = await checkoutFixture(ctx);
|
|
await ctx
|
|
.api()
|
|
.post('/api/v1/checkout')
|
|
.auth(f.actor.token, { type: 'bearer' })
|
|
.send({ ...f.input, couponCode: created.body.code })
|
|
.expect(409);
|
|
});
|
|
it('caps fixed discounts at subtotal and rejects duplicate normalized codes', async () => {
|
|
const input = couponInput();
|
|
const { percentBps: _percent, ...common } = input;
|
|
const fixed = { ...common, kind: 'FIXED', amount: '9999.00' };
|
|
const coupon = await ctx
|
|
.api()
|
|
.post('/api/v1/coupons')
|
|
.auth(ctx.token, { type: 'bearer' })
|
|
.send(fixed)
|
|
.expect(201);
|
|
await ctx
|
|
.api()
|
|
.post('/api/v1/coupons')
|
|
.auth(ctx.token, { type: 'bearer' })
|
|
.send({ ...fixed, code: fixed.code.toLowerCase() })
|
|
.expect(409);
|
|
const f = await checkoutFixture(ctx);
|
|
const order = await ctx
|
|
.api()
|
|
.post('/api/v1/checkout')
|
|
.auth(f.actor.token, { type: 'bearer' })
|
|
.send({ ...f.input, couponCode: coupon.body.code })
|
|
.expect(201);
|
|
expect(order.body.merchandiseTotal).toBe('0');
|
|
expect(order.body.paymentAvailable).toBe(false);
|
|
await ctx
|
|
.api()
|
|
.patch('/api/v1/coupons/' + randomUUID() + '/status')
|
|
.auth(ctx.token, { type: 'bearer' })
|
|
.send({ active: false })
|
|
.expect(404);
|
|
});
|
|
});
|