Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions apps/server/src/modules/event/__tests__/event.service.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

const prismaMock = vi.hoisted(() => ({
competitor: {
findFirst: vi.fn(),
update: vi.fn(),
findUnique: vi.fn(),
},
protocol: {
createMany: vi.fn(),
},
}));

const subscriptionMocks = vi.hoisted(() => ({
publishUpdatedCompetitor: vi.fn(),
publishUpdatedCompetitors: vi.fn(),
}));

vi.mock('../../../utils/context.js', () => ({
default: prismaMock,
}));

vi.mock('../../../utils/subscriptionUtils.js', () => subscriptionMocks);

import { changeCompetitorStatus } from '../event.service.js';

describe('event.service changeCompetitorStatus', () => {
beforeEach(() => {
prismaMock.competitor.findFirst.mockResolvedValue({
id: 7,
classId: 3,
status: 'Active',
lateStart: false,
card: null,
});
prismaMock.competitor.update.mockResolvedValue({});
prismaMock.competitor.findUnique.mockResolvedValue({
id: 7,
classId: 3,
status: 'Active',
lateStart: true,
class: {},
team: null,
});
prismaMock.protocol.createMany.mockResolvedValue({ count: 1 });
subscriptionMocks.publishUpdatedCompetitor.mockResolvedValue(undefined);
subscriptionMocks.publishUpdatedCompetitors.mockResolvedValue(undefined);
});

afterEach(() => {
vi.clearAllMocks();
});

it('logs late start changes even when the persisted status remains Active', async () => {
await changeCompetitorStatus('event-1', 7, 'START', 'LateStart', 11);

expect(prismaMock.competitor.update).toHaveBeenCalledWith({
where: { id: 7 },
data: { status: 'Active', lateStart: true },
});
expect(prismaMock.protocol.createMany).toHaveBeenCalledWith({
data: [
{
eventId: 'event-1',
competitorId: 7,
origin: 'START',
type: 'late_start_change',
previousValue: 'false',
newValue: 'true',
authorId: 11,
},
],
});
});
});
88 changes: 57 additions & 31 deletions apps/server/src/modules/event/event.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export const changeCompetitorStatus = async (eventId, competitorId, origin, stat
id: true,
classId: true,
status: true,
lateStart: true,
card: true,
},
});
Expand Down Expand Up @@ -55,22 +56,42 @@ export const changeCompetitorStatus = async (eventId, competitorId, origin, stat
throw new DatabaseError('Error updating competitor');
}

// Add record to protocol
try {
await prisma.protocol.create({
data: {
eventId: eventId,
competitorId: competitorId,
origin: origin,
type: 'status_change',
previousValue: dbResponseCompetitor.status,
newValue: competitorStatus,
authorId: userId,
},
const changes = [];

if (dbResponseCompetitor.status !== competitorStatus) {
changes.push({
type: 'status_change',
previousValue: dbResponseCompetitor.status,
newValue: competitorStatus,
});
} catch (err) {
console.error('Failed to update competitor:', err);
throw new DatabaseError('Error creating protocol record');
}

if (dbResponseCompetitor.lateStart !== lateStart) {
changes.push({
type: 'late_start_change',
previousValue: String(dbResponseCompetitor.lateStart),
newValue: String(lateStart),
});
}

// Add records to protocol only when the persisted values actually changed.
if (changes.length > 0) {
try {
await prisma.protocol.createMany({
data: changes.map(change => ({
eventId: eventId,
competitorId: competitorId,
origin: origin,
type: change.type,
previousValue: change.previousValue,
newValue: change.newValue,
authorId: userId,
})),
});
} catch (err) {
console.error('Failed to update competitor:', err);
throw new DatabaseError('Error creating protocol record');
}
}

// Select the current competitor from the database
Expand Down Expand Up @@ -263,17 +284,22 @@ export const updateCompetitor = async (eventId, competitorId, origin, updateData
externalId: 'external_id_change',
};

// Iterate over keys in updateData
// Iterate over keys in updateData, log only actual changes
Object.keys(updateData).forEach(key => {
if (keyToTypeMap[key]) {
const previousValue = dbResponseCompetitor[key];
const nextValue = updateData[key];
changes.push({
type: keyToTypeMap[key],
previousValue:
previousValue === null || previousValue === undefined ? null : previousValue.toString(),
newValue: nextValue === null || nextValue === undefined ? 'null' : nextValue.toString(),
});
const prevStr =
previousValue === null || previousValue === undefined ? null : previousValue.toString();
const nextStr =
nextValue === null || nextValue === undefined ? null : nextValue.toString();
if (prevStr !== nextStr) {
changes.push({
type: keyToTypeMap[key],
previousValue: prevStr,
newValue: nextStr ?? 'null',
});
}
}
});

Expand Down Expand Up @@ -303,24 +329,24 @@ export const updateCompetitor = async (eventId, competitorId, origin, updateData
throw new DatabaseError('Error updating competitor');
}

// Add record to protocol
try {
for (const change of changes) {
await prisma.protocol.create({
data: {
// Add records to protocol in a single batch insert
if (changes.length > 0) {
try {
await prisma.protocol.createMany({
data: changes.map(change => ({
eventId: eventId,
competitorId: parseInt(competitorId),
origin: origin,
type: change.type,
previousValue: change.previousValue,
newValue: change.newValue,
authorId: userId,
},
})),
});
} catch (err) {
console.error('Failed to update competitor:', err);
throw new DatabaseError('Error creating protocol record');
}
} catch (err) {
console.error('Failed to update competitor:', err);
throw new DatabaseError('Error creating protocol record');
}

// Select the current competitor from the database
Expand Down
Loading