Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -419,8 +419,7 @@
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"polyfills": ["src/polyfills.ts", "zone.js/testing"],
"tsConfig": "src/tsconfig.spec.json",
"karmaConfig": "src/karma.conf.js",
"styles": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import { UserService } from '../../../core'
import { AccountActionsDuplicatedService } from '../../../core/account-actions-duplicated/account-actions-duplicated.service'

import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
import { MatFormFieldModule } from '@angular/material/form-field'
import { MatInputModule } from '@angular/material/input'
import { NoopAnimationsModule } from '@angular/platform-browser/animations'

describe('SettingsActionsDuplicatedComponent', () => {
let component: SettingsActionsDuplicatedComponent
Expand All @@ -27,6 +30,9 @@ describe('SettingsActionsDuplicatedComponent', () => {
MatDialogModule,
RouterTestingModule,
ReactiveFormsModule,
MatFormFieldModule,
MatInputModule,
NoopAnimationsModule,
],
declarations: [SettingsActionsDuplicatedComponent],
providers: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,27 @@ import { Overlay } from '@angular/cdk/overlay'
import { RouterTestingModule } from '@angular/router/testing'

import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
import { ReactiveFormsModule } from '@angular/forms'
import { MatFormFieldModule } from '@angular/material/form-field'
import { MatInputModule } from '@angular/material/input'
import { MatIconModule } from '@angular/material/icon'
import { NoopAnimationsModule } from '@angular/platform-browser/animations'

describe('SettingsSecurityPasswordComponent', () => {
let component: SettingsSecurityPasswordComponent
let fixture: ComponentFixture<SettingsSecurityPasswordComponent>

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [HttpClientTestingModule, RouterTestingModule],
imports: [
HttpClientTestingModule,
ReactiveFormsModule,
RouterTestingModule,
MatFormFieldModule,
MatInputModule,
MatIconModule,
NoopAnimationsModule,
],
declarations: [SettingsSecurityPasswordComponent],
providers: [
WINDOW_PROVIDERS,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ describe('FormAuthorizeComponent', () => {
let userService: jasmine.SpyObj<UserService>
let togglzService: jasmine.SpyObj<TogglzService>
let platformInfoService: jasmine.SpyObj<PlatformInfoService>
let oauthURLSessionManagerService: jasmine.SpyObj<OauthURLSessionManagerService>
let mockWindow: jasmine.SpyObj<Window>

beforeEach(waitForAsync(() => {
Expand Down Expand Up @@ -66,7 +67,7 @@ describe('FormAuthorizeComponent', () => {
])
const oauthURLSessionManagerServiceSpy = jasmine.createSpyObj(
'OauthURLSessionManagerService',
['getOauthUrlSession', 'consumeJustRegistered']
['getOauthUrlSession', 'consumeJustRegistered', 'set']
)
const mockWindowSpy = jasmine.createSpyObj('Window', ['location'])

Expand Down Expand Up @@ -118,6 +119,9 @@ describe('FormAuthorizeComponent', () => {
platformInfoService = TestBed.inject(
PlatformInfoService
) as jasmine.SpyObj<PlatformInfoService>
oauthURLSessionManagerService = TestBed.inject(
OauthURLSessionManagerService
) as jasmine.SpyObj<OauthURLSessionManagerService>
mockWindow = TestBed.inject(WINDOW) as jasmine.SpyObj<Window>
;(mockWindow as any).outOfRouterNavigation = (value: string) => {
mockWindow.location.href = value
Expand Down Expand Up @@ -393,4 +397,78 @@ describe('FormAuthorizeComponent', () => {
expect(mockWindow.location.href).toBe('/signin')
})
})

describe('preserving the OAuth URL across logout', () => {
const authorizeUrl =
'https://orcid.org/oauth/authorize?client_id=APP-123&response_type=code&scope=%2Fauthenticate&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback'

beforeEach(() => {
component.OAUTH_AUTHORIZATION = true
component.platformInfo = {
queryParameters: { client_id: 'APP-123' },
} as unknown as PlatformInfo
mockWindow.location.href = authorizeUrl
})

// The first sign in consumes and clears the stored URL, and logging out
// navigates straight to /signin without crossing the AuthorizeGuard. Unless
// logout stores it again, a later institutional sign in has nothing to
// return to and the user lands on my-orcid instead of the authorize screen.
it('should store the current authorize URL before logging out', () => {
userService.noRedirectLogout.and.returnValue(
of(new HttpResponse({ body: 'OK' }))
)

component.logout()

expect(oauthURLSessionManagerService.set).toHaveBeenCalledWith(
authorizeUrl
)
})

it('should store the authorize URL before the logout request is made', () => {
userService.noRedirectLogout.and.returnValue(
of(new HttpResponse({ body: 'OK' }))
)

component.logout()

expect(oauthURLSessionManagerService.set).toHaveBeenCalledBefore(
userService.noRedirectLogout
)
})

it('should store the authorize URL even when the logout request fails', () => {
userService.noRedirectLogout.and.returnValue(
throwError(() => new Error('Logout failed'))
)

component.logout()

expect(oauthURLSessionManagerService.set).toHaveBeenCalledWith(
authorizeUrl
)
expect(mockWindow.location.href).toBe('/signin?client_id=APP-123')
})

it('should not store an OAuth URL when OAUTH_AUTHORIZATION is false', () => {
component.OAUTH_AUTHORIZATION = false

component.logout()

expect(oauthURLSessionManagerService.set).not.toHaveBeenCalled()
expect(mockWindow.location.href).toBe('/signout')
})

it('should not store an empty OAuth URL', () => {
mockWindow.location.href = ''
userService.noRedirectLogout.and.returnValue(
of(new HttpResponse({ body: 'OK' }))
)

component.logout()

expect(oauthURLSessionManagerService.set).not.toHaveBeenCalled()
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ export class FormAuthorizeComponent implements OnInit, OnDestroy {
logout() {
this.reportOauthAuthorizationLogout('user_initiated_logout')
if (this.OAUTH_AUTHORIZATION) {
this.preserveOauthUrlForNextSignIn()
this._user
.noRedirectLogout()
.pipe(
Expand All @@ -182,6 +183,20 @@ export class FormAuthorizeComponent implements OnInit, OnDestroy {
}
}

/**
* The stored OAuth URL is consumed and cleared by the first successful sign in,
* so by the time the authorization screen is shown it no longer exists. Signing
* out from here hard navigates straight to /signin, which never crosses the
* AuthorizeGuard that would otherwise store it again. Without this, a later
* institutional sign in has no OAuth URL to return to and lands on my-orcid.
*/
private preserveOauthUrlForNextSignIn() {
const authorizeUrl = this.window?.location?.href
if (authorizeUrl) {
this._oauthURLSessionManagerService.set(authorizeUrl)
}
}

private performRedirect() {
// Redirect to login with current url params using hard reload
const queryParams = this.platformInfo.queryParameters
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'

import { DeepSelectInputComponent } from './deep-select-input.component'
import { PlatformInfoService } from '../../platform-info'
import { FormBuilder } from '@angular/forms'
import { FormBuilder, ReactiveFormsModule } from '@angular/forms'
import { get } from 'lodash'
import { of } from 'rxjs'
import { MatMenuModule } from '@angular/material/menu'
import { MatFormFieldModule } from '@angular/material/form-field'
import { MatInputModule } from '@angular/material/input'
import { MatIconModule } from '@angular/material/icon'
import { MatDividerModule } from '@angular/material/divider'
import { NoopAnimationsModule } from '@angular/platform-browser/animations'

describe('DeepSelectInputComponent', () => {
let component: DeepSelectInputComponent
Expand All @@ -21,18 +26,16 @@ describe('DeepSelectInputComponent', () => {
get: () => of({}),
},
},
{
provide: FormBuilder,
useValue: {
group: () => ({
get: () => ({
valueChanges: of(''),
}),
}),
},
},
],
imports: [MatMenuModule],
imports: [
MatMenuModule,
ReactiveFormsModule,
MatFormFieldModule,
MatInputModule,
MatIconModule,
MatDividerModule,
NoopAnimationsModule,
],
})
fixture = TestBed.createComponent(DeepSelectInputComponent)
component = fixture.componentInstance
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'

import { ShareEmailsDomainsComponent } from './share-emails-domains.component'
import { FormBuilder, FormControl, FormGroup } from '@angular/forms'
import {
FormBuilder,
FormControl,
FormGroup,
ReactiveFormsModule,
} from '@angular/forms'
import { RecordEmailsService } from 'src/app/core/record-emails/record-emails.service'

import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'
import { MatCardModule } from '@angular/material/card'
import { MatCheckboxModule } from '@angular/material/checkbox'
import { MatDividerModule } from '@angular/material/divider'
import { MatIconModule } from '@angular/material/icon'
import { UserService } from 'src/app/core'
import { PlatformInfoService } from 'src/app/cdk/platform-info/platform-info.service'
import { WINDOW_PROVIDERS } from 'src/app/cdk/window'
Expand All @@ -18,18 +27,18 @@ describe('ShareEmailsDomainsComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [ShareEmailsDomainsComponent],
imports: [
MatCardModule,
ReactiveFormsModule,
MatCheckboxModule,
MatDividerModule,
MatIconModule,
],
providers: [
{
provide: PlatformInfoService,
useValue: {},
},
{
provide: FormBuilder,
useValue: {
array: () => [new FormControl({})],
group: () => new FormGroup({}),
},
},
{
provide: RecordEmailsService,
useValue: {},
Expand All @@ -47,7 +56,21 @@ describe('ShareEmailsDomainsComponent', () => {
{
provide: RecordService,
useValue: {
getRecord: () => of({}),
// The component only builds its form once a record carrying
// emailDomains arrives, so an empty record leaves `form` undefined
// and `[formGroup]` with nothing to bind to.
getRecord: () =>
of({
emails: {
emailDomains: [
{
value: 'example.org',
visibility: 'PRIVATE',
createdDate: { timestamp: 1 },
},
],
},
}),
},
},
WINDOW_PROVIDERS,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'

import { PageNotFoundComponent } from './page-not-found.component'
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'

describe('PageNotFoundComponent', () => {
let component: PageNotFoundComponent
Expand All @@ -9,6 +10,7 @@ describe('PageNotFoundComponent', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [PageNotFoundComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
fixture = TestBed.createComponent(PageNotFoundComponent)
component = fixture.componentInstance
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { SnackbarService } from 'src/app/cdk/snackbar/snackbar.service'
import { MatDialog } from '@angular/material/dialog'
import { MatSnackBar } from '@angular/material/snack-bar'
import { MonthDayYearDateToStringPipe } from 'src/app/shared/pipes/month-day-year-date-to-string/month-day-year-date-to-string.pipe'
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'

describe('WorkDetailsComponent', () => {
let component: WorkDetailsComponent
Expand All @@ -18,6 +19,7 @@ describe('WorkDetailsComponent', () => {
imports: [HttpClientTestingModule, RouterTestingModule],
declarations: [WorkDetailsComponent, MonthDayYearDateToStringPipe],
providers: [WINDOW_PROVIDERS, SnackbarService, MatSnackBar, MatDialog],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
fixture = TestBed.createComponent(WorkDetailsComponent)
component = fixture.componentInstance
Expand Down
17 changes: 0 additions & 17 deletions src/test.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/tsconfig.spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
"outDir": "../out-tsc/spec",
"types": ["jasmine", "node"]
},
"files": ["test.ts", "polyfills.ts"],
"files": ["polyfills.ts"],
"include": ["**/*.spec.ts", "**/*.d.ts"]
}
Loading