Skip to content

Commit 539e6c4

Browse files
committed
fixed
1 parent 7b22fc4 commit 539e6c4

1 file changed

Lines changed: 63 additions & 26 deletions

File tree

apps/backend/src/__tests__/analytics.test.ts

Lines changed: 63 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const prismaMock = {
2020
followLog: {
2121
count: vi.fn(),
2222
},
23+
$queryRaw: vi.fn(),
2324
};
2425

2526
// ─── App factory ─────────────────────────────────────────────────────────────
@@ -89,19 +90,16 @@ describe(
8990
() => {
9091
let app: FastifyInstance;
9192

92-
beforeEach(
93-
async () => {
94-
vi.clearAllMocks();
93+
beforeEach(async () => {
94+
vi.clearAllMocks();
9595

96-
mockJwtVerify.mockResolvedValue(
97-
{
98-
id: MOCK_USER_ID,
99-
}
100-
);
96+
mockJwtVerify.mockResolvedValue({ id: MOCK_USER_ID });
10197

102-
app = await buildApp();
103-
}
104-
);
98+
// Default: $queryRaw for uniqueViewers returns 0
99+
prismaMock.$queryRaw.mockResolvedValue([{ count: BigInt(0) }]);
100+
101+
app = await buildApp();
102+
});
105103

106104
afterEach(
107105
async () => {
@@ -147,21 +145,8 @@ describe(
147145
]
148146
);
149147

150-
prismaMock.cardView.groupBy.mockResolvedValue(
151-
[
152-
{
153-
viewerId:
154-
'u1',
155-
viewerIp:
156-
null,
157-
},
158-
{
159-
viewerId:
160-
'u2',
161-
viewerIp:
162-
null,
163-
},
164-
]
148+
prismaMock.$queryRaw.mockResolvedValue(
149+
[{ count: BigInt(2) }]
165150
);
166151

167152
const res =
@@ -296,6 +281,58 @@ describe(
296281
);
297282
}
298283
);
284+
285+
it(
286+
'totalFollows counts rows by followerId (outbound follows), not by targetUsername',
287+
async () => {
288+
prismaMock.cardView.count
289+
.mockResolvedValueOnce(0)
290+
.mockResolvedValueOnce(0);
291+
292+
prismaMock.followLog.count.mockResolvedValue(3);
293+
prismaMock.cardView.findMany.mockResolvedValue([]);
294+
295+
const res = await app.inject({
296+
method: 'GET',
297+
url: '/api/analytics/overview',
298+
headers: authHeader(),
299+
});
300+
301+
expect(res.statusCode).toBe(200);
302+
expect(res.json().totalFollows).toBe(3);
303+
304+
// The query must use followerId, never targetUsername
305+
const followCountCall = prismaMock.followLog.count.mock.calls[0][0];
306+
expect(followCountCall).toMatchObject({
307+
where: {
308+
followerId: MOCK_USER_ID,
309+
status: 'success',
310+
},
311+
});
312+
expect(followCountCall.where).not.toHaveProperty('targetUsername');
313+
}
314+
);
315+
316+
it(
317+
'totalFollows is 0 when user has no successful outbound follows',
318+
async () => {
319+
prismaMock.cardView.count
320+
.mockResolvedValueOnce(50)
321+
.mockResolvedValueOnce(5);
322+
323+
prismaMock.followLog.count.mockResolvedValue(0);
324+
prismaMock.cardView.findMany.mockResolvedValue([]);
325+
326+
const res = await app.inject({
327+
method: 'GET',
328+
url: '/api/analytics/overview',
329+
headers: authHeader(),
330+
});
331+
332+
expect(res.statusCode).toBe(200);
333+
expect(res.json().totalFollows).toBe(0);
334+
}
335+
);
299336
}
300337
);
301338

0 commit comments

Comments
 (0)