Skip to content

Commit 369bd69

Browse files
Kuno Vercammenalexandrevryghem
authored andcommitted
114624: Refactored legacyBitstreamURL resolver into a guard to set the redirect status code to 301 Moved Permanently
1 parent 404ccd9 commit 369bd69

6 files changed

Lines changed: 239 additions & 200 deletions

src/app/bitstream-page/bitstream-page-routing.module.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { ResourcePolicyCreateComponent } from '../shared/resource-policies/creat
88
import { ResourcePolicyResolver } from '../shared/resource-policies/resolvers/resource-policy.resolver';
99
import { ResourcePolicyEditComponent } from '../shared/resource-policies/edit/resource-policy-edit.component';
1010
import { BitstreamAuthorizationsComponent } from './bitstream-authorizations/bitstream-authorizations.component';
11-
import { LegacyBitstreamUrlResolver } from './legacy-bitstream-url.resolver';
11+
import { legacyBitstreamURLRedirectGuard } from './legacy-bitstream-url-redirect.guard';
1212
import { BitstreamBreadcrumbResolver } from '../core/breadcrumbs/bitstream-breadcrumb.resolver';
1313
import { BitstreamBreadcrumbsService } from '../core/breadcrumbs/bitstream-breadcrumbs.service';
1414
import { I18nBreadcrumbResolver } from '../core/breadcrumbs/i18n-breadcrumb.resolver';
@@ -27,17 +27,13 @@ const EDIT_BITSTREAM_AUTHORIZATIONS_PATH = ':id/authorizations';
2727
// Resolve XMLUI bitstream download URLs
2828
path: 'handle/:prefix/:suffix/:filename',
2929
component: BitstreamDownloadPageComponent,
30-
resolve: {
31-
bitstream: LegacyBitstreamUrlResolver
32-
},
30+
canActivate: [legacyBitstreamURLRedirectGuard],
3331
},
3432
{
3533
// Resolve JSPUI bitstream download URLs
3634
path: ':prefix/:suffix/:sequence_id/:filename',
3735
component: BitstreamDownloadPageComponent,
38-
resolve: {
39-
bitstream: LegacyBitstreamUrlResolver
40-
},
36+
canActivate: [legacyBitstreamURLRedirectGuard],
4137
},
4238
{
4339
// Resolve angular bitstream download URLs
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import { EMPTY } from 'rxjs';
2+
import { BitstreamDataService } from '../core/data/bitstream-data.service';
3+
import { RemoteData } from '../core/data/remote-data';
4+
import { RequestEntryState } from '../core/data/request-entry-state.model';
5+
import { legacyBitstreamURLRedirectGuard } from './legacy-bitstream-url-redirect.guard';
6+
import { RouterStub } from '../shared/testing/router.stub';
7+
import { ServerResponseServiceStub } from '../shared/testing/server-response-service.stub';
8+
import { fakeAsync } from '@angular/core/testing';
9+
import { cold } from 'jasmine-marbles';
10+
import { PAGE_NOT_FOUND_PATH } from '../app-routing-paths';
11+
import { Bitstream } from '../core/shared/bitstream.model';
12+
13+
describe('legacyBitstreamURLRedirectGuard', () => {
14+
let resolver: any;
15+
let bitstreamDataService: BitstreamDataService;
16+
let remoteDataMocks: { [type: string]: RemoteData<any> };
17+
let route;
18+
let state;
19+
let serverResponseService: ServerResponseServiceStub;
20+
let router: RouterStub;
21+
22+
beforeEach(() => {
23+
route = {
24+
params: {},
25+
queryParams: {}
26+
};
27+
router = new RouterStub();
28+
serverResponseService = new ServerResponseServiceStub();
29+
state = {};
30+
remoteDataMocks = {
31+
RequestPending: new RemoteData(undefined, 0, 0, RequestEntryState.RequestPending, undefined, undefined, undefined),
32+
ResponsePending: new RemoteData(undefined, 0, 0, RequestEntryState.ResponsePending, undefined, undefined, undefined),
33+
Success: new RemoteData(0, 0, 0, RequestEntryState.Success, undefined, new Bitstream(), 200),
34+
NoContent: new RemoteData(0, 0, 0, RequestEntryState.Success, undefined, undefined, 204),
35+
Error: new RemoteData(0, 0, 0, RequestEntryState.Error, 'Internal server error', undefined, 500),
36+
};
37+
bitstreamDataService = {
38+
findByItemHandle: () => undefined
39+
} as any;
40+
resolver = legacyBitstreamURLRedirectGuard;
41+
});
42+
43+
describe(`resolve`, () => {
44+
describe(`For JSPUI-style URLs`, () => {
45+
beforeEach(() => {
46+
spyOn(bitstreamDataService, 'findByItemHandle').and.returnValue(EMPTY);
47+
route = Object.assign({}, route, {
48+
params: {
49+
prefix: '123456789',
50+
suffix: '1234',
51+
filename: 'some-file.pdf',
52+
sequence_id: '5'
53+
}
54+
});
55+
});
56+
it(`should call findByItemHandle with the handle, sequence id, and filename from the route`, () => {
57+
resolver(route, state, bitstreamDataService, serverResponseService, router);
58+
expect(bitstreamDataService.findByItemHandle).toHaveBeenCalledWith(
59+
`${route.params.prefix}/${route.params.suffix}`,
60+
route.params.sequence_id,
61+
route.params.filename
62+
);
63+
});
64+
});
65+
66+
describe(`For XMLUI-style URLs`, () => {
67+
describe(`when there is a sequenceId query parameter`, () => {
68+
beforeEach(() => {
69+
spyOn(bitstreamDataService, 'findByItemHandle').and.returnValue(EMPTY);
70+
route = Object.assign({}, route, {
71+
params: {
72+
prefix: '123456789',
73+
suffix: '1234',
74+
filename: 'some-file.pdf',
75+
},
76+
queryParams: {
77+
sequenceId: '5'
78+
}
79+
});
80+
});
81+
it(`should call findByItemHandle with the handle and filename from the route, and the sequence ID from the queryParams`, () => {
82+
resolver(route, state, bitstreamDataService, serverResponseService, router);
83+
expect(bitstreamDataService.findByItemHandle).toHaveBeenCalledWith(
84+
`${route.params.prefix}/${route.params.suffix}`,
85+
route.queryParams.sequenceId,
86+
route.params.filename
87+
);
88+
});
89+
});
90+
describe(`when there's no sequenceId query parameter`, () => {
91+
beforeEach(() => {
92+
spyOn(bitstreamDataService, 'findByItemHandle').and.returnValue(EMPTY);
93+
route = Object.assign({}, route, {
94+
params: {
95+
prefix: '123456789',
96+
suffix: '1234',
97+
filename: 'some-file.pdf',
98+
},
99+
});
100+
});
101+
it(`should call findByItemHandle with the handle, and filename from the route`, () => {
102+
resolver(route, state, bitstreamDataService, serverResponseService, router);
103+
expect(bitstreamDataService.findByItemHandle).toHaveBeenCalledWith(
104+
`${route.params.prefix}/${route.params.suffix}`,
105+
undefined,
106+
route.params.filename
107+
);
108+
});
109+
});
110+
});
111+
describe('should return and complete after the RemoteData has...', () => {
112+
it('...failed', fakeAsync(() => {
113+
spyOn(router, 'createUrlTree').and.callThrough();
114+
spyOn(bitstreamDataService, 'findByItemHandle').and.returnValue(cold('a-b-c', {
115+
a: remoteDataMocks.RequestPending,
116+
b: remoteDataMocks.ResponsePending,
117+
c: remoteDataMocks.Error,
118+
}));
119+
resolver(route, state, bitstreamDataService, serverResponseService, router).subscribe(() => {
120+
expect(bitstreamDataService.findByItemHandle).toHaveBeenCalled();
121+
expect(router.createUrlTree).toHaveBeenCalledWith([PAGE_NOT_FOUND_PATH]);
122+
});
123+
}));
124+
125+
it('...succeeded without content', fakeAsync(() => {
126+
spyOn(router, 'createUrlTree').and.callThrough();
127+
spyOn(bitstreamDataService, 'findByItemHandle').and.returnValue(cold('a-b-c', {
128+
a: remoteDataMocks.RequestPending,
129+
b: remoteDataMocks.ResponsePending,
130+
c: remoteDataMocks.NoContent,
131+
}));
132+
resolver(route, state, bitstreamDataService, serverResponseService, router).subscribe(() => {
133+
expect(bitstreamDataService.findByItemHandle).toHaveBeenCalled();
134+
expect(router.createUrlTree).toHaveBeenCalledWith([PAGE_NOT_FOUND_PATH]);
135+
});
136+
}));
137+
138+
it('...succeeded', fakeAsync(() => {
139+
spyOn(serverResponseService, 'setStatus').and.callThrough();
140+
spyOn(bitstreamDataService, 'findByItemHandle').and.returnValue(cold('a-b-c', {
141+
a: remoteDataMocks.RequestPending,
142+
b: remoteDataMocks.ResponsePending,
143+
c: remoteDataMocks.Success,
144+
}));
145+
resolver(route, state, bitstreamDataService, serverResponseService, router).subscribe(() => {
146+
expect(bitstreamDataService.findByItemHandle).toHaveBeenCalled();
147+
expect(serverResponseService.setStatus).toHaveBeenCalledWith(301);
148+
expect(router.parseUrl).toHaveBeenCalled();
149+
});
150+
}));
151+
});
152+
});
153+
});
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { inject } from '@angular/core';
2+
import { ActivatedRouteSnapshot, CanActivateFn, UrlTree, Router, RouterStateSnapshot } from '@angular/router';
3+
import { Observable } from 'rxjs';
4+
import { RemoteData } from '../core/data/remote-data';
5+
import { Bitstream } from '../core/shared/bitstream.model';
6+
import { hasNoValue } from '../shared/empty.util';
7+
import { BitstreamDataService } from '../core/data/bitstream-data.service';
8+
import { ServerResponseService } from '../core/services/server-response.service';
9+
import { map, tap } from 'rxjs/operators';
10+
import { PAGE_NOT_FOUND_PATH } from '../app-routing-paths';
11+
import { getFirstCompletedRemoteData } from '../core/shared/operators';
12+
13+
/**
14+
* Redirects to a bitstream based on the handle of the item, and the sequence id or the filename of the
15+
* bitstream. In production mode the status code will also be set the status code to 301 marking it as a permanent URL
16+
* redirect for bots.
17+
*
18+
* @returns Observable<UrlTree> Returns a URL to redirect the user to the new URL format
19+
*/
20+
export const legacyBitstreamURLRedirectGuard: CanActivateFn = (
21+
route: ActivatedRouteSnapshot,
22+
state: RouterStateSnapshot,
23+
bitstreamDataService: BitstreamDataService = inject(BitstreamDataService),
24+
serverResponseService: ServerResponseService = inject(ServerResponseService),
25+
router: Router = inject(Router),
26+
): Observable<UrlTree> => {
27+
const prefix = route.params.prefix;
28+
const suffix = route.params.suffix;
29+
const filename = route.params.filename;
30+
let sequenceId = route.params.sequence_id;
31+
if (hasNoValue(sequenceId)) {
32+
sequenceId = route.queryParams.sequenceId;
33+
}
34+
return bitstreamDataService.findByItemHandle(
35+
`${prefix}/${suffix}`,
36+
sequenceId,
37+
filename,
38+
).pipe(
39+
getFirstCompletedRemoteData(),
40+
tap((rd: RemoteData<Bitstream>) => {
41+
if (rd.hasSucceeded && !rd.hasNoContent) {
42+
serverResponseService.setStatus(301);
43+
}
44+
}),
45+
map((rd: RemoteData<Bitstream>) => {
46+
if (rd.hasSucceeded && !rd.hasNoContent) {
47+
return router.parseUrl(`/bitstreams/${rd.payload.uuid}/download`);
48+
} else {
49+
return router.createUrlTree([PAGE_NOT_FOUND_PATH]);
50+
}
51+
})
52+
);
53+
};

src/app/bitstream-page/legacy-bitstream-url.resolver.spec.ts

Lines changed: 0 additions & 145 deletions
This file was deleted.

0 commit comments

Comments
 (0)