Skip to content

Commit daf4b6d

Browse files
Merge pull request #257 from WebJamApps/claude/256-gig-venue-linked-validation
Gig create/update rejects venue-linked gigs (Invalid create gig data)
2 parents 634b044 + 0af7930 commit daf4b6d

4 files changed

Lines changed: 166 additions & 4 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "webjamsocketserver",
33
"description": "Uses latest version of socketcluster-server",
4-
"version": "3.0.11",
4+
"version": "3.0.12",
55
"license": "MIT",
66
"type": "module",
77
"main": "build/src/index.js",

src/AgController/index.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,11 @@ class AgController {
269269
try {
270270
const gig = receiver.value.gig ?? receiver.value.tour;
271271
await this.verifyAdminWrite(receiver.value.token);
272-
if (gig && gig.datetime && gig.city && gig.usState && gig.venue) {
272+
// A gig is identified by venueId (linked to a Venue doc) OR a non-empty
273+
// free-text venue (one-off gig) — #256. city/usState are legacy
274+
// display-only fields resolved from the linked venue and are no
275+
// longer required here.
276+
if (gig && gig.datetime && (gig.venueId || gig.venue)) {
273277
await utils.handleGig('createDocs', gig, 'gigCreated', this.gigController, this.server);
274278
} else throw new Error('Invalid create gig data');
275279
} catch (e) {
@@ -305,7 +309,10 @@ class AgController {
305309
try {
306310
const id = data.gigId ?? data.tourId;
307311
const gig = data.gig ?? data.tour ?? {};
308-
if (!gig.venue || !gig.datetime || !gig.city || !gig.usState) throw new Error('Invalid gig data');
312+
// Same rule as newGig (#256): a gig is identified by venueId OR a
313+
// non-empty free-text venue; city/usState are legacy display-only
314+
// fields resolved from the linked venue and are no longer required.
315+
if (!gig.datetime || !(gig.venueId || gig.venue)) throw new Error('Invalid gig data');
309316
r = await this.gigController.findByIdAndUpdate(id, gig);
310317
} catch (e) {
311318
// Rethrow (#253, same pattern as handleImage/JaMmusic#1199): swallowing

src/model/gig/gig-schema.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ const gigSchema = new Schema({
1313
location: { type: String, required: false },
1414
city: { type: String, required: false },
1515
usState: { type: String, required: false },
16-
venue: { type: String, required: true },
16+
// A gig is identified by venueId (linked) OR free-text venue (one-off);
17+
// neither is mandatory at the schema layer — AgController's newGig/updateGig
18+
// guards are the gate (#256).
19+
venue: { type: String, required: false },
1720
tickets: { type: String, required: false },
1821
duration: { type: Number, required: false, default: 0 },
1922
promoImageUrl: { type: String, required: false },

test/AgController/index.spec.ts

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,40 @@ describe('AgControler', () => {
280280
gig: {},
281281
})).rejects.toThrow('Invalid gig data');
282282
});
283+
it('updates a gig when venueId is set and venue/city/usState are empty strings (#256)', async () => {
284+
const agController = new AgController(aStub);
285+
agController.gigController.findByIdAndUpdate = vi.fn(() => Promise.resolve(true));
286+
r = await agController.updateGig({
287+
gigId: testId,
288+
gig: {
289+
venueId: testId, datetime: new Date(), venue: '', city: '', usState: '',
290+
},
291+
});
292+
expect(r).toBe('Gig updated');
293+
});
294+
it('updates a one-off gig with only free-text venue set and no venueId (#256)', async () => {
295+
const agController = new AgController(aStub);
296+
agController.gigController.findByIdAndUpdate = vi.fn(() => Promise.resolve(true));
297+
r = await agController.updateGig({
298+
gigId: testId,
299+
gig: { venue: 'The Local Bar', datetime: new Date() },
300+
});
301+
expect(r).toBe('Gig updated');
302+
});
303+
it('rejects updateGig when neither venueId nor venue is set (#256)', async () => {
304+
const agController = new AgController(aStub);
305+
await expect(agController.updateGig({
306+
gigId: testId,
307+
gig: { datetime: new Date() },
308+
})).rejects.toThrow('Invalid gig data');
309+
});
310+
it('rejects updateGig when datetime is missing even though venueId is set (#256)', async () => {
311+
const agController = new AgController(aStub);
312+
await expect(agController.updateGig({
313+
gigId: testId,
314+
gig: { venueId: testId },
315+
})).rejects.toThrow('Invalid gig data');
316+
});
283317
it('does not process the newTour message from client when token is not valid', async () => {
284318
const agController = new AgController(aStub);
285319
agController.clients = ['123'];
@@ -494,6 +528,124 @@ describe('AgControler', () => {
494528
{ newGig: 'Invalid create gig data' },
495529
);
496530
});
531+
it('creates a gig when venueId is set and venue/city/usState are empty strings (#256)', async () => {
532+
const agController = new AgController(aStub);
533+
agController.clients = ['123'];
534+
agController.gigController.createDocs = vi.fn(() => Promise.resolve([]));
535+
agController.verifyAdminWrite = vi.fn(() => Promise.resolve());
536+
const cStub:any = {
537+
socket: {
538+
id: '123',
539+
listener: () => ({ createConsumer: () => ({ next: () => Promise.resolve({ done: true, value: '1000' }) }) }),
540+
transmit: () => { },
541+
receiver: () => ({
542+
createConsumer: () => ({
543+
next: () => Promise.resolve({
544+
value: {
545+
token: 'token',
546+
gig: {
547+
venueId: testId, datetime: new Date(), venue: '', city: '', usState: '',
548+
},
549+
},
550+
done: true,
551+
}),
552+
}),
553+
}),
554+
},
555+
};
556+
const setIntervalMock:any = vi.fn((cb:any) => cb());
557+
global.setInterval = setIntervalMock;
558+
agController.newGig(cStub, 'newGig');
559+
await delay(1000);
560+
expect(agController.gigController.createDocs).toHaveBeenCalled();
561+
});
562+
it('creates a one-off gig with only free-text venue set and no venueId (#256)', async () => {
563+
const agController = new AgController(aStub);
564+
agController.clients = ['123'];
565+
agController.gigController.createDocs = vi.fn(() => Promise.resolve([]));
566+
agController.verifyAdminWrite = vi.fn(() => Promise.resolve());
567+
const cStub:any = {
568+
socket: {
569+
id: '123',
570+
listener: () => ({ createConsumer: () => ({ next: () => Promise.resolve({ done: true, value: '1000' }) }) }),
571+
transmit: () => { },
572+
receiver: () => ({
573+
createConsumer: () => ({
574+
next: () => Promise.resolve({
575+
value: {
576+
token: 'token',
577+
gig: { venue: 'The Local Bar', datetime: new Date() },
578+
},
579+
done: true,
580+
}),
581+
}),
582+
}),
583+
},
584+
};
585+
const setIntervalMock:any = vi.fn((cb:any) => cb());
586+
global.setInterval = setIntervalMock;
587+
agController.newGig(cStub, 'newGig');
588+
await delay(1000);
589+
expect(agController.gigController.createDocs).toHaveBeenCalled();
590+
});
591+
it('rejects newGig with neither venueId nor venue (#256)', async () => {
592+
const agController = new AgController(aStub);
593+
agController.clients = ['123'];
594+
agController.gigController.createDocs = vi.fn(() => Promise.resolve([]));
595+
agController.verifyAdminWrite = vi.fn(() => Promise.resolve());
596+
const eStub:any = {
597+
socket: {
598+
id: '123',
599+
listener: () => ({ createConsumer: () => ({ next: () => Promise.resolve({ done: true, value: '1000' }) }) }),
600+
transmit: vi.fn(),
601+
receiver: () => ({
602+
createConsumer: () => ({
603+
next: () => Promise.resolve({
604+
value: {
605+
token: 'token',
606+
gig: { datetime: new Date() },
607+
},
608+
done: true,
609+
}),
610+
}),
611+
}),
612+
},
613+
};
614+
const setIntervalMock:any = vi.fn((cb:any) => cb());
615+
global.setInterval = setIntervalMock;
616+
agController.newGig(eStub, 'newGig');
617+
await delay(1000);
618+
expect(eStub.socket.transmit).toHaveBeenCalledWith('socketError', { newGig: 'Invalid create gig data' });
619+
});
620+
it('rejects newGig when datetime is missing even though venueId is set (#256)', async () => {
621+
const agController = new AgController(aStub);
622+
agController.clients = ['123'];
623+
agController.gigController.createDocs = vi.fn(() => Promise.resolve([]));
624+
agController.verifyAdminWrite = vi.fn(() => Promise.resolve());
625+
const eStub:any = {
626+
socket: {
627+
id: '123',
628+
listener: () => ({ createConsumer: () => ({ next: () => Promise.resolve({ done: true, value: '1000' }) }) }),
629+
transmit: vi.fn(),
630+
receiver: () => ({
631+
createConsumer: () => ({
632+
next: () => Promise.resolve({
633+
value: {
634+
token: 'token',
635+
gig: { venueId: testId },
636+
},
637+
done: true,
638+
}),
639+
}),
640+
}),
641+
},
642+
};
643+
const setIntervalMock:any = vi.fn((cb:any) => cb());
644+
global.setInterval = setIntervalMock;
645+
agController.newGig(eStub, 'newGig');
646+
await delay(1000);
647+
expect(eStub.socket.transmit).toHaveBeenCalledWith('socketError', { newGig: 'Invalid create gig data' });
648+
});
497649
it('handles missing receiver value when process the newTour message from client', () => {
498650
const agController = new AgController(aStub);
499651
agController.clients = ['123'];

0 commit comments

Comments
 (0)