* fix(admin): expose view/download counters in the admin photos list (#895 follow-up) st-ivan's re-test after #904: statistics panel and event summary now agree, but the per-image Engagement column still shows 0. Root cause: the admin photos LIST endpoint maps rows to an explicit response object that includes like/comment/rating/favorite counts but never included view_count or download_count — the grid reads photo.view_count ?? 0, so the column showed 0 regardless of what the DB counted. This mapper, not stale data, is also why per-image downloads always displayed 0 in the original report. Suite extended with a list-endpoint assertion (beacon + download, then the admin list reflects 1/1 and untouched photos 0/0). The skip test now neutralizes the route's background pre-zip build, whose async ENOENT against the intentionally missing file could land mid-suite. * test: widen the fire-and-forget settle window (#895 follow-up) The 100ms settle was marginal on loaded CI runners — the counter increments are deliberately fire-and-forget, and the 909 PRs flaked on exactly these assertions. 400ms keeps the suite fast while giving slow runners room. --------- Co-authored-by: Paul Nothaft <[email protected]>
This commit is contained in:
co-authored by
Paul Nothaft
parent
888150ba2d
commit
aca3c8e4bc
@@ -52,7 +52,7 @@ describe('photo engagement counters (#895)', () => {
|
||||
const getPhoto = async (id) => db('photos').where('id', id).first();
|
||||
// The counter writes are fire-and-forget on purpose — give the event
|
||||
// loop a beat before asserting.
|
||||
const settle = () => new Promise((r) => setTimeout(r, 100));
|
||||
const settle = () => new Promise((r) => setTimeout(r, 400));
|
||||
|
||||
beforeAll(async () => {
|
||||
({ db, cleanup } = await bootCrmDb());
|
||||
@@ -115,6 +115,7 @@ describe('photo engagement counters (#895)', () => {
|
||||
app.use(cookieParser());
|
||||
app.use('/api/gallery', require('../../src/routes/gallery'));
|
||||
app.use('/api/admin/events', require('../../src/routes/adminEvents'));
|
||||
app.use('/api/admin/photos', require('../../src/routes/adminPhotos'));
|
||||
}, 120000);
|
||||
|
||||
afterAll(async () => {
|
||||
@@ -200,6 +201,13 @@ describe('photo engagement counters (#895)', () => {
|
||||
// Own event so the on-the-fly archiver path is guaranteed — the
|
||||
// main event may have a cached zip from the previous test's
|
||||
// background generation, and racing its build/invalidate hangs.
|
||||
// The route also fires a background pre-zip build after streaming;
|
||||
// against this event's intentionally missing file it crashes with
|
||||
// an async ENOENT that jest attributes to whatever test is running
|
||||
// by then — neutralize it, it's not under test here.
|
||||
const downloadZipService = require('../../src/services/downloadZipService');
|
||||
const generateZipSpy = jest.spyOn(downloadZipService, 'generateZip')
|
||||
.mockResolvedValue({ success: false, error: 'disabled in test' });
|
||||
const slug2 = `${SLUG}-skip`;
|
||||
const ev = await db('events').insert({
|
||||
slug: slug2,
|
||||
@@ -248,6 +256,33 @@ describe('photo engagement counters (#895)', () => {
|
||||
expect((await db('photos').where('id', ids2[0]).first()).download_count).toBe(1);
|
||||
// photo-1's source was missing → skipped from the zip → not counted
|
||||
expect((await db('photos').where('id', ids2[1]).first()).download_count).toBe(0);
|
||||
generateZipSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('admin photos list exposes the counters (#895 follow-up)', () => {
|
||||
it('returns view_count and download_count so the Engagement column can render them', async () => {
|
||||
// The list mapper builds an explicit object — before this fix it
|
||||
// omitted both fields, so the admin table showed 0 forever even
|
||||
// though the DB counted correctly.
|
||||
await request(app)
|
||||
.post(`/api/gallery/${SLUG}/photo/${photoIds[0]}/view`)
|
||||
.set('Authorization', `Bearer ${galleryToken()}`);
|
||||
await request(app)
|
||||
.get(`/api/gallery/${SLUG}/download/${photoIds[0]}`)
|
||||
.set('Authorization', `Bearer ${galleryToken()}`);
|
||||
await settle();
|
||||
|
||||
const res = await request(app)
|
||||
.get(`/api/admin/photos/${eventId}/photos`)
|
||||
.set('Authorization', `Bearer ${adminToken}`);
|
||||
expect(res.status).toBe(200);
|
||||
const row = res.body.photos.find((p) => p.id === photoIds[0]);
|
||||
expect(row.view_count).toBe(1);
|
||||
expect(row.download_count).toBe(1);
|
||||
const untouched = res.body.photos.find((p) => p.id === photoIds[1]);
|
||||
expect(untouched.view_count).toBe(0);
|
||||
expect(untouched.download_count).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1091,7 +1091,13 @@ router.get('/:eventId/photos', adminAuth, requirePermission('photos.view'), requ
|
||||
average_rating: photo.average_rating || 0,
|
||||
comment_count: commentMap[photo.id] || 0,
|
||||
like_count: photo.like_count || 0,
|
||||
favorite_count: photo.favorite_count || 0
|
||||
favorite_count: photo.favorite_count || 0,
|
||||
// Engagement counters (#895 follow-up): the grid reads these, but
|
||||
// this explicit mapper never included them — so the Engagement
|
||||
// column showed 0 regardless of what the DB counted. This, not
|
||||
// stale data, was why per-image downloads always displayed 0.
|
||||
view_count: photo.view_count || 0,
|
||||
download_count: photo.download_count || 0
|
||||
}))
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user