Skip to content

Commit e1697a2

Browse files
authored
Add unit tests (#213)
* Fix cypress tests * Add unit tests for auth guards and account service * Fix issues running tests + new unit tests in core controllers * Add unit tests to checkout, catalog and details pages * Add unit tests to the pipeline * Fix issues with unit test pipeline * Add tests for seller offer section * Generate coverage report
1 parent 5db2507 commit e1697a2

142 files changed

Lines changed: 4806 additions & 212 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/unit-tests.yml

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
name: Unit Tests
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
branches:
9+
- main
10+
11+
permissions:
12+
contents: read
13+
14+
concurrency:
15+
group: unit-tests-${{ github.workflow }}-${{ github.ref }}
16+
cancel-in-progress: true
17+
18+
jobs:
19+
unit-tests:
20+
runs-on: ubuntu-latest
21+
timeout-minutes: 20
22+
23+
steps:
24+
- name: Checkout
25+
uses: actions/checkout@v4
26+
27+
- name: Setup Node
28+
uses: actions/setup-node@v4
29+
with:
30+
node-version: 20
31+
cache: npm
32+
33+
- name: Install dependencies
34+
run: npm ci
35+
36+
- name: Setup Chrome
37+
id: setup-chrome
38+
uses: browser-actions/setup-chrome@v1
39+
40+
- name: Run unit tests
41+
env:
42+
CHROME_BIN: ${{ steps.setup-chrome.outputs.chrome-path }}
43+
CI: true
44+
run: npm test -- --watch=false --no-progress --code-coverage
45+
46+
- name: Upload coverage report
47+
if: always()
48+
uses: actions/upload-artifact@v4
49+
with:
50+
name: unit-test-coverage
51+
path: coverage
52+
if-no-files-found: warn

angular.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@
8989
"test": {
9090
"builder": "@angular-devkit/build-angular:karma",
9191
"options": {
92+
"karmaConfig": "karma.conf.js",
9293
"polyfills": [
9394
"zone.js",
9495
"zone.js/testing"

cypress/support/constants.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -217,8 +217,8 @@ export const checkHeaderPreLogin = () => {
217217
//cy.intercept( {method: 'GET', url: 'http://proxy.docker:8004/catalog/productOffering?*'}, product_offering).as('productOffering')
218218
cy.intercept( {method: 'GET', url: '**/config*'}, init_config).as('config')
219219
//cy.intercept('GET', '**/catalog/category/urn:ngsi-ld:category:*', category_dft).as('category');
220-
cy.intercept({method: 'GET', url: '**/catalog/catalog*'}, default_catalog).as('catalog')
221-
cy.intercept( {method: 'GET', url: '**/catalog/category*'}, category_dft).as('category')
220+
cy.intercept({method: 'GET', url: '**/catalog/catalog*'}, default_catalog).as('catalog')
221+
cy.intercept( {method: 'GET', url: '**/catalog/category*'}, category_dft).as('category')
222222
// Verify mocks are called 1 time
223223
cy.visit('/', {onBeforeLoad(win) {
224224
win.localStorage.setItem('color-theme', 'dark');
@@ -238,7 +238,6 @@ export const checkHeaderPreLogin = () => {
238238
if ($body.find('[data-cy=browse]').length > 0) cy.getBySel('browse').should('exist')
239239
if ($body.find('[data-cy=about]').length > 0) cy.getBySel('about').should('exist')
240240
})
241-
cy.getBySel('registerAcc').should('exist')
242241
cy.getBySel('knowledge').should('exist')
243242
cy.getBySel('darkMode').should('exist')
244243

@@ -255,7 +254,6 @@ export const checkHeaderPreLogin = () => {
255254
export const checkHeaderPostLogin = () => {
256255
// Verify header interactive elemements are displayed and work as expected
257256
cy.login().should('not.exist')
258-
cy.getBySel('registerAcc').should('not.exist')
259257
cy.getBySel('loggedAcc').should('exist')
260258
cy.get('body').then(($body) => {
261259
if ($body.find('[data-cy=publishOffering]').length > 0) cy.getBySel('publishOffering').should('exist')

karma.conf.js

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Karma configuration file for Angular unit tests.
2+
// Uses a no-sandbox Chrome launcher in CI to avoid Ubuntu namespace sandbox issues.
3+
module.exports = function (config) {
4+
const isCI = !!process.env.CI;
5+
6+
config.set({
7+
basePath: '',
8+
frameworks: ['jasmine', '@angular-devkit/build-angular'],
9+
plugins: [
10+
require('karma-jasmine'),
11+
require('karma-chrome-launcher'),
12+
require('karma-jasmine-html-reporter'),
13+
require('karma-coverage'),
14+
require('@angular-devkit/build-angular/plugins/karma'),
15+
],
16+
client: {
17+
jasmine: {},
18+
clearContext: false,
19+
},
20+
jasmineHtmlReporter: {
21+
suppressAll: true,
22+
},
23+
coverageReporter: {
24+
dir: require('path').join(__dirname, './coverage/bae-frontend'),
25+
subdir: '.',
26+
reporters: [{ type: 'html' }, { type: 'text-summary' }],
27+
},
28+
reporters: ['progress'],
29+
port: 9876,
30+
colors: true,
31+
logLevel: config.LOG_INFO,
32+
autoWatch: true,
33+
browsers: [isCI ? 'ChromeHeadlessNoSandbox' : 'ChromeHeadless'],
34+
customLaunchers: {
35+
ChromeHeadlessNoSandbox: {
36+
base: 'ChromeHeadless',
37+
flags: [
38+
'--no-sandbox',
39+
'--disable-setuid-sandbox',
40+
'--disable-dev-shm-usage',
41+
],
42+
},
43+
},
44+
singleRun: false,
45+
restartOnFileChange: true,
46+
});
47+
};

src/app/app.component.spec.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,33 @@
11
import { TestBed } from '@angular/core/testing';
2+
import { NO_ERRORS_SCHEMA } from '@angular/core';
3+
import { TranslateModule } from '@ngx-translate/core';
4+
import { RouterTestingModule } from '@angular/router/testing';
5+
import { HttpClientTestingModule } from '@angular/common/http/testing';
26
import { AppComponent } from './app.component';
37

48
describe('AppComponent', () => {
59
beforeEach(() => TestBed.configureTestingModule({
6-
declarations: [AppComponent]
7-
}));
10+
schemas: [NO_ERRORS_SCHEMA],
11+
imports: [HttpClientTestingModule, RouterTestingModule, TranslateModule.forRoot()],
12+
declarations: [AppComponent]
13+
}));
814

915
it('should create the app', () => {
1016
const fixture = TestBed.createComponent(AppComponent);
1117
const app = fixture.componentInstance;
1218
expect(app).toBeTruthy();
1319
});
1420

15-
it(`should have as title 'bae-frontend'`, () => {
21+
it(`should have as title 'DOME Marketplace'`, () => {
1622
const fixture = TestBed.createComponent(AppComponent);
1723
const app = fixture.componentInstance;
18-
expect(app.title).toEqual('bae-frontend');
24+
expect(app.title).toEqual('DOME Marketplace');
1925
});
2026

21-
it('should render title', () => {
27+
it('should render shell layout', () => {
2228
const fixture = TestBed.createComponent(AppComponent);
2329
fixture.detectChanges();
2430
const compiled = fixture.nativeElement as HTMLElement;
25-
expect(compiled.querySelector('.content span')?.textContent).toContain('bae-frontend app is running!');
31+
expect(compiled.querySelector('main')).toBeTruthy();
2632
});
2733
});

src/app/chatbot-widget/chatbot-widget.component.spec.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
import { NO_ERRORS_SCHEMA } from '@angular/core';
3+
import { TranslateModule } from '@ngx-translate/core';
4+
import { RouterTestingModule } from '@angular/router/testing';
5+
import { HttpClientTestingModule } from '@angular/common/http/testing';
26

37
import { ChatbotWidgetComponent } from './chatbot-widget.component';
48

@@ -8,7 +12,8 @@ describe('ChatbotWidgetComponent', () => {
812

913
beforeEach(async () => {
1014
await TestBed.configureTestingModule({
11-
imports: [ChatbotWidgetComponent]
15+
schemas: [NO_ERRORS_SCHEMA],
16+
imports: [ChatbotWidgetComponent, HttpClientTestingModule, RouterTestingModule, TranslateModule.forRoot()]
1217
})
1318
.compileComponents();
1419

src/app/guard/auth.guard.spec.ts

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,78 @@
11
import { TestBed } from '@angular/core/testing';
2-
import { CanActivateFn } from '@angular/router';
2+
import { TranslateModule } from '@ngx-translate/core';
3+
import { RouterTestingModule } from '@angular/router/testing';
4+
import { HttpClientTestingModule } from '@angular/common/http/testing';
5+
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
6+
import * as moment from 'moment';
7+
import { LocalStorageService } from '../services/local-storage.service';
8+
import { AuthGuard } from './auth.guard';
39

4-
import { authGuard } from './auth.guard';
10+
describe('AuthGuard', () => {
11+
let guard: AuthGuard;
12+
let localStorageSpy: jasmine.SpyObj<LocalStorageService>;
13+
let routerSpy: jasmine.SpyObj<Router>;
514

6-
describe('authGuard', () => {
7-
const executeGuard: CanActivateFn = (...guardParameters) =>
8-
TestBed.runInInjectionContext(() => authGuard(...guardParameters));
15+
const routeWithRoles = (roles: string[]): ActivatedRouteSnapshot =>
16+
({ data: { roles } } as unknown as ActivatedRouteSnapshot);
17+
18+
const state = {} as RouterStateSnapshot;
919

1020
beforeEach(() => {
11-
TestBed.configureTestingModule({});
21+
localStorageSpy = jasmine.createSpyObj<LocalStorageService>('LocalStorageService', ['getObject']);
22+
routerSpy = jasmine.createSpyObj<Router>('Router', ['navigate']);
23+
24+
TestBed.configureTestingModule({
25+
imports: [HttpClientTestingModule, RouterTestingModule, TranslateModule.forRoot()],
26+
providers: [
27+
AuthGuard,
28+
{ provide: LocalStorageService, useValue: localStorageSpy },
29+
{ provide: Router, useValue: routerSpy },
30+
],
31+
});
32+
33+
guard = TestBed.inject(AuthGuard);
1234
});
1335

1436
it('should be created', () => {
15-
expect(executeGuard).toBeTruthy();
37+
expect(guard).toBeTruthy();
38+
});
39+
40+
it('should redirect to dashboard when login info is empty', () => {
41+
localStorageSpy.getObject.and.returnValue({} as object);
42+
43+
const canActivate = guard.canActivate(routeWithRoles([]), state);
44+
45+
expect(canActivate).toBeFalse();
46+
expect(routerSpy.navigate).toHaveBeenCalledWith(['/dashboard']);
47+
});
48+
49+
it('should allow access for a valid individual with a required role', () => {
50+
localStorageSpy.getObject.and.returnValue({
51+
expire: moment().unix() + 300,
52+
id: 'user-1',
53+
logged_as: 'user-1',
54+
roles: [{ name: 'Seller' }],
55+
organizations: [],
56+
} as object);
57+
58+
const canActivate = guard.canActivate(routeWithRoles(['seller']), state);
59+
60+
expect(canActivate).toBeTrue();
61+
expect(routerSpy.navigate).not.toHaveBeenCalled();
62+
});
63+
64+
it('should deny access when required roles are missing', () => {
65+
localStorageSpy.getObject.and.returnValue({
66+
expire: moment().unix() + 300,
67+
id: 'user-1',
68+
logged_as: 'user-1',
69+
roles: [{ name: 'Buyer' }],
70+
organizations: [],
71+
} as object);
72+
73+
const canActivate = guard.canActivate(routeWithRoles(['seller']), state);
74+
75+
expect(canActivate).toBeFalse();
76+
expect(routerSpy.navigate).toHaveBeenCalledWith(['/dashboard']);
1677
});
1778
});
Lines changed: 43 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,55 @@
11
import { TestBed } from '@angular/core/testing';
2-
import { CanActivateFn } from '@angular/router';
2+
import { TranslateModule } from '@ngx-translate/core';
3+
import { RouterTestingModule } from '@angular/router/testing';
4+
import { HttpClientTestingModule } from '@angular/common/http/testing';
5+
import { Router } from '@angular/router';
6+
import { environment } from '../../environments/environment';
37

48
import { quoteGuardGuard } from './quote-guard.guard';
59

610
describe('quoteGuardGuard', () => {
7-
const executeGuard: CanActivateFn = (...guardParameters) =>
8-
TestBed.runInInjectionContext(() => quoteGuardGuard(...guardParameters));
11+
let guard: quoteGuardGuard;
12+
let routerSpy: jasmine.SpyObj<Router>;
13+
let originalQuotesEnabled: boolean;
914

1015
beforeEach(() => {
11-
TestBed.configureTestingModule({});
16+
routerSpy = jasmine.createSpyObj<Router>('Router', ['navigate']);
17+
originalQuotesEnabled = environment.QUOTES_ENABLED;
18+
19+
TestBed.configureTestingModule({
20+
imports: [HttpClientTestingModule, RouterTestingModule, TranslateModule.forRoot()],
21+
providers: [
22+
quoteGuardGuard,
23+
{ provide: Router, useValue: routerSpy },
24+
],
25+
});
26+
27+
guard = TestBed.inject(quoteGuardGuard);
28+
});
29+
30+
afterEach(() => {
31+
environment.QUOTES_ENABLED = originalQuotesEnabled;
1232
});
1333

1434
it('should be created', () => {
15-
expect(executeGuard).toBeTruthy();
35+
expect(guard).toBeTruthy();
36+
});
37+
38+
it('should return true when quotes are enabled', () => {
39+
environment.QUOTES_ENABLED = true;
40+
41+
const canActivate = guard.canActivate();
42+
43+
expect(canActivate).toBeTrue();
44+
expect(routerSpy.navigate).not.toHaveBeenCalled();
45+
});
46+
47+
it('should redirect to dashboard and return false when quotes are disabled', () => {
48+
environment.QUOTES_ENABLED = false;
49+
50+
const canActivate = guard.canActivate();
51+
52+
expect(canActivate).toBeFalse();
53+
expect(routerSpy.navigate).toHaveBeenCalledWith(['/dashboard']);
1654
});
1755
});

src/app/offerings/contact-us/contact-us.component.spec.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
import { NO_ERRORS_SCHEMA } from '@angular/core';
3+
import { TranslateModule } from '@ngx-translate/core';
4+
import { RouterTestingModule } from '@angular/router/testing';
5+
import { HttpClientTestingModule } from '@angular/common/http/testing';
26

37
import { ContactUsComponent } from './contact-us.component';
48

@@ -8,6 +12,8 @@ describe('ContactUsComponent', () => {
812

913
beforeEach(async () => {
1014
await TestBed.configureTestingModule({
15+
schemas: [NO_ERRORS_SCHEMA],
16+
imports: [HttpClientTestingModule, RouterTestingModule, TranslateModule.forRoot()],
1117
declarations: [ContactUsComponent]
1218
})
1319
.compileComponents();

src/app/offerings/explore-dome/explore-dome.component.spec.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
import { NO_ERRORS_SCHEMA } from '@angular/core';
3+
import { TranslateModule } from '@ngx-translate/core';
4+
import { RouterTestingModule } from '@angular/router/testing';
5+
import { HttpClientTestingModule } from '@angular/common/http/testing';
26

37
import { ExploreDomeComponent } from './explore-dome.component';
48

@@ -8,7 +12,9 @@ describe('ExploreDomeComponent', () => {
812

913
beforeEach(async () => {
1014
await TestBed.configureTestingModule({
11-
imports: [ExploreDomeComponent]
15+
schemas: [NO_ERRORS_SCHEMA],
16+
declarations: [ExploreDomeComponent],
17+
imports: [HttpClientTestingModule, RouterTestingModule, TranslateModule.forRoot()]
1218
})
1319
.compileComponents();
1420

0 commit comments

Comments
 (0)