Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Travelperk Guard #1132

Merged
merged 16 commits into from
Jan 3, 2025
1 change: 1 addition & 0 deletions src/app/core/guard/travelperk-token.guard.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Check failure on line 1 in src/app/core/guard/travelperk-token.guard.spec.ts

View workflow job for this annotation

GitHub Actions / lint

Trailing spaces not allowed
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Critical: Implement test suite for TravelperkTokenGuard.

The test file is empty, but it should contain comprehensive test cases for the TravelperkTokenGuard to ensure proper route protection. The guard needs testing for:

  • Workspace ID validation
  • TravelPerk connection status checks
  • Different routing scenarios (success, redirects to workspace/onboarding)

Would you like me to help generate a complete test suite? Here's what I propose to include:

  1. Test setup with necessary TestBed configuration
  2. Mock services for workspace and TravelPerk connection
  3. Test cases for:
    • Valid workspace ID and active connection
    • Missing workspace ID
    • Inactive/expired TravelPerk connection
    • Various routing scenarios
🧰 Tools
🪛 eslint

[error] 1-1: Trailing spaces not allowed.

(no-trailing-spaces)

🪛 GitHub Check: lint

[failure] 1-1:
Trailing spaces not allowed

50 changes: 50 additions & 0 deletions src/app/core/guard/travelperk-token.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Injectable } from '@angular/core';
import { Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable, catchError, map, throwError } from 'rxjs';
import { WorkspaceService } from '../services/common/workspace.service';
import { TravelperkService } from '../services/travelperk/travelperk.service';
import { globalCacheBusterNotifier } from 'ts-cacheable';
import { IntegrationsToastService } from '../services/common/integrations-toast.service';
import { TravelPerkOnboardingState, ToastSeverity } from '../models/enum/enum.model';

@Injectable({
providedIn: 'root'
})
export class TravelperkTokenGuard {
constructor(
private travelperkService: TravelperkService,
private router: Router,
private toastService: IntegrationsToastService,
private workspaceService: WorkspaceService
) { }

canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot
): Observable<boolean> {
Comment on lines +21 to +24
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add return type for getTravelperkTokenHealth response

The service method's response type is {}. Consider creating an interface for better type safety.

// src/app/core/models/travelperk/travelperk.model.ts
export interface TravelperkTokenHealth {
  is_healthy: boolean;
}

const workspaceId = this.workspaceService.getWorkspaceId();

if (!workspaceId) {
this.router.navigateByUrl('workspaces');
return throwError(() => new Error('Workspace not found'));
}
anishfyle marked this conversation as resolved.
Show resolved Hide resolved

return this.travelperkService.getTravelperkData().pipe(
anishfyle marked this conversation as resolved.
Show resolved Hide resolved
map(() => true),
catchError(error => {
if (error.status === 400) {
globalCacheBusterNotifier.next();
this.toastService.displayToastMessage(ToastSeverity.ERROR, 'Oops! Your TravelPerk connection expired, please connect again');

const onboardingState = this.workspaceService.getOnboardingState();
if (onboardingState !== TravelPerkOnboardingState.COMPLETE) {
this.router.navigateByUrl('integrations/travelperk/onboarding/landing');
} else {
this.router.navigateByUrl('integrations/travelperk/onboarding/landing');
}
anishfyle marked this conversation as resolved.
Show resolved Hide resolved
}
return throwError(() => error);
})
);
}
}
11 changes: 9 additions & 2 deletions src/app/core/services/travelperk/travelperk.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { catchError, Observable, Subject, throwError } from 'rxjs';
import { Cacheable, CacheBuster } from 'ts-cacheable';
import { Travelperk, TravelperkConfiguration, TravelperkDestinationAttribuite } from '../../models/travelperk/travelperk.model';
import { ApiService } from '../common/api.service';
Expand Down Expand Up @@ -30,7 +30,14 @@ export class TravelperkService {
}

getTravelperkData(): Observable<Travelperk> {
return this.apiService.get(`/orgs/${this.orgId}/travelperk/`, {});
return this.apiService.get(`/orgs/${this.orgId}/travelperk/`, {}).pipe(
catchError(error => {
if (error.status === 400 && error.error?.message?.includes('token expired')) {
error.error.is_expired = true;
}
return throwError(() => error);
})
);
}

connectTravelperk(): Observable<{}>{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { TravelperkOnboardingAdvancedSettingsComponent } from './travelperk-onbo
import { TravelperkOnboardingComponent } from './travelperk-onboarding.component';
import { TravelperkOnboardingLandingComponent } from './travelperk-onboarding-landing/travelperk-onboarding-landing.component';
import { TravelperkOnboardingDoneComponent } from './travelperk-onboarding-done/travelperk-onboarding-done.component';
import { TravelperkTokenGuard } from 'src/app/core/guard/travelperk-token.guard';

const routes: Routes = [
{
Expand All @@ -17,15 +18,18 @@ const routes: Routes = [
},
{
path: 'payment_profile_settings',
component: TravelperkOnboardingPaymentProfileSettingsComponent
component: TravelperkOnboardingPaymentProfileSettingsComponent,
canActivate: [TravelperkTokenGuard]
},
{
path: 'advanced_settings',
component: TravelperkOnboardingAdvancedSettingsComponent
component: TravelperkOnboardingAdvancedSettingsComponent,
canActivate: [TravelperkTokenGuard]
},
{
path: 'done',
component: TravelperkOnboardingDoneComponent
component: TravelperkOnboardingDoneComponent,
canActivate: [TravelperkTokenGuard]
}
]
}
Expand Down
4 changes: 3 additions & 1 deletion src/app/integrations/travelperk/travelperk-routing.module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { TravelperkComponent } from './travelperk.component';
import { TravelperkTokenGuard } from 'src/app/core/guard/travelperk-token.guard';

const routes: Routes = [
{
Expand All @@ -13,7 +14,8 @@ const routes: Routes = [
},
{
path: 'main',
loadChildren: () => import('./travelperk-main/travelperk-main.module').then(m => m.TravelperkMainModule)
loadChildren: () => import('./travelperk-main/travelperk-main.module').then(m => m.TravelperkMainModule),
canActivate: [TravelperkTokenGuard]
}
]
}
Expand Down
Loading