Skip to content

Commit 5f507f0

Browse files
authored
[ENG-10408] - fix Bibliographic Contributor checkbox (#908)
- Ticket: [ENG-10408] - Feature flag: n/a ## Purpose fix Bibliographic Contributor checkbox ## Summary of Changes Bug fix: Added isBibliographic: true to handleResourcesRawResponse() in contributors.service.ts.
1 parent fafcb51 commit 5f507f0

3 files changed

Lines changed: 542 additions & 0 deletions

File tree

Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
1+
import { MockProvider } from 'ng-mocks';
2+
3+
import { firstValueFrom } from 'rxjs';
4+
5+
import { HttpTestingController } from '@angular/common/http/testing';
6+
import { TestBed } from '@angular/core/testing';
7+
8+
import { ENVIRONMENT_DO_NO_USE } from '@core/constants/environment.token';
9+
import { ContributorPermission } from '@osf/shared/enums/contributors/contributor-permission.enum';
10+
import { ResourceType } from '@osf/shared/enums/resource-type.enum';
11+
import { ContributorModel } from '@osf/shared/models/contributors/contributor.model';
12+
import { ContributorAddModel } from '@osf/shared/models/contributors/contributor-add.model';
13+
14+
import { ContributorsService } from './contributors.service';
15+
16+
import {
17+
getContributorsListData,
18+
getContributorsSearchData,
19+
getContributorsSearchDataSecondUser,
20+
getContributorsSearchDataWithPagination,
21+
} from '@testing/data/contributors/contributors.data';
22+
import { provideOSFCore, provideOSFHttp } from '@testing/osf.testing.provider';
23+
24+
const SHARE_TROVE_URL = 'https://share.osf.io/trove';
25+
const API_URL = 'http://localhost:8000/v2';
26+
const WEB_URL = 'http://localhost:4200';
27+
28+
describe('ContributorsService', () => {
29+
let service: ContributorsService;
30+
31+
beforeEach(() => {
32+
TestBed.configureTestingModule({
33+
providers: [
34+
provideOSFCore(),
35+
provideOSFHttp(),
36+
MockProvider(ENVIRONMENT_DO_NO_USE, {
37+
production: false,
38+
apiDomainUrl: 'http://localhost:8000',
39+
shareTroveUrl: SHARE_TROVE_URL,
40+
webUrl: WEB_URL,
41+
}),
42+
ContributorsService,
43+
],
44+
});
45+
46+
service = TestBed.inject(ContributorsService);
47+
});
48+
49+
it('getUsersByLink should set isBibliographic: true for users returned from search', async () => {
50+
const httpMock = TestBed.inject(HttpTestingController);
51+
52+
const resultPromise = firstValueFrom(service.getUsersByLink(`${SHARE_TROVE_URL}/index-card-search`));
53+
54+
const req = httpMock.expectOne(`${SHARE_TROVE_URL}/index-card-search`);
55+
expect(req.request.method).toBe('GET');
56+
req.flush(getContributorsSearchData());
57+
58+
const result = await resultPromise;
59+
expect(result.users[0].isBibliographic).toBe(true);
60+
expect(result.users[0].permission).toBe(ContributorPermission.Write);
61+
expect(result.users[0].fullName).toBe('Test User');
62+
expect(result.users[0].id).toBe('abc12');
63+
expect(result.totalCount).toBe(1);
64+
expect(result.next).toBeNull();
65+
expect(result.previous).toBeNull();
66+
67+
httpMock.verify();
68+
});
69+
70+
it('getUsersByLink should return next and previous pagination links when present', async () => {
71+
const httpMock = TestBed.inject(HttpTestingController);
72+
73+
const resultPromise = firstValueFrom(service.getUsersByLink(`${SHARE_TROVE_URL}/index-card-search`));
74+
75+
const req = httpMock.expectOne(`${SHARE_TROVE_URL}/index-card-search`);
76+
req.flush(getContributorsSearchDataWithPagination());
77+
78+
const result = await resultPromise;
79+
expect(result.next).toBe('https://share.osf.io/trove/index-card-search?page=2');
80+
expect(result.previous).toBe('https://share.osf.io/trove/index-card-search?page=1');
81+
82+
httpMock.verify();
83+
});
84+
85+
it('searchUsersByName should search by name with correct query params', async () => {
86+
const httpMock = TestBed.inject(HttpTestingController);
87+
88+
const resultPromise = firstValueFrom(service.searchUsersByName('alice'));
89+
90+
const req = httpMock.expectOne((request) => request.url === `${SHARE_TROVE_URL}/index-card-search`);
91+
expect(req.request.method).toBe('GET');
92+
expect(req.request.params.get('cardSearchFilter[resourceType]')).toBe('Person');
93+
expect(req.request.params.get('cardSearchText[name]')).toBe('alice*');
94+
req.flush(getContributorsSearchData());
95+
96+
const result = await resultPromise;
97+
expect(result.users[0].isBibliographic).toBe(true);
98+
99+
httpMock.verify();
100+
});
101+
102+
it('searchUsersById should search by id with correct query params', async () => {
103+
const httpMock = TestBed.inject(HttpTestingController);
104+
105+
const resultPromise = firstValueFrom(service.searchUsersById('abc12'));
106+
107+
const req = httpMock.expectOne((request) => request.url === `${SHARE_TROVE_URL}/index-card-search`);
108+
expect(req.request.method).toBe('GET');
109+
expect(req.request.params.get('cardSearchFilter[sameAs]')).toBe(`${WEB_URL}/abc12`);
110+
req.flush(getContributorsSearchData());
111+
112+
const result = await resultPromise;
113+
expect(result.users[0].isBibliographic).toBe(true);
114+
115+
httpMock.verify();
116+
});
117+
118+
it('searchUsers should call only searchUsersByName when value length is not 5', async () => {
119+
const httpMock = TestBed.inject(HttpTestingController);
120+
121+
const resultPromise = firstValueFrom(service.searchUsers('ali'));
122+
123+
const reqs = httpMock.match((request) => request.url === `${SHARE_TROVE_URL}/index-card-search`);
124+
expect(reqs.length).toBe(1);
125+
reqs[0].flush(getContributorsSearchData());
126+
127+
const result = await resultPromise;
128+
expect(result.users.length).toBe(1);
129+
130+
httpMock.verify();
131+
});
132+
133+
it('searchUsers should forkJoin name and id searches when value length is 5 and deduplicate results', async () => {
134+
const httpMock = TestBed.inject(HttpTestingController);
135+
136+
const resultPromise = firstValueFrom(service.searchUsers('alice'));
137+
138+
const reqs = httpMock.match((request) => request.url === `${SHARE_TROVE_URL}/index-card-search`);
139+
expect(reqs.length).toBe(2);
140+
reqs[0].flush(getContributorsSearchData());
141+
reqs[1].flush(getContributorsSearchData());
142+
143+
const result = await resultPromise;
144+
expect(result.users.length).toBe(1);
145+
146+
httpMock.verify();
147+
});
148+
149+
it('searchUsers should merge unique users from name and id searches', async () => {
150+
const httpMock = TestBed.inject(HttpTestingController);
151+
152+
const resultPromise = firstValueFrom(service.searchUsers('alice'));
153+
154+
const reqs = httpMock.match((request) => request.url === `${SHARE_TROVE_URL}/index-card-search`);
155+
expect(reqs.length).toBe(2);
156+
reqs[0].flush(getContributorsSearchData());
157+
reqs[1].flush(getContributorsSearchDataSecondUser());
158+
159+
const result = await resultPromise;
160+
expect(result.users.length).toBe(2);
161+
expect(result.users[0].id).toBe('abc12');
162+
expect(result.users[1].id).toBe('xyz99');
163+
164+
httpMock.verify();
165+
});
166+
167+
it('getAllContributors should GET contributors with pagination params', async () => {
168+
const httpMock = TestBed.inject(HttpTestingController);
169+
170+
const resultPromise = firstValueFrom(service.getAllContributors(ResourceType.Project, 'node-id', 1, 10));
171+
172+
const req = httpMock.expectOne(`${API_URL}/nodes/node-id/contributors/?page=1&page%5Bsize%5D=10`);
173+
expect(req.request.method).toBe('GET');
174+
req.flush(getContributorsListData());
175+
176+
const result = await resultPromise;
177+
expect(result.totalCount).toBe(1);
178+
expect(result.data[0].isBibliographic).toBe(true);
179+
180+
httpMock.verify();
181+
});
182+
183+
it('getBibliographicContributors should GET bibliographic_contributors endpoint', async () => {
184+
const httpMock = TestBed.inject(HttpTestingController);
185+
186+
const resultPromise = firstValueFrom(service.getBibliographicContributors(ResourceType.Project, 'node-id', 1, 10));
187+
188+
const req = httpMock.expectOne(`${API_URL}/nodes/node-id/bibliographic_contributors/?page=1&page%5Bsize%5D=10`);
189+
expect(req.request.method).toBe('GET');
190+
req.flush(getContributorsListData());
191+
192+
await resultPromise;
193+
httpMock.verify();
194+
});
195+
196+
it('bulkAddContributors should return empty array when contributors list is empty', async () => {
197+
const result = await firstValueFrom(service.bulkAddContributors(ResourceType.Project, 'node-id', []));
198+
expect(result).toEqual([]);
199+
});
200+
201+
it('bulkAddContributors should POST registered contributor with correct payload', async () => {
202+
const httpMock = TestBed.inject(HttpTestingController);
203+
const contributor: ContributorAddModel = {
204+
id: 'user-id',
205+
fullName: 'John Doe',
206+
isBibliographic: true,
207+
permission: ContributorPermission.Write,
208+
};
209+
210+
const resultPromise = firstValueFrom(service.bulkAddContributors(ResourceType.Project, 'node-id', [contributor]));
211+
212+
const req = httpMock.expectOne(`${API_URL}/nodes/node-id/contributors/`);
213+
expect(req.request.method).toBe('POST');
214+
expect(req.request.body.data[0].type).toBe('contributors');
215+
expect(req.request.body.data[0].attributes.bibliographic).toBe(true);
216+
req.flush(getContributorsListData());
217+
218+
await resultPromise;
219+
httpMock.verify();
220+
});
221+
222+
it('bulkAddContributors should POST unregistered contributor when id is missing', async () => {
223+
const httpMock = TestBed.inject(HttpTestingController);
224+
const contributor: ContributorAddModel = {
225+
fullName: 'Unregistered User',
226+
email: 'new@example.com',
227+
isBibliographic: false,
228+
permission: ContributorPermission.Read,
229+
};
230+
231+
const resultPromise = firstValueFrom(service.bulkAddContributors(ResourceType.Project, 'node-id', [contributor]));
232+
233+
const req = httpMock.expectOne(`${API_URL}/nodes/node-id/contributors/`);
234+
expect(req.request.method).toBe('POST');
235+
expect(req.request.body.data[0].attributes.full_name).toBe('Unregistered User');
236+
req.flush(getContributorsListData());
237+
238+
await resultPromise;
239+
httpMock.verify();
240+
});
241+
242+
it('bulkAddContributors should POST with childNodeIds when provided', async () => {
243+
const httpMock = TestBed.inject(HttpTestingController);
244+
const contributor: ContributorAddModel = {
245+
id: 'user-id',
246+
fullName: 'John Doe',
247+
isBibliographic: true,
248+
permission: ContributorPermission.Write,
249+
};
250+
251+
const resultPromise = firstValueFrom(
252+
service.bulkAddContributors(ResourceType.Project, 'node-id', [contributor], ['child-1'])
253+
);
254+
255+
const req = httpMock.expectOne(`${API_URL}/nodes/node-id/contributors/`);
256+
expect(req.request.body.data[0].attributes.child_nodes).toEqual(['child-1']);
257+
req.flush(getContributorsListData());
258+
259+
await resultPromise;
260+
httpMock.verify();
261+
});
262+
263+
it('bulkUpdateContributors should return empty array when contributors list is empty', async () => {
264+
const result = await firstValueFrom(service.bulkUpdateContributors(ResourceType.Project, 'node-id', []));
265+
expect(result).toEqual([]);
266+
});
267+
268+
it('bulkUpdateContributors should PATCH contributors with correct payload', async () => {
269+
const httpMock = TestBed.inject(HttpTestingController);
270+
const contributor: ContributorModel = {
271+
id: 'node-id-user-id',
272+
userId: 'user-id',
273+
type: 'contributors',
274+
fullName: 'John Doe',
275+
givenName: 'John',
276+
familyName: 'Doe',
277+
isUnregisteredContributor: false,
278+
permission: ContributorPermission.Write,
279+
isBibliographic: true,
280+
isCurator: false,
281+
index: 0,
282+
education: [],
283+
employment: [],
284+
deactivated: false,
285+
};
286+
287+
const resultPromise = firstValueFrom(
288+
service.bulkUpdateContributors(ResourceType.Project, 'node-id', [contributor])
289+
);
290+
291+
const req = httpMock.expectOne(`${API_URL}/nodes/node-id/contributors/`);
292+
expect(req.request.method).toBe('PATCH');
293+
expect(req.request.body.data[0].id).toBe('node-id-user-id');
294+
expect(req.request.body.data[0].attributes.bibliographic).toBe(true);
295+
req.flush(getContributorsListData());
296+
297+
await resultPromise;
298+
httpMock.verify();
299+
});
300+
301+
it('addContributorsFromProject should PATCH with copy_contributors_from_parent_project param', async () => {
302+
const httpMock = TestBed.inject(HttpTestingController);
303+
304+
const resultPromise = firstValueFrom(service.addContributorsFromProject(ResourceType.Project, 'node-id'));
305+
306+
const req = httpMock.expectOne(`${API_URL}/nodes/node-id/contributors/?copy_contributors_from_parent_project=true`);
307+
expect(req.request.method).toBe('PATCH');
308+
req.flush(null);
309+
310+
await resultPromise;
311+
httpMock.verify();
312+
});
313+
314+
it('deleteContributor should DELETE contributor by userId', async () => {
315+
const httpMock = TestBed.inject(HttpTestingController);
316+
317+
const resultPromise = firstValueFrom(service.deleteContributor(ResourceType.Project, 'node-id', 'user-id'));
318+
319+
const req = httpMock.expectOne(`${API_URL}/nodes/node-id/contributors/user-id/`);
320+
expect(req.request.method).toBe('DELETE');
321+
req.flush(null);
322+
323+
await resultPromise;
324+
httpMock.verify();
325+
});
326+
327+
it('deleteContributor should DELETE with include_children param when removeFromChildren is true', async () => {
328+
const httpMock = TestBed.inject(HttpTestingController);
329+
330+
const resultPromise = firstValueFrom(service.deleteContributor(ResourceType.Project, 'node-id', 'user-id', true));
331+
332+
const req = httpMock.expectOne(`${API_URL}/nodes/node-id/contributors/user-id/?include_children=true`);
333+
expect(req.request.method).toBe('DELETE');
334+
req.flush(null);
335+
336+
await resultPromise;
337+
httpMock.verify();
338+
});
339+
340+
it('getBaseUrl should throw error for unsupported resource type', () => {
341+
expect(() => service.getAllContributors(ResourceType.File, 'id', 1, 10).subscribe()).toThrowError(
342+
'Unsupported resource type: 1'
343+
);
344+
});
345+
});

src/app/shared/services/contributors.service.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ export class ContributorsService {
172172
({
173173
id: user.absoluteUrl.split('/').pop(),
174174
fullName: user.name,
175+
isBibliographic: true,
175176
permission: ContributorPermission.Write,
176177
}) as ContributorAddModel
177178
);

0 commit comments

Comments
 (0)