From 90f894c95c7aad15541b08367e4877839b28da5a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 11:07:26 +0000 Subject: [PATCH 1/7] chore(deps): Bump brace-expansion from 5.0.6 to 5.0.7 Bumps [brace-expansion](https://github.com/juliangruber/brace-expansion) from 5.0.6 to 5.0.7. - [Release notes](https://github.com/juliangruber/brace-expansion/releases) - [Commits](https://github.com/juliangruber/brace-expansion/compare/v5.0.6...v5.0.7) --- updated-dependencies: - dependency-name: brace-expansion dependency-version: 5.0.7 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 57fe906..e52e48c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1802,9 +1802,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" From 2a7afb0fe4ec558f0efa0ea9a3f4159524d3c0dc Mon Sep 17 00:00:00 2001 From: JoshuaVSherman Date: Wed, 22 Jul 2026 04:12:10 -0400 Subject: [PATCH 2/7] =?UTF-8?q?wip:=20rescue=20uncommitted=20gig=E2=86=94v?= =?UTF-8?q?enue=20feed-populate=20WIP=20(found=202026-07-22)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Preserves working-tree changes that were left uncommitted on `dev` since 2026-07-17 (no branch/issue/PR). Adds a `venueId` ObjectId ref to the gig schema plus a minimal Venue model, and makes gig find/findSort populate the linked venue's name/city/usState/website so the SocketCluster gig feed can carry venue details. Relates to the gig↔venue linkage effort (JaM#1220 render-time venue link, JaM#1242 Gigs Location column). INCOMPLETE for the current design: the venue schema here lacks a street `address` field that JaM#1242 needs. Not reviewed, not tested, no issue yet — parked pending decision. Co-Authored-By: Claude Opus 4.8 --- src/model/gig/gig-facade.ts | 10 ++++++++++ src/model/gig/gig-schema.ts | 5 +++++ src/model/venue/venue-schema.ts | 12 ++++++++++++ 3 files changed, 27 insertions(+) create mode 100644 src/model/venue/venue-schema.ts diff --git a/src/model/gig/gig-facade.ts b/src/model/gig/gig-facade.ts index 6799728..dc08117 100644 --- a/src/model/gig/gig-facade.ts +++ b/src/model/gig/gig-facade.ts @@ -2,7 +2,17 @@ import Model from '../../lib/facade.js'; import gigSchema from './gig-schema.js'; class GigModel extends Model { + async find(query: any): Promise { + let result; + try { result = await this.Schema.find(query).populate('venueId', 'name city usState website').lean().exec(); } catch (e) { return Promise.reject(e); } + return Promise.resolve(result); + } + async findSort(query: any, sort: any): Promise { + let result; + try { result = await this.Schema.find(query).sort(sort).populate('venueId', 'name city usState website').lean().exec(); } catch (e) { return Promise.reject(e); } + return Promise.resolve(result); + } } export default new GigModel(gigSchema); diff --git a/src/model/gig/gig-schema.ts b/src/model/gig/gig-schema.ts index 8925e71..e60539a 100644 --- a/src/model/gig/gig-schema.ts +++ b/src/model/gig/gig-schema.ts @@ -22,7 +22,12 @@ const gigSchema = new Schema({ // all pre-#237 records, which read as the default (josh) artist. Kept in // sync with web-jam-back's mirror of this schema. artist: { type: String, required: false }, + venueId: { + type: Schema.Types.ObjectId, ref: 'Venue', required: false, + }, }, options); +import '../venue/venue-schema.js'; + // Explicit collection name 'gigs' (a tours -> gigs migration moves the data). export default mongoose.models.Gig || mongoose.model('Gig', gigSchema, 'gigs'); diff --git a/src/model/venue/venue-schema.ts b/src/model/venue/venue-schema.ts new file mode 100644 index 0000000..6b0d002 --- /dev/null +++ b/src/model/venue/venue-schema.ts @@ -0,0 +1,12 @@ +import mongoose from '../db.js'; + +const { Schema } = mongoose; + +const venueSchema = new Schema({ + name: { type: String, required: true, trim: true }, + city: { type: String, required: false, trim: true }, + usState: { type: String, required: false, trim: true }, + website: { type: String, required: false, trim: true }, +}); + +export default mongoose.models.Venue || mongoose.model('Venue', venueSchema, 'venues'); From 393e82ad2a93b64e0843b3cedba02e868c66f9a4 Mon Sep 17 00:00:00 2001 From: JoshuaVSherman Date: Wed, 22 Jul 2026 04:16:18 -0400 Subject: [PATCH 3/7] chore: bump version to 3.0.9 for WIP PR (#244) Co-Authored-By: Claude Opus 4.8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fcfbff3..e93b2b3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "webjamsocketserver", "description": "Uses latest version of socketcluster-server", - "version": "3.0.8", + "version": "3.0.9", "license": "MIT", "type": "module", "main": "build/src/index.js", From 813d046996d35e738b526110edf6ce19773a42ef Mon Sep 17 00:00:00 2001 From: JoshuaVSherman Date: Fri, 24 Jul 2026 03:40:49 -0400 Subject: [PATCH 4/7] Fix lint, add venue address field, and test gig venue populate (#244) - Reformat gig-facade.ts try/catch blocks to multi-line so the populate() calls no longer trip max-len (fixes the RED lint gate). - Add a street `address` field to the Venue schema and include it in the gig find/findSort populate alongside name/city/usState/website, for JaM#1242's Gigs Location column. - Add gig-facade tests covering find/findSort populate behaviour (success returns a populated venue subdocument incl. address; failure propagates the rejection), matching the existing Facade test style. --- src/model/gig/gig-facade.ts | 14 ++++++++-- src/model/venue/venue-schema.ts | 1 + test/gig/gig-facade.test.ts | 47 +++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 test/gig/gig-facade.test.ts diff --git a/src/model/gig/gig-facade.ts b/src/model/gig/gig-facade.ts index dc08117..914e6ca 100644 --- a/src/model/gig/gig-facade.ts +++ b/src/model/gig/gig-facade.ts @@ -1,16 +1,26 @@ import Model from '../../lib/facade.js'; import gigSchema from './gig-schema.js'; +const venuePopulateFields = 'name address city usState website'; + class GigModel extends Model { async find(query: any): Promise { let result; - try { result = await this.Schema.find(query).populate('venueId', 'name city usState website').lean().exec(); } catch (e) { return Promise.reject(e); } + try { + result = await this.Schema.find(query).populate('venueId', venuePopulateFields).lean().exec(); + } catch (e) { + return Promise.reject(e); + } return Promise.resolve(result); } async findSort(query: any, sort: any): Promise { let result; - try { result = await this.Schema.find(query).sort(sort).populate('venueId', 'name city usState website').lean().exec(); } catch (e) { return Promise.reject(e); } + try { + result = await this.Schema.find(query).sort(sort).populate('venueId', venuePopulateFields).lean().exec(); + } catch (e) { + return Promise.reject(e); + } return Promise.resolve(result); } } diff --git a/src/model/venue/venue-schema.ts b/src/model/venue/venue-schema.ts index 6b0d002..32d6605 100644 --- a/src/model/venue/venue-schema.ts +++ b/src/model/venue/venue-schema.ts @@ -4,6 +4,7 @@ const { Schema } = mongoose; const venueSchema = new Schema({ name: { type: String, required: true, trim: true }, + address: { type: String, required: false, trim: true }, city: { type: String, required: false, trim: true }, usState: { type: String, required: false, trim: true }, website: { type: String, required: false, trim: true }, diff --git a/test/gig/gig-facade.test.ts b/test/gig/gig-facade.test.ts new file mode 100644 index 0000000..ad5dbee --- /dev/null +++ b/test/gig/gig-facade.test.ts @@ -0,0 +1,47 @@ +import gigFacade from '../../src/model/gig/gig-facade.js'; + +describe('GigModel', () => { + const populatedVenue = { + name: 'The Venue', address: '123 Main St', city: 'Salem', usState: 'OR', website: 'https://venue.example', + }; + + describe('find', () => { + it('returns a gig with venueId populated as a subdocument carrying name, city, usState, website, and address', async () => { + const gig = { _id: 'gig1', venueId: populatedVenue }; + (gigFacade as any).Schema = { + find: () => ({ populate: () => ({ lean: () => ({ exec: () => Promise.resolve([gig]) }) }) }), + }; + const result = await gigFacade.find({}); + expect(result).toEqual([gig]); + expect(result[0].venueId).toEqual(populatedVenue); + expect(result[0].venueId.address).toBe('123 Main St'); + }); + + it('propagates a rejection', async () => { + (gigFacade as any).Schema = { + find: () => ({ populate: () => ({ lean: () => ({ exec: () => Promise.reject(new Error('bad')) }) }) }), + }; + await expect(gigFacade.find({})).rejects.toThrow('bad'); + }); + }); + + describe('findSort', () => { + it('returns a gig with venueId populated as a subdocument carrying name, city, usState, website, and address', async () => { + const gig = { _id: 'gig1', venueId: populatedVenue }; + (gigFacade as any).Schema = { + find: () => ({ sort: () => ({ populate: () => ({ lean: () => ({ exec: () => Promise.resolve([gig]) }) }) }) }), + }; + const result = await gigFacade.findSort({}, {}); + expect(result).toEqual([gig]); + expect(result[0].venueId).toEqual(populatedVenue); + expect(result[0].venueId.address).toBe('123 Main St'); + }); + + it('propagates a rejection', async () => { + (gigFacade as any).Schema = { + find: () => ({ sort: () => ({ populate: () => ({ lean: () => ({ exec: () => Promise.reject(new Error('bad')) }) }) }) }), + }; + await expect(gigFacade.findSort({}, {})).rejects.toThrow('bad'); + }); + }); +}); From 761d215bee511f778ccdbe47ceb817af69dad3b4 Mon Sep 17 00:00:00 2001 From: JoshuaVSherman Date: Fri, 24 Jul 2026 04:34:55 -0400 Subject: [PATCH 5/7] Fix newGig receiver loop breaking permanently after one error (#246) The catch block in AgController.newGig() transmitted socketError then broke out of the receiver loop, permanently killing that client socket's newGig/newTour consumer after the first error. Sibling handlers (editDoc, removeGig) already transmit-and-continue; newGig now matches that behavior. Bump 3.0.8 -> 3.0.10 (skipping 3.0.9, already claimed by PR #245/#244). --- package-lock.json | 4 +-- package.json | 2 +- src/AgController/index.ts | 1 - test/AgController/index.spec.ts | 43 +++++++++++++++++++++++++++++++++ 4 files changed, 46 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index e52e48c..51172bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "webjamsocketserver", - "version": "3.0.8", + "version": "3.0.10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "webjamsocketserver", - "version": "3.0.8", + "version": "3.0.10", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index fcfbff3..8c83c4d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "webjamsocketserver", "description": "Uses latest version of socketcluster-server", - "version": "3.0.8", + "version": "3.0.10", "license": "MIT", "type": "module", "main": "build/src/index.js", diff --git a/src/AgController/index.ts b/src/AgController/index.ts index 87ebdf0..530f817 100644 --- a/src/AgController/index.ts +++ b/src/AgController/index.ts @@ -277,7 +277,6 @@ class AgController { const eMessage = (e as Error).message; debug(eMessage); client.socket.transmit('socketError', { newGig: eMessage });// send error back to client - break; } } })(); diff --git a/test/AgController/index.spec.ts b/test/AgController/index.spec.ts index f6a7b0d..b1df817 100644 --- a/test/AgController/index.spec.ts +++ b/test/AgController/index.spec.ts @@ -500,6 +500,49 @@ describe('AgControler', () => { agController.newGig(cStub, 'newGig'); expect(utils.handleGig).not.toHaveBeenCalled(); }); + it('keeps handling newGig on the same socket after an earlier newGig errored (#246)', async () => { + const agController = new AgController(aStub); + agController.clients = ['123']; + agController.gigController.createDocs = vi.fn(() => Promise.resolve([])); + agController.verifyAdminWrite = vi.fn(() => Promise.resolve()); + const transmit = vi.fn(); + let call = 0; + const cStub:any = { + socket: { + id: '123', + listener: () => ({ createConsumer: () => ({ next: () => Promise.resolve({ done: true, value: '1000' }) }) }), + transmit, + receiver: () => ({ + createConsumer: () => ({ + next: () => { + call += 1; + // First newGig on this socket: invalid gig data -> throws, must NOT break the loop. + if (call === 1) { + return Promise.resolve({ + value: { token: 'token', gig: { venue: 'venue' } }, + done: false, + }); + } + // Second newGig on the SAME socket/consumer: valid data -> must still be handled. + return Promise.resolve({ + value: { + token: 'token', + gig: { + venue: 'venue', datetime: new Date(), city: 'city', usState: 'state', + }, + }, + done: true, + }); + }, + }), + }), + }, + }; + agController.newGig(cStub, 'newGig'); + await delay(1000); + expect(transmit).toHaveBeenCalledWith('socketError', { newGig: 'Invalid create gig data' }); + expect(agController.gigController.createDocs).toHaveBeenCalled(); + }); it('process the newImage message from client', async () => { const agController = new AgController(aStub); agController.clients = ['123']; From 94ee046895a7bd6440788e37b65b68cdc6203eb7 Mon Sep 17 00:00:00 2001 From: JoshuaVSherman Date: Fri, 24 Jul 2026 09:22:32 -0400 Subject: [PATCH 6/7] =?UTF-8?q?chore:=20de-zmpox=20.env.example=20?= =?UTF-8?q?=E2=80=94=20point=20placeholder=20at=20neutral=20host=20(closes?= =?UTF-8?q?=20#240)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 60d4415..a1b7e5c 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,5 @@ -MONGO_DB_URI=mongodb+srv://username:password@orgname-zmpox.mongodb.net/databasename -TEST_DB=mongodb+srv://username:password@orgname-zmpox.mongodb.net/databasename +MONGO_DB_URI=mongodb+srv://username:password@orgname-xxxxx.mongodb.net/databasename +TEST_DB=mongodb+srv://username:password@orgname-xxxxx.mongodb.net/databasename GoogleClientSecret= HashString= AllowUrl={"urls": ["http://localhost:9000", "https://localhost:9000", "http://localhost:7000", "https://localhost:7000"]} From 1d6e45235dc99b18fe80a12be0fdfe512279cc0a Mon Sep 17 00:00:00 2001 From: JoshuaVSherman Date: Sat, 25 Jul 2026 04:51:37 -0400 Subject: [PATCH 7/7] Fix test-suite hang: newGig loop must check receiver.done even on error (#246) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CircleCI build 474 timed out in test/AgController/index.spec.ts. Root cause: after the earlier fix removed the error-path `break`, `newGig()`'s `if (receiver.done) break;` remained INSIDE the try block, right after the success path — so it was skipped whenever the catch fired. Several existing tests mock the receiver consumer to always resolve `{ ..., done: true }` on a payload that throws (invalid token/gig data); with the check unreachable on that path, those tests' un-awaited background loops spin forever, starving the event loop and hanging the whole suite. Fix: move the done-check to run unconditionally after the try/catch, matching the sibling editDoc/removeGig handlers. Production behavior is now correct too — the loop keeps serving newGig events after an error and still terminates cleanly when the client stream truly ends. Also fixed a latent test-pollution bug this hang had been masking: a test replaces the shared `utils.handleGig`/`utils.removeGig` exports with vi.fn() stubs and never restores them, which silently broke the #246 regression test's assertion that createDocs gets called on the second, valid newGig. Added restoration in afterEach. npm test: 10 test files / 86 tests passed, lint clean, no hang. --- src/AgController/index.ts | 2 +- test/AgController/index.spec.ts | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/AgController/index.ts b/src/AgController/index.ts index 530f817..690d893 100644 --- a/src/AgController/index.ts +++ b/src/AgController/index.ts @@ -272,12 +272,12 @@ class AgController { if (gig && gig.datetime && gig.city && gig.usState && gig.venue) { await utils.handleGig('createDocs', gig, 'gigCreated', this.gigController, this.server); } else throw new Error('Invalid create gig data'); - if (receiver.done) break; } catch (e) { const eMessage = (e as Error).message; debug(eMessage); client.socket.transmit('socketError', { newGig: eMessage });// send error back to client } + /* istanbul ignore else */if (receiver.done) break; } })(); } diff --git a/test/AgController/index.spec.ts b/test/AgController/index.spec.ts index b1df817..33c4510 100644 --- a/test/AgController/index.spec.ts +++ b/test/AgController/index.spec.ts @@ -30,8 +30,19 @@ const aStub:any = { }), }; +const realHandleGig = utils.handleGig; +const realRemoveGig = utils.removeGig; + describe('AgControler', () => { - afterEach(() => { vi.unstubAllGlobals(); }); + afterEach(() => { + vi.unstubAllGlobals(); + // Several tests replace these shared utils exports with vi.fn() stubs + // (e.g. line ~483) without restoring them, which otherwise leaks into + // later tests (like the #246 regression test) that need the real + // implementation and silently breaks their assertions. + utils.handleGig = realHandleGig; + utils.removeGig = realRemoveGig; + }); let r, clientStub:any = { id: '123', listener: () => ({ createConsumer: () => ({ next: () => Promise.resolve({ done: true, value: '1000' }) }) }),