main did not build, so every Portainer deploy failed with `npm run build` exit code 2. My regression from #241. findOrFail<T>(items: T[], predicate: (item: T) => boolean) infers T from both parameters. The call sites annotate the predicate as documentation, and the arrays come from .json(), which is any and offers no competing candidate — so T became the one-field shape written in the lambda and every caller failed on the field it actually wanted. Array.prototype.find has no such problem, which is why the code this replaced type-checked. The four responses are now typed at their call sites, so T is inferred from real data and the predicates need no annotation. findOrFail additionally takes NoInfer<T> on its predicate, so a stray annotation can never drive the element type again. The specs are better typed than before this change: `.json()` was plain any, and the annotations only ever documented a shape nothing enforced. I did not catch this because I verified with a bare `npx tsc --noEmit`, and tsconfig.json is `"include": ["src"]` — it structurally cannot see tests/. The specs are checked by the second command in `npm run build`, which is the step Docker runs and the step that failed. Verified this time with `npm run build` itself, plus lint, the unit suite, and two consecutive full e2e passes. Closes #254 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
104 lines
3.5 KiB
TypeScript
104 lines
3.5 KiB
TypeScript
import { test, expect, createItem, uniqueSuffix, findOrFail } from './fixtures';
|
|
|
|
test.describe('Disabling a customer account', () => {
|
|
test('an admin can disable an account and the customer is told at sign-in', async ({
|
|
page,
|
|
customer,
|
|
admin,
|
|
adminCustomers,
|
|
authModal
|
|
}) => {
|
|
await admin.open('Customers');
|
|
await expect(adminCustomers.row(customer.email)).toBeVisible();
|
|
await expect(adminCustomers.status(customer.email, 'ACTIVE')).toBeVisible();
|
|
|
|
await adminCustomers.disable(customer.email);
|
|
await expect(page.getByText('Account disabled')).toBeVisible();
|
|
await expect(adminCustomers.status(customer.email, 'DISABLED')).toBeVisible();
|
|
|
|
// A generic credential error would send a real customer round the
|
|
// password-reset loop forever.
|
|
await authModal.gotoLogIn();
|
|
await authModal.logIn(customer.email, customer.password);
|
|
await expect(page.getByText(/disabled/i)).toBeVisible();
|
|
});
|
|
|
|
test('an existing session stops working immediately', async ({
|
|
page,
|
|
request,
|
|
customer,
|
|
accountModal
|
|
}) => {
|
|
// Still signed in from registration, in this same browser context.
|
|
await accountModal.open();
|
|
await expect(accountModal.emailText(customer.email)).toBeVisible();
|
|
|
|
const customers: { id: number; email: string }[] = await (
|
|
await request.get('/api/admin/customers')
|
|
).json();
|
|
const id = findOrFail(
|
|
customers,
|
|
(c) => c.email === customer.email,
|
|
`the customer ${customer.email}`
|
|
).id;
|
|
await request.post(`/api/admin/customers/${id}/disable`);
|
|
|
|
// The cookie is unchanged, so this proves the server rejects it rather
|
|
// than the browser having discarded it.
|
|
await page.goto('/account');
|
|
await expect(page).toHaveURL(/\/login/);
|
|
});
|
|
|
|
test('re-enabling restores sign-in', async ({
|
|
page,
|
|
request,
|
|
customer,
|
|
admin,
|
|
adminCustomers,
|
|
authModal,
|
|
header
|
|
}) => {
|
|
const customers: { id: number; email: string }[] = await (
|
|
await request.get('/api/admin/customers')
|
|
).json();
|
|
const id = findOrFail(
|
|
customers,
|
|
(c) => c.email === customer.email,
|
|
`the customer ${customer.email}`
|
|
).id;
|
|
await request.post(`/api/admin/customers/${id}/disable`);
|
|
|
|
await admin.open('Customers');
|
|
await expect(adminCustomers.row(customer.email)).toBeVisible();
|
|
await adminCustomers.reEnable(customer.email);
|
|
await expect(page.getByText('Account re-enabled')).toBeVisible();
|
|
|
|
await authModal.gotoLogIn();
|
|
await authModal.logIn(customer.email, customer.password);
|
|
await header.waitForSignedIn();
|
|
});
|
|
|
|
test('the confirmation warns that held items will be released', async ({
|
|
page,
|
|
request,
|
|
customer,
|
|
admin,
|
|
adminCustomers
|
|
}) => {
|
|
const item = await createItem(request, { name: `Held ${uniqueSuffix()}`, price: '40' });
|
|
expect((await page.request.post(`/api/cart/items/${item.id}`)).status()).toBe(201);
|
|
|
|
await admin.open('Customers');
|
|
await expect(adminCustomers.row(customer.email)).toBeVisible();
|
|
await adminCustomers.disableButton(customer.email).click();
|
|
|
|
// The consequence has to be visible at the moment of the decision.
|
|
await expect(page.getByText(/1 reserved item/)).toBeVisible();
|
|
await adminCustomers.confirmDialog.getByRole('button', { name: 'Disable' }).click();
|
|
await expect(page.getByText('Account disabled')).toBeVisible();
|
|
|
|
const released = await (await request.get(`/api/items/${item.id}`)).json();
|
|
expect(released.status).toBe('available');
|
|
});
|
|
});
|