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
52 changes: 52 additions & 0 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: Unit Tests

on:
push:
branches:
- main
pull_request:
branches:
- main

permissions:
contents: read

concurrency:
group: unit-tests-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
unit-tests:
runs-on: ubuntu-latest
timeout-minutes: 20

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- name: Install dependencies
run: npm ci

- name: Setup Chrome
id: setup-chrome
uses: browser-actions/setup-chrome@v1

- name: Run unit tests
env:
CHROME_BIN: ${{ steps.setup-chrome.outputs.chrome-path }}
CI: true
run: npm test -- --watch=false --no-progress --code-coverage

- name: Upload coverage report
if: always()
uses: actions/upload-artifact@v4
with:
name: unit-test-coverage
path: coverage
if-no-files-found: warn
1 change: 1 addition & 0 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"karmaConfig": "karma.conf.js",
"polyfills": [
"zone.js",
"zone.js/testing"
Expand Down
6 changes: 2 additions & 4 deletions cypress/support/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,8 +217,8 @@ export const checkHeaderPreLogin = () => {
//cy.intercept( {method: 'GET', url: 'http://proxy.docker:8004/catalog/productOffering?*'}, product_offering).as('productOffering')
cy.intercept( {method: 'GET', url: '**/config*'}, init_config).as('config')
//cy.intercept('GET', '**/catalog/category/urn:ngsi-ld:category:*', category_dft).as('category');
cy.intercept({method: 'GET', url: '**/catalog/catalog*'}, default_catalog).as('catalog')
cy.intercept( {method: 'GET', url: '**/catalog/category*'}, category_dft).as('category')
cy.intercept({method: 'GET', url: '**/catalog/catalog*'}, default_catalog).as('catalog')
cy.intercept( {method: 'GET', url: '**/catalog/category*'}, category_dft).as('category')
// Verify mocks are called 1 time
cy.visit('/', {onBeforeLoad(win) {
win.localStorage.setItem('color-theme', 'dark');
Expand All @@ -238,7 +238,6 @@ export const checkHeaderPreLogin = () => {
if ($body.find('[data-cy=browse]').length > 0) cy.getBySel('browse').should('exist')
if ($body.find('[data-cy=about]').length > 0) cy.getBySel('about').should('exist')
})
cy.getBySel('registerAcc').should('exist')
cy.getBySel('knowledge').should('exist')
cy.getBySel('darkMode').should('exist')

Expand All @@ -255,7 +254,6 @@ export const checkHeaderPreLogin = () => {
export const checkHeaderPostLogin = () => {
// Verify header interactive elemements are displayed and work as expected
cy.login().should('not.exist')
cy.getBySel('registerAcc').should('not.exist')
cy.getBySel('loggedAcc').should('exist')
cy.get('body').then(($body) => {
if ($body.find('[data-cy=publishOffering]').length > 0) cy.getBySel('publishOffering').should('exist')
Expand Down
47 changes: 47 additions & 0 deletions karma.conf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Karma configuration file for Angular unit tests.
// Uses a no-sandbox Chrome launcher in CI to avoid Ubuntu namespace sandbox issues.
module.exports = function (config) {
const isCI = !!process.env.CI;

config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma'),
],
client: {
jasmine: {},
clearContext: false,
},
jasmineHtmlReporter: {
suppressAll: true,
},
coverageReporter: {
dir: require('path').join(__dirname, './coverage/bae-frontend'),
subdir: '.',
reporters: [{ type: 'html' }, { type: 'text-summary' }],
},
reporters: ['progress'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: [isCI ? 'ChromeHeadlessNoSandbox' : 'ChromeHeadless'],
customLaunchers: {
ChromeHeadlessNoSandbox: {
base: 'ChromeHeadless',
flags: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
],
},
},
singleRun: false,
restartOnFileChange: true,
});
};
18 changes: 12 additions & 6 deletions src/app/app.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,33 @@
import { TestBed } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { TranslateModule } from '@ngx-translate/core';
import { RouterTestingModule } from '@angular/router/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { AppComponent } from './app.component';

describe('AppComponent', () => {
beforeEach(() => TestBed.configureTestingModule({
declarations: [AppComponent]
}));
schemas: [NO_ERRORS_SCHEMA],
imports: [HttpClientTestingModule, RouterTestingModule, TranslateModule.forRoot()],
declarations: [AppComponent]
}));

it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app).toBeTruthy();
});

it(`should have as title 'bae-frontend'`, () => {
it(`should have as title 'DOME Marketplace'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.componentInstance;
expect(app.title).toEqual('bae-frontend');
expect(app.title).toEqual('DOME Marketplace');
});

it('should render title', () => {
it('should render shell layout', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('.content span')?.textContent).toContain('bae-frontend app is running!');
expect(compiled.querySelector('main')).toBeTruthy();
});
});
7 changes: 6 additions & 1 deletion src/app/chatbot-widget/chatbot-widget.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { TranslateModule } from '@ngx-translate/core';
import { RouterTestingModule } from '@angular/router/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';

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

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

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ChatbotWidgetComponent]
schemas: [NO_ERRORS_SCHEMA],
imports: [ChatbotWidgetComponent, HttpClientTestingModule, RouterTestingModule, TranslateModule.forRoot()]
})
.compileComponents();

Expand Down
75 changes: 68 additions & 7 deletions src/app/guard/auth.guard.spec.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,78 @@
import { TestBed } from '@angular/core/testing';
import { CanActivateFn } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import { RouterTestingModule } from '@angular/router/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router';
import * as moment from 'moment';
import { LocalStorageService } from '../services/local-storage.service';
import { AuthGuard } from './auth.guard';

import { authGuard } from './auth.guard';
describe('AuthGuard', () => {
let guard: AuthGuard;
let localStorageSpy: jasmine.SpyObj<LocalStorageService>;
let routerSpy: jasmine.SpyObj<Router>;

describe('authGuard', () => {
const executeGuard: CanActivateFn = (...guardParameters) =>
TestBed.runInInjectionContext(() => authGuard(...guardParameters));
const routeWithRoles = (roles: string[]): ActivatedRouteSnapshot =>
({ data: { roles } } as unknown as ActivatedRouteSnapshot);

const state = {} as RouterStateSnapshot;

beforeEach(() => {
TestBed.configureTestingModule({});
localStorageSpy = jasmine.createSpyObj<LocalStorageService>('LocalStorageService', ['getObject']);
routerSpy = jasmine.createSpyObj<Router>('Router', ['navigate']);

TestBed.configureTestingModule({
imports: [HttpClientTestingModule, RouterTestingModule, TranslateModule.forRoot()],
providers: [
AuthGuard,
{ provide: LocalStorageService, useValue: localStorageSpy },
{ provide: Router, useValue: routerSpy },
],
});

guard = TestBed.inject(AuthGuard);
});

it('should be created', () => {
expect(executeGuard).toBeTruthy();
expect(guard).toBeTruthy();
});

it('should redirect to dashboard when login info is empty', () => {
localStorageSpy.getObject.and.returnValue({} as object);

const canActivate = guard.canActivate(routeWithRoles([]), state);

expect(canActivate).toBeFalse();
expect(routerSpy.navigate).toHaveBeenCalledWith(['/dashboard']);
});

it('should allow access for a valid individual with a required role', () => {
localStorageSpy.getObject.and.returnValue({
expire: moment().unix() + 300,
id: 'user-1',
logged_as: 'user-1',
roles: [{ name: 'Seller' }],
organizations: [],
} as object);

const canActivate = guard.canActivate(routeWithRoles(['seller']), state);

expect(canActivate).toBeTrue();
expect(routerSpy.navigate).not.toHaveBeenCalled();
});

it('should deny access when required roles are missing', () => {
localStorageSpy.getObject.and.returnValue({
expire: moment().unix() + 300,
id: 'user-1',
logged_as: 'user-1',
roles: [{ name: 'Buyer' }],
organizations: [],
} as object);

const canActivate = guard.canActivate(routeWithRoles(['seller']), state);

expect(canActivate).toBeFalse();
expect(routerSpy.navigate).toHaveBeenCalledWith(['/dashboard']);
});
});
48 changes: 43 additions & 5 deletions src/app/guard/quote-guard.guard.spec.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,55 @@
import { TestBed } from '@angular/core/testing';
import { CanActivateFn } from '@angular/router';
import { TranslateModule } from '@ngx-translate/core';
import { RouterTestingModule } from '@angular/router/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { Router } from '@angular/router';
import { environment } from '../../environments/environment';

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

describe('quoteGuardGuard', () => {
const executeGuard: CanActivateFn = (...guardParameters) =>
TestBed.runInInjectionContext(() => quoteGuardGuard(...guardParameters));
let guard: quoteGuardGuard;
let routerSpy: jasmine.SpyObj<Router>;
let originalQuotesEnabled: boolean;

beforeEach(() => {
TestBed.configureTestingModule({});
routerSpy = jasmine.createSpyObj<Router>('Router', ['navigate']);
originalQuotesEnabled = environment.QUOTES_ENABLED;

TestBed.configureTestingModule({
imports: [HttpClientTestingModule, RouterTestingModule, TranslateModule.forRoot()],
providers: [
quoteGuardGuard,
{ provide: Router, useValue: routerSpy },
],
});

guard = TestBed.inject(quoteGuardGuard);
});

afterEach(() => {
environment.QUOTES_ENABLED = originalQuotesEnabled;
});

it('should be created', () => {
expect(executeGuard).toBeTruthy();
expect(guard).toBeTruthy();
});

it('should return true when quotes are enabled', () => {
environment.QUOTES_ENABLED = true;

const canActivate = guard.canActivate();

expect(canActivate).toBeTrue();
expect(routerSpy.navigate).not.toHaveBeenCalled();
});

it('should redirect to dashboard and return false when quotes are disabled', () => {
environment.QUOTES_ENABLED = false;

const canActivate = guard.canActivate();

expect(canActivate).toBeFalse();
expect(routerSpy.navigate).toHaveBeenCalledWith(['/dashboard']);
});
});
6 changes: 6 additions & 0 deletions src/app/offerings/contact-us/contact-us.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { TranslateModule } from '@ngx-translate/core';
import { RouterTestingModule } from '@angular/router/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';

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

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

beforeEach(async () => {
await TestBed.configureTestingModule({
schemas: [NO_ERRORS_SCHEMA],
imports: [HttpClientTestingModule, RouterTestingModule, TranslateModule.forRoot()],
declarations: [ContactUsComponent]
})
.compileComponents();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { TranslateModule } from '@ngx-translate/core';
import { RouterTestingModule } from '@angular/router/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';

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

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

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ExploreDomeComponent]
schemas: [NO_ERRORS_SCHEMA],
declarations: [ExploreDomeComponent],
imports: [HttpClientTestingModule, RouterTestingModule, TranslateModule.forRoot()]
})
.compileComponents();

Expand Down
6 changes: 6 additions & 0 deletions src/app/offerings/faq/faq.component.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { TranslateModule } from '@ngx-translate/core';
import { RouterTestingModule } from '@angular/router/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';

import { FaqComponent } from './faq.component';

Expand All @@ -8,6 +12,8 @@ describe('FaqComponent', () => {

beforeEach(async () => {
await TestBed.configureTestingModule({
schemas: [NO_ERRORS_SCHEMA],
imports: [HttpClientTestingModule, RouterTestingModule, TranslateModule.forRoot()],
declarations: [FaqComponent]
})
.compileComponents();
Expand Down
Loading
Loading