Skip to content

Commit b2ba964

Browse files
committed
feat: update includeInherited guestbooks queryparam
1 parent 8c66e14 commit b2ba964

6 files changed

Lines changed: 226 additions & 21 deletions

File tree

src/guestbooks/domain/repositories/IGuestbooksRepository.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ export interface IGuestbooksRepository {
1010
getGuestbook(guestbookId: number): Promise<Guestbook>
1111
getGuestbooksByCollectionId(
1212
collectionIdOrAlias: number | string,
13-
includeStats?: boolean
13+
includeStats?: boolean,
14+
includeInherited?: boolean
1415
): Promise<Guestbook[]>
1516
getGuestbookResponsesByDataverseId(
1617
dataverseId: number | string,

src/guestbooks/domain/useCases/GetGuestbooksByCollectionId.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,22 @@ export class GetGuestbooksByCollectionId implements UseCase<Guestbook[]> {
1010
*
1111
* @param {number | string} collectionIdOrAlias - Collection identifier (numeric id or alias).
1212
* @param {boolean} [includeStats=false] - Include usage and response counts for each guestbook.
13+
* @param {boolean} [includeInherited=false] - Include guestbooks inherited from hierarchical owners.
1314
* @returns {Promise<Guestbook[]>}
1415
*/
15-
async execute(collectionIdOrAlias: number | string, includeStats = false): Promise<Guestbook[]> {
16-
if (!includeStats) {
16+
async execute(
17+
collectionIdOrAlias: number | string,
18+
includeStats = false,
19+
includeInherited = false
20+
): Promise<Guestbook[]> {
21+
if (!includeStats && !includeInherited) {
1722
return await this.guestbooksRepository.getGuestbooksByCollectionId(collectionIdOrAlias)
1823
}
1924

2025
return await this.guestbooksRepository.getGuestbooksByCollectionId(
2126
collectionIdOrAlias,
22-
includeStats
27+
includeStats,
28+
includeInherited
2329
)
2430
}
2531
}

src/guestbooks/infra/repositories/GuestbooksRepository.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,18 @@ export class GuestbooksRepository extends ApiRepository implements IGuestbooksRe
3636

3737
public async getGuestbooksByCollectionId(
3838
collectionIdOrAlias: number | string,
39-
includeStats = false
39+
includeStats = false,
40+
includeInherited = false
4041
): Promise<Guestbook[]> {
42+
const queryParams = {
43+
...(includeStats ? { includeStats } : {}),
44+
...(includeInherited ? { includeInherited } : {})
45+
}
46+
4147
return this.doGet(
4248
this.buildApiEndpoint(this.guestbooksResourceName, `${collectionIdOrAlias}/list`),
4349
true,
44-
includeStats ? { includeStats } : {}
50+
queryParams
4551
)
4652
.then((response) => response.data.data as Guestbook[])
4753
.catch((error) => {
@@ -53,10 +59,12 @@ export class GuestbooksRepository extends ApiRepository implements IGuestbooksRe
5359
dataverseId: number | string,
5460
guestbookId?: number
5561
): Promise<GuestbookResponse[]> {
56-
const endpoint = `/${this.dataversesResourceName}/${dataverseId}/guestbookResponses`
57-
const queryParams = guestbookId === undefined ? {} : { guestbookId }
62+
const endpoint = this.buildApiEndpoint(
63+
this.dataversesResourceName,
64+
`${dataverseId}/guestbookResponses`
65+
)
5866

59-
return this.doGet(endpoint, true, queryParams)
67+
return this.doGet(endpoint, true, guestbookId ? { guestbookId } : {})
6068
.then((response) => response.data.data as GuestbookResponse[])
6169
.catch((error) => {
6270
throw error
@@ -67,10 +75,12 @@ export class GuestbooksRepository extends ApiRepository implements IGuestbooksRe
6775
dataverseId: number | string,
6876
guestbookId?: number
6977
): Promise<string> {
70-
const endpoint = `/${this.dataversesResourceName}/${dataverseId}/guestbookResponses`
71-
const queryParams = guestbookId === undefined ? {} : { guestbookId }
78+
const endpoint = this.buildApiEndpoint(
79+
this.dataversesResourceName,
80+
`${dataverseId}/guestbookResponses`
81+
)
7282

73-
return this.doGet(endpoint, true, queryParams)
83+
return this.doGet(endpoint, true, guestbookId ? { guestbookId } : {})
7484
.then((response) => response.data as string)
7585
.catch((error) => {
7686
throw error

test/integration/guestbooks/GuestbooksRepository.test.ts

Lines changed: 130 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,10 @@ describe('GuestbooksRepository', () => {
119119
createdGuestbookId = await sut.createGuestbook(testCollectionId, createGuestbookDTO)
120120
const actual = await sut.getGuestbooksByCollectionId(testCollectionId)
121121
expect(actual.length).toBeGreaterThan(0)
122-
expect(actual.some((guestbook) => guestbook.id === createdGuestbookId)).toBe(true)
122+
const createdGuestbook = actual.find((guestbook) => guestbook.id === createdGuestbookId)
123+
expect(createdGuestbook).toBeDefined()
124+
expect(createdGuestbook?.usageCount).toBeUndefined()
125+
expect(createdGuestbook?.responseCount).toBeUndefined()
123126
})
124127

125128
test('should list guestbooks for collection by collection alias', async () => {
@@ -220,6 +223,132 @@ describe('GuestbooksRepository', () => {
220223
}
221224
})
222225

226+
test('should include hierarchical owner guestbooks when includeInherited is true', async () => {
227+
const uniqueSuffix = Date.now().toString()
228+
const childCollectionAlias = `testGuestbooksInheritedChild${uniqueSuffix}`
229+
const parentGuestbookName = `parent inherited guestbook ${uniqueSuffix}`
230+
const childGuestbookName = `child inherited guestbook ${uniqueSuffix}`
231+
232+
let childCollectionId: number | undefined
233+
234+
try {
235+
await createCollectionViaApi(childCollectionAlias, testCollectionAlias).then(
236+
(collectionPayload: CollectionPayload) => (childCollectionId = collectionPayload.id)
237+
)
238+
await publishCollectionViaApi(childCollectionAlias)
239+
240+
const parentGuestbookId = await sut.createGuestbook(testCollectionAlias, {
241+
...createGuestbookDTO,
242+
name: parentGuestbookName,
243+
customQuestions: []
244+
})
245+
const childGuestbookId = await sut.createGuestbook(childCollectionAlias, {
246+
...createGuestbookDTO,
247+
name: childGuestbookName,
248+
customQuestions: []
249+
})
250+
251+
const withoutInherited = await sut.getGuestbooksByCollectionId(childCollectionAlias)
252+
const withInherited = await sut.getGuestbooksByCollectionId(
253+
childCollectionAlias,
254+
false,
255+
true
256+
)
257+
258+
expect(childCollectionId).toBeDefined()
259+
expect(withoutInherited.some((guestbook) => guestbook.id === childGuestbookId)).toBe(true)
260+
expect(withoutInherited.some((guestbook) => guestbook.id === parentGuestbookId)).toBe(false)
261+
262+
expect(withInherited.some((guestbook) => guestbook.id === childGuestbookId)).toBe(true)
263+
expect(withInherited.some((guestbook) => guestbook.id === parentGuestbookId)).toBe(true)
264+
265+
const inheritedGuestbook = withInherited.find(
266+
(guestbook) => guestbook.id === parentGuestbookId
267+
)
268+
expect(inheritedGuestbook?.name).toBe(parentGuestbookName)
269+
} finally {
270+
if (childCollectionId !== undefined) {
271+
await deleteCollectionViaApi(childCollectionAlias)
272+
}
273+
}
274+
})
275+
276+
test('should not include hierarchical owner guestbooks when includeInherited is false', async () => {
277+
const uniqueSuffix = Date.now().toString()
278+
const childCollectionAlias = `testGuestbooksNoInheritedChild${uniqueSuffix}`
279+
const parentGuestbookName = `parent non inherited guestbook ${uniqueSuffix}`
280+
const childGuestbookName = `child non inherited guestbook ${uniqueSuffix}`
281+
282+
let childCollectionId: number | undefined
283+
284+
try {
285+
await createCollectionViaApi(childCollectionAlias, testCollectionAlias).then(
286+
(collectionPayload: CollectionPayload) => (childCollectionId = collectionPayload.id)
287+
)
288+
await publishCollectionViaApi(childCollectionAlias)
289+
290+
const parentGuestbookId = await sut.createGuestbook(testCollectionAlias, {
291+
...createGuestbookDTO,
292+
name: parentGuestbookName,
293+
customQuestions: []
294+
})
295+
const childGuestbookId = await sut.createGuestbook(childCollectionAlias, {
296+
...createGuestbookDTO,
297+
name: childGuestbookName,
298+
customQuestions: []
299+
})
300+
301+
const withoutInherited = await sut.getGuestbooksByCollectionId(
302+
childCollectionAlias,
303+
false,
304+
false
305+
)
306+
307+
expect(childCollectionId).toBeDefined()
308+
expect(withoutInherited.some((guestbook) => guestbook.id === childGuestbookId)).toBe(true)
309+
expect(withoutInherited.some((guestbook) => guestbook.id === parentGuestbookId)).toBe(false)
310+
} finally {
311+
if (childCollectionId !== undefined) {
312+
await deleteCollectionViaApi(childCollectionAlias)
313+
}
314+
}
315+
})
316+
317+
test('should return inherited guestbooks for unpublished child collection when includeInherited is true', async () => {
318+
const uniqueSuffix = Date.now().toString()
319+
const unpublishedChildCollectionAlias = `testGuestbooksUnpublishedChild${uniqueSuffix}`
320+
const parentGuestbookName = `parent unpublished child guestbook ${uniqueSuffix}`
321+
let childCollectionId: number | undefined
322+
let parentGuestbookId: number | undefined
323+
324+
try {
325+
await createCollectionViaApi(unpublishedChildCollectionAlias, testCollectionAlias).then(
326+
(collectionPayload: CollectionPayload) => (childCollectionId = collectionPayload.id)
327+
)
328+
329+
parentGuestbookId = await sut.createGuestbook(testCollectionAlias, {
330+
...createGuestbookDTO,
331+
name: parentGuestbookName,
332+
customQuestions: []
333+
})
334+
335+
const actual = await sut.getGuestbooksByCollectionId(
336+
unpublishedChildCollectionAlias,
337+
false,
338+
true
339+
)
340+
341+
expect(childCollectionId).toBeDefined()
342+
expect(parentGuestbookId).toBeDefined()
343+
expect(actual.some((guestbook) => guestbook.id === parentGuestbookId)).toBe(true)
344+
expect(actual.some((guestbook) => guestbook.name === parentGuestbookName)).toBe(true)
345+
} finally {
346+
if (childCollectionId !== undefined) {
347+
await deleteCollectionViaApi(unpublishedChildCollectionAlias)
348+
}
349+
}
350+
})
351+
223352
test('should return error when collection does not exist', async () => {
224353
await expect(sut.getGuestbooksByCollectionId(999999)).rejects.toThrow(ReadError)
225354
})

test/unit/guestbooks/GetGuestbooksByCollectionId.test.ts

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,21 @@ import { IGuestbooksRepository } from '../../../src/guestbooks/domain/repositori
44
import { GetGuestbooksByCollectionId } from '../../../src/guestbooks/domain/useCases/GetGuestbooksByCollectionId'
55

66
describe('GetGuestbooksByCollectionId', () => {
7-
const guestbooks: Guestbook[] = [
7+
const guestbooksWithoutStats: Guestbook[] = [
8+
{
9+
id: 12,
10+
name: 'test',
11+
enabled: true,
12+
emailRequired: true,
13+
nameRequired: true,
14+
institutionRequired: false,
15+
positionRequired: false,
16+
customQuestions: [],
17+
createTime: '2024-01-01T00:00:00Z',
18+
dataverseId: 10
19+
}
20+
]
21+
const guestbooksWithStats: Guestbook[] = [
822
{
923
id: 12,
1024
name: 'test',
@@ -24,24 +38,48 @@ describe('GetGuestbooksByCollectionId', () => {
2438

2539
test('should return guestbooks for collection', async () => {
2640
const repository: IGuestbooksRepository = {} as IGuestbooksRepository
27-
repository.getGuestbooksByCollectionId = jest.fn().mockResolvedValue(guestbooks)
41+
repository.getGuestbooksByCollectionId = jest.fn().mockResolvedValue(guestbooksWithoutStats)
2842

2943
const sut = new GetGuestbooksByCollectionId(repository)
3044
const actual = await sut.execute(collectionId)
3145

3246
expect(repository.getGuestbooksByCollectionId).toHaveBeenCalledWith(collectionId)
33-
expect(actual).toEqual(guestbooks)
47+
expect(actual).toEqual(guestbooksWithoutStats)
48+
expect(actual[0].usageCount).toBeUndefined()
49+
expect(actual[0].responseCount).toBeUndefined()
3450
})
3551

3652
test('should request guestbooks with stats when includeStats is true', async () => {
3753
const repository: IGuestbooksRepository = {} as IGuestbooksRepository
38-
repository.getGuestbooksByCollectionId = jest.fn().mockResolvedValue(guestbooks)
54+
repository.getGuestbooksByCollectionId = jest.fn().mockResolvedValue(guestbooksWithStats)
3955

4056
const sut = new GetGuestbooksByCollectionId(repository)
4157
const actual = await sut.execute(collectionId, true)
4258

43-
expect(repository.getGuestbooksByCollectionId).toHaveBeenCalledWith(collectionId, true)
44-
expect(actual).toEqual(guestbooks)
59+
expect(repository.getGuestbooksByCollectionId).toHaveBeenCalledWith(collectionId, true, false)
60+
expect(actual).toEqual(guestbooksWithStats)
61+
})
62+
63+
test('should request guestbooks with inherited guestbooks when includeInherited is true', async () => {
64+
const repository: IGuestbooksRepository = {} as IGuestbooksRepository
65+
repository.getGuestbooksByCollectionId = jest.fn().mockResolvedValue(guestbooksWithoutStats)
66+
67+
const sut = new GetGuestbooksByCollectionId(repository)
68+
const actual = await sut.execute(collectionId, false, true)
69+
70+
expect(repository.getGuestbooksByCollectionId).toHaveBeenCalledWith(collectionId, false, true)
71+
expect(actual).toEqual(guestbooksWithoutStats)
72+
})
73+
74+
test('should request guestbooks with options object when includeInherited is true', async () => {
75+
const repository: IGuestbooksRepository = {} as IGuestbooksRepository
76+
repository.getGuestbooksByCollectionId = jest.fn().mockResolvedValue(guestbooksWithoutStats)
77+
78+
const sut = new GetGuestbooksByCollectionId(repository)
79+
const actual = await sut.execute(collectionId, undefined, true)
80+
81+
expect(repository.getGuestbooksByCollectionId).toHaveBeenCalledWith(collectionId, false, true)
82+
expect(actual).toEqual(guestbooksWithoutStats)
4583
})
4684

4785
test('should throw ReadError when repository fails', async () => {

test/unit/guestbooks/GuestbooksRepository.test.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,25 @@ import { TestConstants } from '../../testHelpers/TestConstants'
1010
describe('GuestbooksRepository', () => {
1111
const sut = new GuestbooksRepository()
1212
const collectionIdOrAlias = 'collectionAlias'
13+
const guestbooksResponseWithoutStats = {
14+
data: {
15+
status: 'OK',
16+
data: [
17+
{
18+
id: 12,
19+
name: 'test',
20+
enabled: true,
21+
emailRequired: true,
22+
nameRequired: true,
23+
institutionRequired: false,
24+
positionRequired: false,
25+
customQuestions: [],
26+
createTime: '2024-01-01T00:00:00Z',
27+
dataverseId: 10
28+
}
29+
]
30+
}
31+
}
1332
const guestbooksResponse = {
1433
data: {
1534
status: 'OK',
@@ -59,15 +78,17 @@ describe('GuestbooksRepository', () => {
5978

6079
describe('getGuestbooksByCollectionId', () => {
6180
test('should list guestbooks without stats by default', async () => {
62-
jest.spyOn(axios, 'get').mockResolvedValue(guestbooksResponse)
81+
jest.spyOn(axios, 'get').mockResolvedValue(guestbooksResponseWithoutStats)
6382

6483
const actual = await sut.getGuestbooksByCollectionId(collectionIdOrAlias)
6584

6685
expect(axios.get).toHaveBeenCalledWith(
6786
`${TestConstants.TEST_API_URL}/guestbooks/${collectionIdOrAlias}/list`,
6887
TestConstants.TEST_EXPECTED_AUTHENTICATED_REQUEST_CONFIG_API_KEY
6988
)
70-
expect(actual).toStrictEqual(guestbooksResponse.data.data)
89+
expect(actual).toStrictEqual(guestbooksResponseWithoutStats.data.data)
90+
expect(actual[0].usageCount).toBeUndefined()
91+
expect(actual[0].responseCount).toBeUndefined()
7192
})
7293

7394
test('should list guestbooks with stats when includeStats is true', async () => {

0 commit comments

Comments
 (0)