- Colony end releases the queen in the same transaction; open suggestions become obsolete and a CLEANUP suggestion lists open tasks and the queen; apply-cleanup validates evidence and blocks reactivated colonies - Withdrawal chain: plausibility warnings on harvest and bottling, conflict notifications also when a treatment is entered or corrected after the harvest (naming bottled lots), withdrawal spans in the calendar and M365 sync, Berlin-day end events - Next 3 steps per colony and the "Heute" endpoint with ranking and an explicit merge table by intent - Inspection-triggered rule suggestions follow the colony's latest inspection (resolve when the finding is gone, refresh when it stays) - Frontend: colony card takeover in the inspection form, feed status as condition, occupied frames 0-40; legacy offline entries keep syncing - Notifications and preferences are deleted with their user Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
587 lines
19 KiB
TypeScript
587 lines
19 KiB
TypeScript
import { INestApplication } from '@nestjs/common';
|
|
import * as bcrypt from 'bcryptjs';
|
|
import request from 'supertest';
|
|
import { App } from 'supertest/types';
|
|
import { PrismaService } from '../src/prisma/prisma.service';
|
|
import { login } from './utils/api';
|
|
import { createTestApp } from './utils/create-test-app';
|
|
import { drainDomainEvents } from './utils/domain-events';
|
|
import { proposeForTest } from './utils/suggestions';
|
|
|
|
interface CleanupBody {
|
|
cancelled: number;
|
|
completed: number;
|
|
kept: number;
|
|
queenStatus: string | null;
|
|
}
|
|
|
|
interface EndedColony {
|
|
colonyId: string;
|
|
queenId: string;
|
|
taskIds: string[];
|
|
suggestionId: string;
|
|
}
|
|
|
|
/**
|
|
* A colony that ends (winter loss, edit to SOLD): the queen assignment ends
|
|
* in the same transaction, open suggestions turn OBSOLETE, one clean-up
|
|
* suggestion is left, and "Aufräumen" settles tasks and queen. Every
|
|
* assertion is on this file's own colonies.
|
|
*/
|
|
describe('Colony ended (e2e)', () => {
|
|
let app: INestApplication<App>;
|
|
let prisma: PrismaService;
|
|
const suffix = `${Date.now()}-${Math.round(Math.random() * 1e6)}`;
|
|
const password = 'SuperSecret123!';
|
|
const FULL_PERMISSIONS = [
|
|
'tasks:read',
|
|
'tasks:create',
|
|
'tasks:update',
|
|
'tasks:complete',
|
|
'queens:update',
|
|
'colonies:read',
|
|
'colonies:update',
|
|
'colonies:deactivate',
|
|
'winter-checks:read',
|
|
'winter-checks:create',
|
|
];
|
|
let apiaryId: string;
|
|
const colonyIds: string[] = [];
|
|
const queenIds: string[] = [];
|
|
const roleIds: string[] = [];
|
|
const userIds: string[] = [];
|
|
const tokens = { full: '', noComplete: '', noQueens: '' };
|
|
let counter = 0;
|
|
const nextKey = () => `colony-ended-${suffix}-${(counter += 1)}-key`;
|
|
|
|
const api = () => request(app.getHttpServer());
|
|
const auth = (token: string) => ({ Authorization: `Bearer ${token}` });
|
|
|
|
async function createUser(
|
|
label: string,
|
|
permissions: string[],
|
|
): Promise<string> {
|
|
const role = await prisma.role.create({
|
|
data: {
|
|
key: `COLONY_ENDED_${label.toUpperCase()}_${suffix}`,
|
|
name: `Colony ended ${label}`,
|
|
permissions: {
|
|
create: permissions.map((key) => ({
|
|
permission: { connect: { key } },
|
|
})),
|
|
},
|
|
},
|
|
});
|
|
roleIds.push(role.id);
|
|
const email = `colony-ended-${label}-${suffix}@example.com`;
|
|
const user = await prisma.user.create({
|
|
data: {
|
|
email,
|
|
passwordHash: await bcrypt.hash(password, 12),
|
|
name: `Colony ended ${label}`,
|
|
role: { connect: { id: role.id } },
|
|
},
|
|
});
|
|
userIds.push(user.id);
|
|
return (await login(app, email, password)).accessToken;
|
|
}
|
|
|
|
/** A living colony with an active queen assigned today and some open tasks. */
|
|
async function newColony(
|
|
label: string,
|
|
taskCount = 3,
|
|
): Promise<{ colonyId: string; queenId: string; taskIds: string[] }> {
|
|
const queen = await prisma.queen.create({
|
|
data: { code: `CE-Q-${label}-${suffix}`, status: 'ACTIVE' },
|
|
});
|
|
queenIds.push(queen.id);
|
|
const colony = await prisma.colony.create({
|
|
data: {
|
|
code: `CE-${label}-${suffix}`,
|
|
apiaryId,
|
|
colonyType: 'PRODUCTION_COLONY',
|
|
currentQueenId: queen.id,
|
|
},
|
|
});
|
|
colonyIds.push(colony.id);
|
|
await prisma.queenColonyHistory.create({
|
|
data: {
|
|
queenId: queen.id,
|
|
colonyId: colony.id,
|
|
startDate: new Date('2002-04-01T00:00:00.000Z'),
|
|
},
|
|
});
|
|
const taskIds: string[] = [];
|
|
for (let index = 0; index < taskCount; index += 1) {
|
|
const task = await prisma.task.create({
|
|
data: {
|
|
title: `Aufgabe ${index}`,
|
|
relatedType: 'COLONY',
|
|
relatedId: colony.id,
|
|
},
|
|
});
|
|
taskIds.push(task.id);
|
|
}
|
|
return { colonyId: colony.id, queenId: queen.id, taskIds };
|
|
}
|
|
|
|
/** Ends a colony through the edit route and returns its clean-up suggestion. */
|
|
async function endColony(label: string): Promise<EndedColony> {
|
|
const colony = await newColony(label);
|
|
await api()
|
|
.patch(`/api/v1/colonies/${colony.colonyId}`)
|
|
.set(auth(tokens.full))
|
|
.send({ status: 'SOLD' })
|
|
.expect(200);
|
|
await drainDomainEvents(app, [colony.colonyId]);
|
|
const suggestion = await prisma.suggestion.findFirstOrThrow({
|
|
where: { colonyId: colony.colonyId, intent: 'CLEANUP', status: 'OPEN' },
|
|
});
|
|
return { ...colony, suggestionId: suggestion.id };
|
|
}
|
|
|
|
const applyCleanup = (token: string, suggestionId: string, body: object) =>
|
|
api()
|
|
.post(`/api/v1/suggestions/${suggestionId}/apply-cleanup`)
|
|
.set(auth(token))
|
|
.set('Idempotency-Key', nextKey())
|
|
.send(body);
|
|
|
|
beforeAll(async () => {
|
|
app = await createTestApp();
|
|
prisma = app.get(PrismaService);
|
|
apiaryId = (
|
|
await prisma.apiary.create({ data: { name: `Colony ended ${suffix}` } })
|
|
).id;
|
|
tokens.full = await createUser('full', FULL_PERMISSIONS);
|
|
tokens.noQueens = await createUser(
|
|
'noqueens',
|
|
FULL_PERMISSIONS.filter((key) => key !== 'queens:update'),
|
|
);
|
|
tokens.noComplete = await createUser(
|
|
'nocomplete',
|
|
FULL_PERMISSIONS.filter((key) => key !== 'tasks:complete'),
|
|
);
|
|
});
|
|
|
|
afterAll(async () => {
|
|
const taskIds = (
|
|
await prisma.task.findMany({
|
|
where: { relatedId: { in: colonyIds } },
|
|
select: { id: true },
|
|
})
|
|
).map((row) => row.id);
|
|
await prisma.domainEvent.deleteMany({
|
|
where: { aggregateId: { in: [...taskIds, ...colonyIds, ...queenIds] } },
|
|
});
|
|
await prisma.calendarSyncOutboxEntry.deleteMany({
|
|
where: { sourceType: 'TASK', sourceId: { in: taskIds } },
|
|
});
|
|
await prisma.task.deleteMany({ where: { id: { in: taskIds } } });
|
|
await prisma.suggestion.deleteMany({
|
|
where: { colonyId: { in: colonyIds } },
|
|
});
|
|
await prisma.winterCheck.deleteMany({
|
|
where: { colonyId: { in: colonyIds } },
|
|
});
|
|
await prisma.queenColonyHistory.deleteMany({
|
|
where: { colonyId: { in: colonyIds } },
|
|
});
|
|
await prisma.colony.deleteMany({ where: { id: { in: colonyIds } } });
|
|
await prisma.queen.deleteMany({ where: { id: { in: queenIds } } });
|
|
await prisma.apiary.delete({ where: { id: apiaryId } });
|
|
await prisma.idempotencyRecord.deleteMany({
|
|
where: { userId: { in: userIds } },
|
|
});
|
|
await prisma.refreshToken.deleteMany({
|
|
where: { userId: { in: userIds } },
|
|
});
|
|
await prisma.auditLog.deleteMany({ where: { userId: { in: userIds } } });
|
|
await prisma.user.deleteMany({ where: { id: { in: userIds } } });
|
|
await prisma.role.deleteMany({ where: { id: { in: roleIds } } });
|
|
await app.close();
|
|
});
|
|
|
|
it('a winter loss with markColonyDeceased ends the queen assignment in the same transaction', async () => {
|
|
const { colonyId, queenId } = await newColony('winter', 0);
|
|
|
|
await api()
|
|
.post('/api/v1/winter-checks')
|
|
.set(auth(tokens.full))
|
|
.send({
|
|
colonyId,
|
|
season: 'SPRING',
|
|
date: '2002-03-20',
|
|
outcome: 'LOST',
|
|
lossCause: 'DEAD_OR_EMPTY',
|
|
markColonyDeceased: true,
|
|
})
|
|
.expect(201);
|
|
|
|
const colony = await prisma.colony.findUniqueOrThrow({
|
|
where: { id: colonyId },
|
|
});
|
|
expect(colony).toMatchObject({ status: 'DECEASED', currentQueenId: null });
|
|
const history = await prisma.queenColonyHistory.findMany({
|
|
where: { colonyId, queenId },
|
|
});
|
|
expect(history).toHaveLength(1);
|
|
expect(history[0].endDate?.toISOString()).toBe('2002-03-20T00:00:00.000Z');
|
|
// The queen herself keeps her status: the clean-up decides.
|
|
await expect(
|
|
prisma.queen.findUniqueOrThrow({ where: { id: queenId } }),
|
|
).resolves.toMatchObject({ status: 'ACTIVE' });
|
|
const ended = await prisma.domainEvent.findMany({
|
|
where: { aggregateId: colonyId, type: 'ColonyEnded' },
|
|
});
|
|
expect(ended.map((event) => event.payload)).toEqual([
|
|
{
|
|
status: 'DECEASED',
|
|
endedOn: '2002-03-20',
|
|
releasedQueenId: queenId,
|
|
via: 'WINTER_CHECK',
|
|
},
|
|
]);
|
|
});
|
|
|
|
it('open suggestions become OBSOLETE after draining and one CLEANUP suggestion is left', async () => {
|
|
const { colonyId, taskIds, queenId } = await newColony('obsolete', 2);
|
|
const open = await proposeForTest(app, {
|
|
ruleKey: 'INSPECTION_INTERVAL',
|
|
source: 'RULE',
|
|
mode: 'DO',
|
|
intent: 'INSPECT',
|
|
relatedType: 'COLONY',
|
|
relatedId: colonyId,
|
|
colonyId,
|
|
reasonCode: `COLONY_ENDED_E2E_${suffix}`,
|
|
evidence: { colonyId },
|
|
requiredPermission: 'inspections:read',
|
|
dueDate: '2026-05-12',
|
|
anchor: `ce-${suffix}`,
|
|
});
|
|
|
|
await api()
|
|
.patch(`/api/v1/colonies/${colonyId}`)
|
|
.set(auth(tokens.full))
|
|
.send({ status: 'DECEASED' })
|
|
.expect(200);
|
|
await drainDomainEvents(app, [colonyId]);
|
|
|
|
await expect(
|
|
prisma.suggestion.findUniqueOrThrow({ where: { id: open.id } }),
|
|
).resolves.toMatchObject({ status: 'OBSOLETE' });
|
|
const cleanups = await prisma.suggestion.findMany({
|
|
where: { colonyId, intent: 'CLEANUP' },
|
|
});
|
|
expect(cleanups).toHaveLength(1);
|
|
expect(cleanups[0]).toMatchObject({
|
|
status: 'OPEN',
|
|
mode: 'DO',
|
|
ruleKey: 'COLONY_ENDED_CLEANUP',
|
|
requiredPermission: 'colonies:read',
|
|
});
|
|
const evidence = cleanups[0].evidence as Record<string, unknown>;
|
|
expect(evidence).toMatchObject({
|
|
colonyId,
|
|
status: 'DECEASED',
|
|
via: 'EDIT',
|
|
queenId,
|
|
});
|
|
expect(evidence.openTaskIds).toEqual([...taskIds].sort());
|
|
const colony = await prisma.colony.findUniqueOrThrow({
|
|
where: { id: colonyId },
|
|
});
|
|
expect(colony.currentQueenId).toBeNull();
|
|
});
|
|
|
|
it('apply-cleanup cancels 2 tasks and completes 1, then the suggestion is RESOLVED', async () => {
|
|
const ended = await endColony('apply');
|
|
const [first, second, third] = [...ended.taskIds].sort();
|
|
|
|
const response = await applyCleanup(tokens.full, ended.suggestionId, {
|
|
tasks: [
|
|
{ id: first, outcome: 'CANCEL' },
|
|
{ id: second, outcome: 'CANCEL' },
|
|
{ id: third, outcome: 'DONE' },
|
|
],
|
|
queen: { id: ended.queenId, status: 'SOLD' },
|
|
}).expect(201);
|
|
|
|
expect(response.body as CleanupBody).toEqual({
|
|
cancelled: 2,
|
|
completed: 1,
|
|
kept: 0,
|
|
queenStatus: 'SOLD',
|
|
});
|
|
const tasks = await prisma.task.findMany({
|
|
where: { id: { in: ended.taskIds } },
|
|
orderBy: { id: 'asc' },
|
|
});
|
|
expect(tasks.map((task) => task.status)).toEqual([
|
|
'CANCELLED',
|
|
'CANCELLED',
|
|
'DONE',
|
|
]);
|
|
expect(tasks[2].completedAt).not.toBeNull();
|
|
await expect(
|
|
prisma.queen.findUniqueOrThrow({ where: { id: ended.queenId } }),
|
|
).resolves.toMatchObject({ status: 'SOLD' });
|
|
await drainDomainEvents(app, [first, second]);
|
|
for (const id of [first, second]) {
|
|
const updates = await prisma.domainEvent.findMany({
|
|
where: { aggregateId: id, type: 'TaskUpdated' },
|
|
include: { deliveries: true },
|
|
});
|
|
expect(updates).toHaveLength(1);
|
|
expect(updates[0].payload).toMatchObject({ status: 'CANCELLED' });
|
|
expect(updates[0].deliveries).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
handler: 'calendar-sync.task',
|
|
status: 'DONE',
|
|
}),
|
|
]),
|
|
);
|
|
}
|
|
const suggestion = await prisma.suggestion.findUniqueOrThrow({
|
|
where: { id: ended.suggestionId },
|
|
});
|
|
expect(suggestion).toMatchObject({
|
|
status: 'RESOLVED',
|
|
decisionNote: 'Aufgeräumt',
|
|
dedupeKey: null,
|
|
});
|
|
await expect(
|
|
prisma.auditLog.count({
|
|
where: {
|
|
entityType: 'Suggestion',
|
|
entityId: ended.suggestionId,
|
|
action: 'UPDATE',
|
|
},
|
|
}),
|
|
).resolves.toBe(1);
|
|
});
|
|
|
|
it('apply-cleanup DONE writes the audit entry and TaskCompleted like a normal completion (delivered after draining)', async () => {
|
|
const ended = await endColony('done');
|
|
const [first, ...rest] = [...ended.taskIds].sort();
|
|
|
|
await applyCleanup(tokens.full, ended.suggestionId, {
|
|
tasks: [
|
|
{ id: first, outcome: 'DONE' },
|
|
...rest.map((id) => ({ id, outcome: 'KEEP' })),
|
|
],
|
|
}).expect(201);
|
|
await drainDomainEvents(app, [first]);
|
|
|
|
const audit = await prisma.auditLog.findMany({
|
|
where: { entityType: 'Task', entityId: first, action: 'UPDATE' },
|
|
});
|
|
expect(audit).toHaveLength(1);
|
|
expect(audit[0].afterData).toMatchObject({ status: 'DONE' });
|
|
const completed = await prisma.domainEvent.findMany({
|
|
where: { aggregateId: first, type: 'TaskCompleted' },
|
|
include: { deliveries: true },
|
|
});
|
|
expect(completed).toHaveLength(1);
|
|
expect(completed[0].deliveries).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
handler: 'calendar-sync.task',
|
|
status: 'DONE',
|
|
}),
|
|
]),
|
|
);
|
|
const kept = await prisma.task.findMany({
|
|
where: { id: { in: rest } },
|
|
});
|
|
expect(kept.map((task) => task.status)).toEqual(['OPEN', 'OPEN']);
|
|
});
|
|
|
|
it('403 without tasks:complete when one task is DONE, and nothing changes', async () => {
|
|
const ended = await endColony('forbidden');
|
|
const [first] = [...ended.taskIds].sort();
|
|
|
|
await applyCleanup(tokens.noComplete, ended.suggestionId, {
|
|
tasks: [{ id: first, outcome: 'DONE' }],
|
|
}).expect(403);
|
|
|
|
await expect(
|
|
prisma.task.findUniqueOrThrow({ where: { id: first } }),
|
|
).resolves.toMatchObject({ status: 'OPEN' });
|
|
await expect(
|
|
prisma.suggestion.findUniqueOrThrow({
|
|
where: { id: ended.suggestionId },
|
|
}),
|
|
).resolves.toMatchObject({ status: 'OPEN' });
|
|
});
|
|
|
|
it('400 for a task id that is not in the evidence', async () => {
|
|
const ended = await endColony('foreign');
|
|
const other = await newColony('foreign-other', 1);
|
|
|
|
const response = await applyCleanup(tokens.full, ended.suggestionId, {
|
|
tasks: [{ id: other.taskIds[0], outcome: 'CANCEL' }],
|
|
}).expect(400);
|
|
|
|
expect((response.body as { message: string }).message).toBe(
|
|
'Nur Aufgaben und Königin aus dem Vorschlag.',
|
|
);
|
|
await expect(
|
|
prisma.task.findUniqueOrThrow({ where: { id: other.taskIds[0] } }),
|
|
).resolves.toMatchObject({ status: 'OPEN' });
|
|
});
|
|
|
|
it('409 when the suggestion was cleaned up already', async () => {
|
|
const ended = await endColony('twice');
|
|
|
|
await applyCleanup(tokens.full, ended.suggestionId, {
|
|
tasks: [],
|
|
}).expect(201);
|
|
const second = await applyCleanup(tokens.full, ended.suggestionId, {
|
|
tasks: [],
|
|
}).expect(409);
|
|
|
|
expect(second.body).toMatchObject({
|
|
message: 'Der Vorschlag ist nicht mehr offen.',
|
|
status: 'RESOLVED',
|
|
});
|
|
});
|
|
|
|
it('403 without queens:update when the queen is given, and nothing changes', async () => {
|
|
const ended = await endColony('noqueens');
|
|
|
|
await applyCleanup(tokens.noQueens, ended.suggestionId, {
|
|
tasks: [],
|
|
queen: { id: ended.queenId, status: 'SOLD' },
|
|
}).expect(403);
|
|
|
|
await expect(
|
|
prisma.queen.findUniqueOrThrow({ where: { id: ended.queenId } }),
|
|
).resolves.toMatchObject({ status: 'ACTIVE' });
|
|
});
|
|
|
|
it('400 for a queen id that is not in the evidence', async () => {
|
|
const ended = await endColony('foreignqueen');
|
|
const other = await newColony('foreignqueen-other', 0);
|
|
|
|
await applyCleanup(tokens.full, ended.suggestionId, {
|
|
tasks: [],
|
|
queen: { id: other.queenId, status: 'SOLD' },
|
|
}).expect(400);
|
|
|
|
await expect(
|
|
prisma.queen.findUniqueOrThrow({ where: { id: other.queenId } }),
|
|
).resolves.toMatchObject({ status: 'ACTIVE' });
|
|
});
|
|
|
|
it('400 when the evidence has no queen but the request names one', async () => {
|
|
const colony = await newColony('noqueenevidence');
|
|
await prisma.queen.update({
|
|
where: { id: colony.queenId },
|
|
data: { status: 'LOST' },
|
|
});
|
|
await api()
|
|
.patch(`/api/v1/colonies/${colony.colonyId}`)
|
|
.set(auth(tokens.full))
|
|
.send({ status: 'SOLD' })
|
|
.expect(200);
|
|
await drainDomainEvents(app, [colony.colonyId]);
|
|
const suggestion = await prisma.suggestion.findFirstOrThrow({
|
|
where: { colonyId: colony.colonyId, intent: 'CLEANUP', status: 'OPEN' },
|
|
});
|
|
expect((suggestion.evidence as { queenId: unknown }).queenId).toBeNull();
|
|
|
|
await applyCleanup(tokens.full, suggestion.id, {
|
|
tasks: [],
|
|
queen: { id: colony.queenId, status: 'SOLD' },
|
|
}).expect(400);
|
|
});
|
|
|
|
it('409 when a colony holds the queen again, and the whole clean-up rolls back', async () => {
|
|
const ended = await endColony('held');
|
|
const holder = await newColony('held-holder', 0);
|
|
await prisma.colony.update({
|
|
where: { id: holder.colonyId },
|
|
data: { currentQueenId: ended.queenId },
|
|
});
|
|
const [first] = [...ended.taskIds].sort();
|
|
|
|
await applyCleanup(tokens.full, ended.suggestionId, {
|
|
tasks: [{ id: first, outcome: 'CANCEL' }],
|
|
queen: { id: ended.queenId, status: 'DECEASED' },
|
|
}).expect(409);
|
|
|
|
await expect(
|
|
prisma.task.findUniqueOrThrow({ where: { id: first } }),
|
|
).resolves.toMatchObject({ status: 'OPEN' });
|
|
await expect(
|
|
prisma.suggestion.findUniqueOrThrow({
|
|
where: { id: ended.suggestionId },
|
|
}),
|
|
).resolves.toMatchObject({ status: 'OPEN' });
|
|
await expect(
|
|
prisma.queen.findUniqueOrThrow({ where: { id: ended.queenId } }),
|
|
).resolves.toMatchObject({ status: 'ACTIVE' });
|
|
});
|
|
|
|
it('reactivating the colony turns its open CLEANUP suggestion OBSOLETE', async () => {
|
|
const ended = await endColony('reactivate');
|
|
|
|
await api()
|
|
.post(`/api/v1/colonies/${ended.colonyId}/reactivate`)
|
|
.set(auth(tokens.full))
|
|
.expect(201);
|
|
|
|
await expect(
|
|
prisma.suggestion.findUniqueOrThrow({
|
|
where: { id: ended.suggestionId },
|
|
}),
|
|
).resolves.toMatchObject({ status: 'OBSOLETE' });
|
|
});
|
|
|
|
it('409 "Das Volk ist wieder aktiv." when the colony lives again but the suggestion is still open', async () => {
|
|
const ended = await endColony('living');
|
|
await prisma.colony.update({
|
|
where: { id: ended.colonyId },
|
|
data: { status: 'ACTIVE', deactivatedAt: null },
|
|
});
|
|
const [first] = [...ended.taskIds].sort();
|
|
|
|
const response = await applyCleanup(tokens.full, ended.suggestionId, {
|
|
tasks: [{ id: first, outcome: 'CANCEL' }],
|
|
}).expect(409);
|
|
|
|
expect((response.body as { message: string }).message).toBe(
|
|
'Das Volk ist wieder aktiv.',
|
|
);
|
|
await expect(
|
|
prisma.task.findUniqueOrThrow({ where: { id: first } }),
|
|
).resolves.toMatchObject({ status: 'OPEN' });
|
|
});
|
|
|
|
it('end, reactivate, end again the same day leaves one OBSOLETE and one OPEN CLEANUP suggestion', async () => {
|
|
const ended = await endColony('reend');
|
|
|
|
await api()
|
|
.post(`/api/v1/colonies/${ended.colonyId}/reactivate`)
|
|
.set(auth(tokens.full))
|
|
.expect(201);
|
|
await api()
|
|
.patch(`/api/v1/colonies/${ended.colonyId}`)
|
|
.set(auth(tokens.full))
|
|
.send({ status: 'DECEASED' })
|
|
.expect(200);
|
|
await drainDomainEvents(app, [ended.colonyId]);
|
|
|
|
const cleanups = await prisma.suggestion.findMany({
|
|
where: { colonyId: ended.colonyId, intent: 'CLEANUP' },
|
|
});
|
|
expect(cleanups.map((row) => row.status).sort()).toEqual([
|
|
'OBSOLETE',
|
|
'OPEN',
|
|
]);
|
|
});
|
|
});
|