Skip to content

Commit 88c7e18

Browse files
authored
hotfix: clean up published docs with deleted collections (hoppscotch#5624)
1 parent 217563e commit 88c7e18

File tree

4 files changed

+279
-15
lines changed

4 files changed

+279
-15
lines changed

packages/hoppscotch-backend/src/published-docs/published-docs.resolver.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,15 @@ export class PublishedDocsResolver {
3434

3535
@ResolveField(() => User, {
3636
description: 'Returns the creator of the published document',
37+
nullable: true,
3738
})
3839
async creator(@Parent() publishedDocs: PublishedDocs): Promise<User> {
3940
const creator = await this.publishedDocsService.getPublishedDocsCreator(
4041
publishedDocs.id,
4142
);
4243

4344
if (E.isLeft(creator)) throwErr(creator.left);
44-
return {
45-
...creator.right,
46-
currentGQLSession: JSON.stringify(creator.right.currentGQLSession),
47-
currentRESTSession: JSON.stringify(creator.right.currentRESTSession),
48-
};
45+
return creator.right;
4946
}
5047

5148
@ResolveField(() => PublishedDocsCollection, {

packages/hoppscotch-backend/src/published-docs/published-docs.service.spec.ts

Lines changed: 186 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ const user: User = {
4747
lastLoggedOn: currentTime,
4848
lastActiveOn: currentTime,
4949
createdOn: currentTime,
50-
currentGQLSession: JSON.stringify({}),
51-
currentRESTSession: JSON.stringify({}),
50+
currentGQLSession: {} as any,
51+
currentRESTSession: {} as any,
5252
};
5353

5454
const userPublishedDoc: DBPublishedDocs = {
@@ -179,6 +179,9 @@ describe('getPublishedDocByID', () => {
179179
describe('getAllUserPublishedDocs', () => {
180180
test('should return a list of user published documents with pagination', async () => {
181181
mockPrisma.publishedDocs.findMany.mockResolvedValueOnce([userPublishedDoc]);
182+
mockPrisma.userCollection.findMany.mockResolvedValueOnce([
183+
{ id: 'collection_1' },
184+
] as any);
182185

183186
const result = await publishedDocsService.getAllUserPublishedDocs(
184187
user.uid,
@@ -190,6 +193,7 @@ describe('getAllUserPublishedDocs', () => {
190193

191194
test('should return an empty array when no documents found', async () => {
192195
mockPrisma.publishedDocs.findMany.mockResolvedValueOnce([]);
196+
mockPrisma.userCollection.findMany.mockResolvedValueOnce([]);
193197

194198
const result = await publishedDocsService.getAllUserPublishedDocs(
195199
user.uid,
@@ -201,18 +205,104 @@ describe('getAllUserPublishedDocs', () => {
201205
test('should return paginated results correctly', async () => {
202206
const docs = [userPublishedDoc, { ...userPublishedDoc, id: 'pub_doc_3' }];
203207
mockPrisma.publishedDocs.findMany.mockResolvedValueOnce([docs[0]]);
208+
mockPrisma.userCollection.findMany.mockResolvedValueOnce([
209+
{ id: 'collection_1' },
210+
] as any);
204211

205212
const result = await publishedDocsService.getAllUserPublishedDocs(
206213
user.uid,
207214
{ skip: 0, take: 1 },
208215
);
209216
expect(result).toHaveLength(1);
210217
});
218+
219+
test('should filter out published docs with non-existent collections', async () => {
220+
const doc1 = {
221+
...userPublishedDoc,
222+
id: 'pub_doc_1',
223+
collectionID: 'collection_1',
224+
};
225+
const doc2 = {
226+
...userPublishedDoc,
227+
id: 'pub_doc_2',
228+
collectionID: 'collection_2',
229+
};
230+
const doc3 = {
231+
...userPublishedDoc,
232+
id: 'pub_doc_3',
233+
collectionID: 'collection_3',
234+
};
235+
236+
mockPrisma.publishedDocs.findMany.mockResolvedValueOnce([doc1, doc2, doc3]);
237+
// Only collection_1 and collection_3 exist
238+
mockPrisma.userCollection.findMany.mockResolvedValueOnce([
239+
{ id: 'collection_1' },
240+
{ id: 'collection_3' },
241+
] as any);
242+
243+
const result = await publishedDocsService.getAllUserPublishedDocs(
244+
user.uid,
245+
{ skip: 0, take: 10 },
246+
);
247+
248+
// Should only return docs with existing collections
249+
expect(result).toHaveLength(2);
250+
expect(result.map((d) => d.id)).toEqual(['pub_doc_1', 'pub_doc_3']);
251+
});
252+
253+
test('should delete published docs with non-existent collections', async () => {
254+
const doc1 = {
255+
...userPublishedDoc,
256+
id: 'pub_doc_1',
257+
collectionID: 'collection_1',
258+
};
259+
const doc2 = {
260+
...userPublishedDoc,
261+
id: 'pub_doc_2',
262+
collectionID: 'collection_deleted',
263+
};
264+
265+
mockPrisma.publishedDocs.findMany.mockResolvedValueOnce([doc1, doc2]);
266+
mockPrisma.userCollection.findMany.mockResolvedValueOnce([
267+
{ id: 'collection_1' },
268+
] as any);
269+
mockPrisma.publishedDocs.deleteMany.mockResolvedValueOnce({
270+
count: 1,
271+
} as any);
272+
273+
await publishedDocsService.getAllUserPublishedDocs(user.uid, {
274+
skip: 0,
275+
take: 10,
276+
});
277+
278+
expect(mockPrisma.publishedDocs.deleteMany).toHaveBeenCalledWith({
279+
where: {
280+
id: { in: ['pub_doc_2'] },
281+
},
282+
});
283+
});
284+
285+
test('should not call deleteMany when all collections exist', async () => {
286+
mockPrisma.publishedDocs.findMany.mockResolvedValueOnce([userPublishedDoc]);
287+
mockPrisma.userCollection.findMany.mockResolvedValueOnce([
288+
{ id: 'collection_1' },
289+
] as any);
290+
291+
await publishedDocsService.getAllUserPublishedDocs(user.uid, {
292+
skip: 0,
293+
take: 10,
294+
});
295+
296+
expect(mockPrisma.publishedDocs.deleteMany).not.toHaveBeenCalled();
297+
});
211298
});
212299

213300
describe('getAllTeamPublishedDocs', () => {
214301
test('should return a list of team published documents with pagination', async () => {
215302
mockPrisma.publishedDocs.findMany.mockResolvedValueOnce([teamPublishedDoc]);
303+
mockPrisma.teamCollection.findMany.mockResolvedValueOnce([
304+
{ id: 'team_collection_1' },
305+
] as any);
216306

217307
const result = await publishedDocsService.getAllTeamPublishedDocs(
218308
'team_1',
@@ -225,6 +315,7 @@ describe('getAllTeamPublishedDocs', () => {
225315

226316
test('should return an empty array when no team documents found', async () => {
227317
mockPrisma.publishedDocs.findMany.mockResolvedValueOnce([]);
318+
mockPrisma.teamCollection.findMany.mockResolvedValueOnce([]);
228319

229320
const result = await publishedDocsService.getAllTeamPublishedDocs(
230321
'team_1',
@@ -236,6 +327,9 @@ describe('getAllTeamPublishedDocs', () => {
236327

237328
test('should filter by teamID and collectionID correctly', async () => {
238329
mockPrisma.publishedDocs.findMany.mockResolvedValueOnce([teamPublishedDoc]);
330+
mockPrisma.teamCollection.findMany.mockResolvedValueOnce([
331+
{ id: 'team_collection_1' },
332+
] as any);
239333

240334
await publishedDocsService.getAllTeamPublishedDocs(
241335
'team_1',
@@ -256,6 +350,88 @@ describe('getAllTeamPublishedDocs', () => {
256350
},
257351
});
258352
});
353+
354+
test('should filter out published docs with non-existent team collections', async () => {
355+
const doc1 = {
356+
...teamPublishedDoc,
357+
id: 'pub_doc_1',
358+
collectionID: 'team_collection_1',
359+
};
360+
const doc2 = {
361+
...teamPublishedDoc,
362+
id: 'pub_doc_2',
363+
collectionID: 'team_collection_2',
364+
};
365+
const doc3 = {
366+
...teamPublishedDoc,
367+
id: 'pub_doc_3',
368+
collectionID: 'team_collection_3',
369+
};
370+
371+
mockPrisma.publishedDocs.findMany.mockResolvedValueOnce([doc1, doc2, doc3]);
372+
// Only team_collection_1 and team_collection_3 exist
373+
mockPrisma.teamCollection.findMany.mockResolvedValueOnce([
374+
{ id: 'team_collection_1' },
375+
{ id: 'team_collection_3' },
376+
] as any);
377+
378+
const result = await publishedDocsService.getAllTeamPublishedDocs(
379+
'team_1',
380+
undefined,
381+
{ skip: 0, take: 10 },
382+
);
383+
384+
// Should only return docs with existing collections
385+
expect(result).toHaveLength(2);
386+
expect(result.map((d) => d.id)).toEqual(['pub_doc_1', 'pub_doc_3']);
387+
});
388+
389+
test('should delete published docs with non-existent team collections', async () => {
390+
const doc1 = {
391+
...teamPublishedDoc,
392+
id: 'pub_doc_1',
393+
collectionID: 'team_collection_1',
394+
};
395+
const doc2 = {
396+
...teamPublishedDoc,
397+
id: 'pub_doc_2',
398+
collectionID: 'team_collection_deleted',
399+
};
400+
401+
mockPrisma.publishedDocs.findMany.mockResolvedValueOnce([doc1, doc2]);
402+
mockPrisma.teamCollection.findMany.mockResolvedValueOnce([
403+
{ id: 'team_collection_1' },
404+
] as any);
405+
mockPrisma.publishedDocs.deleteMany.mockResolvedValueOnce({
406+
count: 1,
407+
} as any);
408+
409+
await publishedDocsService.getAllTeamPublishedDocs('team_1', undefined, {
410+
skip: 0,
411+
take: 10,
412+
});
413+
414+
expect(mockPrisma.publishedDocs.deleteMany).toHaveBeenCalledWith({
415+
where: {
416+
id: { in: ['pub_doc_2'] },
417+
},
418+
});
419+
});
420+
421+
test('should not call deleteMany when all team collections exist', async () => {
422+
mockPrisma.publishedDocs.findMany.mockResolvedValueOnce([teamPublishedDoc]);
423+
mockPrisma.teamCollection.findMany.mockResolvedValueOnce([
424+
{ id: 'team_collection_1' },
425+
] as any);
426+
427+
await publishedDocsService.getAllTeamPublishedDocs(
428+
'team_1',
429+
'team_collection_1',
430+
{ skip: 0, take: 10 },
431+
);
432+
433+
expect(mockPrisma.publishedDocs.deleteMany).not.toHaveBeenCalled();
434+
});
259435
});
260436

261437
describe('createPublishedDoc', () => {
@@ -650,7 +826,14 @@ describe('getPublishedDocsCreator', () => {
650826
const result = await publishedDocsService.getPublishedDocsCreator(
651827
userPublishedDoc.id,
652828
);
653-
expect(result).toEqualRight(user);
829+
830+
const expectedUser = {
831+
...user,
832+
currentGQLSession: JSON.stringify(user.currentGQLSession),
833+
currentRESTSession: JSON.stringify(user.currentRESTSession),
834+
};
835+
836+
expect(result).toEqualRight(expectedUser);
654837
});
655838

656839
test('should throw PUBLISHED_DOCS_NOT_FOUND when document ID is invalid', async () => {

0 commit comments

Comments
 (0)