Skip to content

Commit

Permalink
Add initial groundwork for event log viewing
Browse files Browse the repository at this point in the history
  • Loading branch information
pmachapman committed Dec 19, 2024
1 parent 114083c commit 520e355
Show file tree
Hide file tree
Showing 8 changed files with 201 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { AuthGuard } from 'xforge-common/auth.guard';
import { SystemAdminAuthGuard } from 'xforge-common/system-admin-auth.guard';
import { SystemAdministrationComponent } from 'xforge-common/system-administration/system-administration.component';
import { ConnectProjectComponent } from './connect-project/connect-project.component';
import { EventMetricsAuthGuard } from './event-metrics/event-metrics-auth.guard';
import { EventMetricsComponent } from './event-metrics/event-metrics.component';
import { JoinComponent } from './join/join.component';
import { MyProjectsComponent } from './my-projects/my-projects.component';
import { ProjectComponent } from './project/project.component';
Expand All @@ -21,6 +23,7 @@ const routes: Routes = [
{ path: 'login', redirectTo: 'projects', pathMatch: 'full' },
{ path: 'join/:shareKey', component: JoinComponent },
{ path: 'join/:shareKey/:locale', component: JoinComponent },
{ path: 'projects/:projectId/event-log', component: EventMetricsComponent, canActivate: [EventMetricsAuthGuard] },
{ path: 'projects/:projectId/settings', component: SettingsComponent, canActivate: [SettingsAuthGuard] },
{ path: 'projects/:projectId/sync', component: SyncComponent, canActivate: [SyncAuthGuard] },
{ path: 'projects/:projectId', component: ProjectComponent, canActivate: [AuthGuard] },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { fakeAsync, TestBed, tick } from '@angular/core/testing';
import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { SystemRole } from 'realtime-server/lib/esm/common/models/system-role';
import { SFProjectRole } from 'realtime-server/lib/esm/scriptureforge/models/sf-project-role';
import { createTestProjectProfile } from 'realtime-server/lib/esm/scriptureforge/models/sf-project-test-data';
import { of } from 'rxjs';
import { anything, mock, when } from 'ts-mockito';
import { AuthGuard } from 'xforge-common/auth.guard';
import { AuthService } from 'xforge-common/auth.service';
import { configureTestingModule } from 'xforge-common/test-utils';
import { UserService } from 'xforge-common/user.service';
import { SFProjectProfileDoc } from '../core/models/sf-project-profile-doc';
import { SFProjectService } from '../core/sf-project.service';
import { EventMetricsAuthGuard } from './event-metrics-auth.guard';

const mockedAuthGuard = mock(AuthGuard);
const mockedAuthService = mock(AuthService);
const mockedProjectService = mock(SFProjectService);
const mockedUserService = mock(UserService);

describe('EventMetricsAuthGuard', () => {
const project01 = 'project01';
const project02 = 'project02';
const user01 = 'user01';
configureTestingModule(() => ({
providers: [
{ provide: AuthGuard, useMock: mockedAuthGuard },
{ provide: AuthService, useMock: mockedAuthService },
{ provide: SFProjectService, useMock: mockedProjectService },
{ provide: UserService, useMock: mockedUserService }
]
}));

it('can activate if user is logged in and has ServalAdmin role', fakeAsync(() => {
const env = new TestEnvironment(true, SystemRole.ServalAdmin);

env.service.canActivate(env.getActivatedRouteSnapshot(project02), {} as RouterStateSnapshot).subscribe(result => {
expect(result).toBe(true);
});

env.wait();
}));

it('can activate if user is logged in and has SystemAdmin role', fakeAsync(() => {
const env = new TestEnvironment(true, SystemRole.SystemAdmin);

env.service.canActivate(env.getActivatedRouteSnapshot(project02), {} as RouterStateSnapshot).subscribe(result => {
expect(result).toBe(true);
});

env.wait();
}));

it('can activate if user is logged in and has pt_administrator role', fakeAsync(() => {
const env = new TestEnvironment(true, SystemRole.User);

env.service.canActivate(env.getActivatedRouteSnapshot(project01), {} as RouterStateSnapshot).subscribe(result => {
expect(result).toBe(true);
});

env.wait();
}));

it('cannot activate if user is not logged in', fakeAsync(() => {
const env = new TestEnvironment(false, SystemRole.None);

env.service.canActivate(env.getActivatedRouteSnapshot(project02), {} as RouterStateSnapshot).subscribe(result => {
expect(result).toBe(false);
});

env.wait();
}));

it('cannot activate if user is logged in but does not have any administrator role', fakeAsync(() => {
const env = new TestEnvironment(true, SystemRole.User);

env.service.canActivate(env.getActivatedRouteSnapshot(project02), {} as RouterStateSnapshot).subscribe(result => {
expect(result).toBe(false);
});

env.wait();
}));

class TestEnvironment {
readonly service: EventMetricsAuthGuard;

constructor(isLoggedIn: boolean, role: SystemRole) {
this.service = TestBed.inject(EventMetricsAuthGuard);
when(mockedAuthGuard.canActivate(anything(), anything())).thenReturn(of(isLoggedIn));
when(mockedAuthGuard.allowTransition()).thenReturn(of(isLoggedIn));
when(mockedAuthService.currentUserRoles).thenReturn([role]);
when(mockedUserService.currentUserId).thenReturn(user01);

when(mockedProjectService.getProfile(project01)).thenReturn(
Promise.resolve({
data: createTestProjectProfile({ userRoles: { user01: SFProjectRole.ParatextAdministrator } })
} as SFProjectProfileDoc)
);
when(mockedProjectService.getProfile(project02)).thenReturn(
Promise.resolve({
data: createTestProjectProfile()
} as SFProjectProfileDoc)
);
}

getActivatedRouteSnapshot(projectId: string): ActivatedRouteSnapshot {
const snapshot = new ActivatedRouteSnapshot();
snapshot.params = { projectId: projectId };
return snapshot;
}

wait(): void {
tick();
}
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Injectable } from '@angular/core';
import { SystemRole } from 'realtime-server/lib/esm/common/models/system-role';
import { SFProjectRole } from 'realtime-server/lib/esm/scriptureforge/models/sf-project-role';
import { AuthGuard } from 'xforge-common/auth.guard';
import { AuthService } from 'xforge-common/auth.service';
import { UserService } from 'xforge-common/user.service';
import { SFProjectProfileDoc } from '../core/models/sf-project-profile-doc';
import { SFProjectService } from '../core/sf-project.service';
import { RouterGuard } from '../shared/project-router.guard';

@Injectable({
providedIn: 'root'
})
export class EventMetricsAuthGuard extends RouterGuard {
constructor(
readonly authGuard: AuthGuard,
private readonly authService: AuthService,
readonly projectService: SFProjectService,
private readonly userService: UserService
) {
super(authGuard, projectService);
}

check(projectDoc: SFProjectProfileDoc): boolean {
return (
this.authService.currentUserRoles.includes(SystemRole.ServalAdmin) ||
this.authService.currentUserRoles.includes(SystemRole.SystemAdmin) ||
(projectDoc.data != null &&
projectDoc.data.userRoles[this.userService.currentUserId] === SFProjectRole.ParatextAdministrator)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<h1>Event Log</h1>
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { TestRealtimeModule } from 'xforge-common/test-realtime.module';
import { configureTestingModule, TestTranslocoModule } from 'xforge-common/test-utils';
import { SF_TYPE_REGISTRY } from '../core/models/sf-type-registry';
import { EventMetricsComponent } from './event-metrics.component';

describe('EventMetricsComponent', () => {
configureTestingModule(() => ({
imports: [
EventMetricsComponent,
NoopAnimationsModule,
TestTranslocoModule,
TestRealtimeModule.forRoot(SF_TYPE_REGISTRY)
],
providers: [provideHttpClient(withInterceptorsFromDi()), provideHttpClientTesting()]
}));

it('should be created', () => {
const env = new TestEnvironment();
expect(env.component).toBeTruthy();
});

class TestEnvironment {
readonly component: EventMetricsComponent;
readonly fixture: ComponentFixture<EventMetricsComponent>;

constructor() {
this.fixture = TestBed.createComponent(EventMetricsComponent);
this.component = this.fixture.componentInstance;
this.fixture.detectChanges();
}
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Component } from '@angular/core';

@Component({
selector: 'app-event-metrics',
templateUrl: './event-metrics.component.html',
styleUrls: ['./event-metrics.component.scss'],
standalone: true,
imports: []
})
export class EventMetricsComponent {}
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import { SFProjectProfileDoc } from '../core/models/sf-project-profile-doc';
import { PermissionsService } from '../core/permissions.service';
import { SFProjectService } from '../core/sf-project.service';

abstract class RouterGuard {
export abstract class RouterGuard {
constructor(
protected authGuard: AuthGuard,
protected projectService: SFProjectService
protected readonly authGuard: AuthGuard,
protected readonly projectService: SFProjectService
) {}

canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> {
Expand Down

0 comments on commit 520e355

Please sign in to comment.