Skip to content

Commit c59e5ca

Browse files
authored
Merge pull request #836 from PermanentOrg/PER-10357-fallback-for-share-token
[PER-10357] Add fallback for stela folder calls
2 parents 518b4c5 + ada626c commit c59e5ca

File tree

6 files changed

+262
-91
lines changed

6 files changed

+262
-91
lines changed

src/app/shared/services/api/base.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { HttpService } from '@shared/services/http/http.service';
22
import { SimpleVO, ResponseMessageType } from '@root/app/models';
33
import { compact } from 'lodash';
4+
import { Injectable } from '@angular/core';
45
import { HttpV2Service } from '../http-v2/http-v2.service';
56

67
export interface CSRFResponse {
@@ -75,6 +76,7 @@ export class BaseResponse {
7576
}
7677
}
7778

79+
@Injectable()
7880
export class BaseRepo {
7981
constructor(
8082
public http: HttpService,
Lines changed: 132 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,132 @@
1-
// This suite doesn't actually have any tests, but for some reason the setup
2-
// was causing the test bed to get contaminated.
3-
// For this reason, I comment it out! There are other tests
4-
// fully commented out, and we will be cleaning them up
5-
6-
// import { TestBed } from '@angular/core/testing';
7-
// import {
8-
// HttpTestingController,
9-
// provideHttpClientTesting,
10-
// } from '@angular/common/http/testing';
11-
12-
// import { HttpService } from '@shared/services/http/http.service';
13-
// import { FolderRepo } from '@shared/services/api/folder.repo';
14-
// import {
15-
// provideHttpClient,
16-
// withInterceptorsFromDi,
17-
// } from '@angular/common/http';
18-
19-
// describe('FolderRepo', () => {
20-
// let repo: FolderRepo;
21-
// let httpMock: HttpTestingController;
22-
23-
// beforeEach(() => {
24-
// TestBed.configureTestingModule({
25-
// imports: [],
26-
// providers: [
27-
// HttpService,
28-
// provideHttpClient(withInterceptorsFromDi()),
29-
// provideHttpClientTesting(),
30-
// ],
31-
// });
32-
33-
// httpMock = TestBed.inject(HttpTestingController);
34-
// });
35-
36-
// afterEach(() => {
37-
// httpMock.verify();
38-
// });
39-
// });
1+
import { TestBed } from '@angular/core/testing';
2+
import { FolderVO } from '@models/index';
3+
import { of } from 'rxjs';
4+
import { HttpV2Service } from '../http-v2/http-v2.service';
5+
import { HttpService } from '../http/http.service';
6+
import { FolderRepo } from './folder.repo';
7+
8+
const emptyResponse = { items: [] };
9+
const fakeFolderResponse = {
10+
items: [
11+
{
12+
id: 42,
13+
name: 'Auth Folder',
14+
},
15+
],
16+
};
17+
const fakeChildrenResponse = {
18+
items: [
19+
{
20+
id: 300,
21+
name: 'Auth Child',
22+
thumbnailUrls: { 200: 'test' },
23+
paths: { names: 'test' },
24+
location: { stelaLocation: { id: 13 } },
25+
},
26+
],
27+
};
28+
29+
describe('Folder repo', () => {
30+
let folderRepo: FolderRepo;
31+
let httpSpy: jasmine.SpyObj<HttpService>;
32+
let httpV2Spy: jasmine.SpyObj<HttpV2Service>;
33+
34+
beforeEach(() => {
35+
httpSpy = jasmine.createSpyObj('HttpService', [
36+
'sendRequest',
37+
'sendRequestPromise',
38+
]);
39+
httpV2Spy = jasmine.createSpyObj('HttpV2Service', ['get']);
40+
41+
TestBed.configureTestingModule({
42+
providers: [
43+
FolderRepo,
44+
{ provide: HttpService, useValue: httpSpy },
45+
{ provide: HttpV2Service, useValue: httpV2Spy },
46+
],
47+
});
48+
49+
folderRepo = TestBed.inject(FolderRepo);
50+
});
51+
52+
it('should get folder with children using the auth token', async () => {
53+
const mockFolderVO = { folderId: 42 } as FolderVO;
54+
55+
httpV2Spy.get.and.returnValues(
56+
of([fakeFolderResponse]),
57+
of([fakeChildrenResponse]),
58+
);
59+
60+
const result = await folderRepo.getWithChildren([mockFolderVO]);
61+
62+
expect(httpV2Spy.get).toHaveBeenCalledWith('v2/folder', {
63+
folderIds: [42],
64+
});
65+
66+
expect(httpV2Spy.get).toHaveBeenCalledWith('v2/folder/42/children', {
67+
pageSize: 99999999,
68+
});
69+
70+
expect(result.isSuccessful).toBeTrue();
71+
expect(result.Results[0].data[0].FolderVO).toBeDefined();
72+
});
73+
74+
it('should get folder with children using the share token', async () => {
75+
const mockFolderVO = { folderId: 42 } as FolderVO;
76+
77+
httpV2Spy.get.and.returnValues(
78+
of([fakeFolderResponse]),
79+
of([fakeChildrenResponse]),
80+
);
81+
82+
const result = await folderRepo.getWithChildren(
83+
[mockFolderVO],
84+
'share-token-123',
85+
);
86+
87+
expect(httpV2Spy.get).toHaveBeenCalledWith(
88+
'v2/folder',
89+
{ folderIds: [42] },
90+
null,
91+
{ authToken: false, shareToken: 'share-token-123' },
92+
);
93+
94+
expect(httpV2Spy.get).toHaveBeenCalledWith(
95+
'v2/folder/42/children',
96+
jasmine.anything(),
97+
null,
98+
{ authToken: false, shareToken: 'share-token-123' },
99+
);
100+
101+
expect(result.Results[0].data[0].FolderVO).toBeDefined();
102+
});
103+
104+
it('should get folder with children using fallback to auth token', async () => {
105+
const mockFolderVO = { folderId: 42 } as FolderVO;
106+
107+
httpV2Spy.get.and.returnValues(
108+
of([emptyResponse]),
109+
of([fakeFolderResponse]),
110+
of([emptyResponse]),
111+
of([fakeChildrenResponse]),
112+
);
113+
114+
const result = await folderRepo.getWithChildren(
115+
[mockFolderVO],
116+
'bad-share-token',
117+
);
118+
119+
expect(httpV2Spy.get).toHaveBeenCalledWith(
120+
'v2/folder',
121+
{ folderIds: [42] },
122+
null,
123+
{ authToken: false, shareToken: 'bad-share-token' },
124+
);
125+
126+
expect(httpV2Spy.get).toHaveBeenCalledWith('v2/folder', {
127+
folderIds: [42],
128+
});
129+
130+
expect(result.Results[0].data[0].FolderVO).toBeDefined();
131+
});
132+
});

src/app/shared/services/api/folder.repo.ts

Lines changed: 67 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -115,17 +115,17 @@ const convertStelaFolderToFolderVO = (stelaFolder: StelaFolder): FolderVO => {
115115
imageRatio: stelaFolder.imageRatio,
116116
type: stelaFolder.type,
117117
thumbStatus: stelaFolder.status,
118-
thumbURL200: stelaFolder.thumbnailUrls['200'],
119-
thumbURL500: stelaFolder.thumbnailUrls['500'],
120-
thumbURL1000: stelaFolder.thumbnailUrls['1000'],
121-
thumbURL2000: stelaFolder.thumbnailUrls['2000'],
118+
thumbURL200: stelaFolder.thumbnailUrls?.['200'],
119+
thumbURL500: stelaFolder.thumbnailUrls?.['500'],
120+
thumbURL1000: stelaFolder.thumbnailUrls?.['1000'],
121+
thumbURL2000: stelaFolder.thumbnailUrls?.['2000'],
122122
thumbDT: stelaFolder.displayTimestamp,
123-
thumbnail256: stelaFolder.thumbnailUrls['256'],
124-
thumbnail256CloudPath: stelaFolder.thumbnailUrls['256'],
123+
thumbnail256: stelaFolder.thumbnailUrls?.['256'],
124+
thumbnail256CloudPath: stelaFolder.thumbnailUrls?.['256'],
125125
status: stelaFolder.status,
126126
publicDT: stelaFolder.publicAt,
127127
parentFolderId: stelaFolder.parentFolder?.id,
128-
pathAsText: stelaFolder.paths.names,
128+
pathAsText: stelaFolder.paths?.names,
129129
ParentFolderVOs: [new FolderVO({ folderId: stelaFolder.parentFolder?.id })],
130130
ChildFolderVOs: childFolderVOs,
131131
RecordVOs: childRecordVOs,
@@ -176,23 +176,37 @@ export class FolderRepo extends BaseRepo {
176176
const queryData = {
177177
folderIds: [folderVO.folderId],
178178
};
179-
let options = {};
179+
let folderResponse: PagedStelaResponse<StelaFolder>;
180180
if (shareToken) {
181-
options = {
182-
authToken: false,
183-
shareToken,
184-
};
181+
folderResponse = (
182+
await firstValueFrom(
183+
this.httpV2.get<PagedStelaResponse<StelaFolder>>(
184+
`v2/folder`,
185+
queryData,
186+
null,
187+
{
188+
authToken: false,
189+
shareToken,
190+
},
191+
),
192+
)
193+
)[0];
194+
}
195+
// we do not want to reveal the existence or non-existence of
196+
// matching items, unless the request is authenticated by auth token
197+
// a request authenticated by a share token will not get a 404 or 401,
198+
// it just responds with a 200 and an empty array and if we get an empty array,
199+
// we try as a fallback and see if maybe we can get the files as an authenticated user
200+
if (!folderResponse?.items?.[0]) {
201+
folderResponse = (
202+
await firstValueFrom(
203+
this.httpV2.get<PagedStelaResponse<StelaFolder>>(
204+
`v2/folder`,
205+
queryData,
206+
),
207+
)
208+
)[0];
185209
}
186-
const folderResponse = (
187-
await firstValueFrom(
188-
this.httpV2.get<PagedStelaResponse<StelaFolder>>(
189-
`v2/folder`,
190-
queryData,
191-
null,
192-
options,
193-
),
194-
)
195-
)[0];
196210
return folderResponse.items[0];
197211
}
198212

@@ -203,23 +217,39 @@ export class FolderRepo extends BaseRepo {
203217
const queryData = {
204218
pageSize: 99999999, // We want all results in one request
205219
};
206-
let options = {};
220+
221+
let childrenResponse: PagedStelaResponse<StelaFolderChild>;
207222
if (shareToken) {
208-
options = {
209-
authToken: false,
210-
shareToken,
211-
};
223+
childrenResponse = (
224+
await firstValueFrom(
225+
this.httpV2.get<PagedStelaResponse<StelaFolderChild>>(
226+
`v2/folder/${folderVO.folderId}/children`,
227+
queryData,
228+
null,
229+
{
230+
authToken: false,
231+
shareToken,
232+
},
233+
),
234+
)
235+
)[0];
212236
}
213-
const childrenResponse = (
214-
await firstValueFrom(
215-
this.httpV2.get<PagedStelaResponse<StelaFolderChild>>(
216-
`v2/folder/${folderVO.folderId}/children`,
217-
queryData,
218-
null,
219-
options,
220-
),
221-
)
222-
)[0];
237+
// we do not want to reveal the existence or non-existence of
238+
// matching items, unless the request is authenticated by auth token
239+
// a request authenticated by a share token will not get a 404 or 401,
240+
// it just responds with a 200 and an empty array and if we get an empty array,
241+
// we try as a fallback and see if maybe we can get the files as an authenticated user
242+
if (!childrenResponse?.items) {
243+
childrenResponse = (
244+
await firstValueFrom(
245+
this.httpV2.get<PagedStelaResponse<StelaFolderChild>>(
246+
`v2/folder/${folderVO.folderId}/children`,
247+
queryData,
248+
),
249+
)
250+
)[0];
251+
}
252+
223253
return childrenResponse.items;
224254
}
225255

src/app/shared/services/api/record.repo.spec.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { TestBed } from '@angular/core/testing';
1+
import { fakeAsync, TestBed, tick } from '@angular/core/testing';
22
import {
33
HttpTestingController,
44
provideHttpClientTesting,
55
} from '@angular/common/http/testing';
66
import { environment } from '@root/environments/environment';
77
import { HttpService } from '@shared/services/http/http.service';
8-
import { RecordRepo } from '@shared/services/api/record.repo';
8+
import { RecordRepo, RecordResponse } from '@shared/services/api/record.repo';
99
import { RecordVO } from '@root/app/models';
1010
import {
1111
provideHttpClient,
@@ -39,6 +39,33 @@ describe('RecordRepo', () => {
3939
httpMock.verify();
4040
});
4141

42+
// in order to test that the request is made with the auth token,
43+
// with the share token and that the fallback works, the test suite
44+
// should be refactored to mock the http and httpV2 services
45+
46+
// isolating the record.repo service functionality from other
47+
// dependencies would make the tests more reliable
48+
it('should use a V2 request to get records by id', fakeAsync(() => {
49+
const fakeRecordVO = {
50+
recordId: 5,
51+
} as unknown as RecordVO;
52+
53+
const recordPromise = repo.get([fakeRecordVO]);
54+
55+
tick();
56+
57+
const req = httpMock.expectOne(
58+
`${environment.apiUrl}/v2/record?recordIds[]=5`,
59+
);
60+
61+
expect(req.request.method).toBe('GET');
62+
expect(req.request.headers.get('Request-Version')).toBe('2');
63+
req.flush([fakeRecordVO]);
64+
recordPromise.then((recordResponse: RecordResponse) => {
65+
expect(recordResponse).toBeDefined();
66+
});
67+
}));
68+
4269
it('should use a V2 request for registerRecord', (done) => {
4370
const testRecord = new RecordVO({
4471
displayName: 'test',

0 commit comments

Comments
 (0)