Compare commits

..

6 Commits

Author SHA1 Message Date
4989ff9883 Merge pull request 'develop' (#12) from develop into prod
All checks were successful
bangararaju.kottedi.in/portfolio-admin/pipeline/head This commit looks good
Reviewed-on: #12
2026-03-06 12:14:51 +05:30
15788dbaa6 fix: update test command to use ChromeHeadless browser
Some checks are pending
bangararaju.kottedi.in/portfolio-admin/pipeline/head This commit looks good
bangararaju.kottedi.in/portfolio-admin/pipeline/pr-prod Build queued...
2026-03-06 12:11:47 +05:30
3aec2d5290 fix: update test command to use ChromeHeadlessCI browser
Some checks failed
bangararaju.kottedi.in/portfolio-admin/pipeline/head There was a failure building this commit
2026-03-06 12:07:33 +05:30
ddf92e0548 fix: update ChromeHeadless command to include --no-sandbox option
Some checks failed
bangararaju.kottedi.in/portfolio-admin/pipeline/head There was a failure building this commit
2026-03-06 11:58:30 +05:30
196ad13eea feat: update Angular dependencies and improve test configurations
Some checks failed
bangararaju.kottedi.in/portfolio-admin/pipeline/head There was a failure building this commit
2026-03-06 09:24:54 +05:30
1eca714680 refactor: enhance Jenkinsfile structure and improve stage conditions for better clarity and maintainability
Some checks failed
bangararaju.kottedi.in/portfolio-admin/pipeline/head There was a failure building this commit
2026-03-06 08:03:03 +05:30
18 changed files with 252 additions and 119 deletions

118
Jenkinsfile vendored
View File

@ -1,17 +1,21 @@
pipeline { pipeline {
agent none agent none
options { options {
buildDiscarder(logRotator(numToKeepStr: '10')) buildDiscarder(logRotator(numToKeepStr: '10'))
disableConcurrentBuilds() disableConcurrentBuilds()
} }
stages { stages {
stage('Build') { stage('Build & Test') {
agent { label getAgentLabel() } agent { label getAgentLabel() }
environment {
CHROME_BIN = "${getChromeBin()}"
}
stages { stages {
stage('Cleanup Workspace') { stage('Cleanup Workspace') {
@ -27,6 +31,14 @@ stages {
} }
stage('Inject Environment File') { stage('Inject Environment File') {
when {
not {
anyOf {
branch pattern: "feature/.*", comparator: "REGEXP"
branch pattern: "bug/.*", comparator: "REGEXP"
}
}
}
steps { steps {
configFileProvider( configFileProvider(
[configFile( [configFile(
@ -40,18 +52,43 @@ stages {
} }
} }
stage('Install & Build') { stage('Install Dependencies') {
steps {
sh 'npm ci'
}
}
stage('Run Tests') {
when {
anyOf {
branch pattern: "feature/.*", comparator: "REGEXP"
branch pattern: "bug/.*", comparator: "REGEXP"
branch 'develop'
branch 'prod'
}
}
steps {
sh 'npm run test -- --watch=false --browsers=ChromeHeadless'
}
}
stage('Build Angular App') {
when {
anyOf {
branch 'develop'
branch 'prod'
}
}
steps { steps {
sh ''' sh '''
npm ci
ng build --configuration production --base-href /admin/ ng build --configuration production --base-href /admin/
''' '''
} }
} }
} }
} }
// 🚨 Production Approval Gate
stage('Production Approval') { stage('Production Approval') {
when { when {
branch 'prod' branch 'prod'
@ -62,6 +99,14 @@ stages {
} }
stage('Deploy & Verify') { stage('Deploy & Verify') {
when {
anyOf {
branch 'develop'
branch 'prod'
}
}
agent { label getAgentLabel() } agent { label getAgentLabel() }
stages { stages {
@ -86,17 +131,16 @@ stages {
} }
} }
} }
post { post {
success { success {
echo "Deployment successful for ${env.BRANCH_NAME}" echo "Deployment successful for ${env.BRANCH_NAME}"
} }
failure { failure {
echo "❌ Deployment failed for ${env.BRANCH_NAME}" echo "Deployment failed for ${env.BRANCH_NAME}"
}
} }
}
} }
// //
@ -104,33 +148,41 @@ post {
// //
def getAgentLabel() { def getAgentLabel() {
if (env.BRANCH_NAME == 'prod') { if (env.BRANCH_NAME == 'prod') {
return 'oracle-prod' return 'oracle-prod'
} else { } else {
return 'built-in' return 'built-in'
} }
} }
def getEnvFileId() { def getEnvFileId() {
if (env.BRANCH_NAME == 'prod') { if (env.BRANCH_NAME == 'prod') {
return 'admin-prod-properties' return 'admin-prod-properties'
} else { } else {
return 'admin-uat-properties' return 'admin-uat-properties'
} }
} }
def getDeployPath() { def getDeployPath() {
if (env.BRANCH_NAME == 'prod') { if (env.BRANCH_NAME == 'prod') {
return "/var/www/bangararaju.kottedi.in/admin" return "/var/www/bangararaju.kottedi.in/admin"
} else { } else {
return "/var/www/bangararaju.kottedi.in/admin" return "/var/www/bangararaju.kottedi.in/admin"
} }
} }
def getHealthUrl() { def getHealthUrl() {
if (env.BRANCH_NAME == 'prod') { if (env.BRANCH_NAME == 'prod') {
return "https://bangararaju.kottedi.in/admin" return "https://bangararaju.kottedi.in/admin"
} else { } else {
return "https://bangararaju-uat.kottedi.in/admin" return "https://bangararaju-uat.kottedi.in/admin"
}
} }
def getChromeBin() {
if (env.BRANCH_NAME == 'prod') {
return "/snap/bin/chromium"
} else {
return "/usr/bin/chromium"
}
} }

View File

@ -79,6 +79,8 @@
"test": { "test": {
"builder": "@angular/build:karma", "builder": "@angular/build:karma",
"options": { "options": {
"main": "./src/test.ts",
"polyfills": [],
"tsConfig": "tsconfig.spec.json", "tsConfig": "tsconfig.spec.json",
"inlineStyleLanguage": "scss", "inlineStyleLanguage": "scss",
"assets": [ "assets": [

20
package-lock.json generated
View File

@ -28,6 +28,7 @@
"@angular/build": "^20.3.2", "@angular/build": "^20.3.2",
"@angular/cli": "^20.3.2", "@angular/cli": "^20.3.2",
"@angular/compiler-cli": "^20.3.0", "@angular/compiler-cli": "^20.3.0",
"@angular/platform-browser-dynamic": "^20.3.4",
"@types/express": "^5.0.1", "@types/express": "^5.0.1",
"@types/jasmine": "~5.1.0", "@types/jasmine": "~5.1.0",
"@types/node": "^20.17.19", "@types/node": "^20.17.19",
@ -754,6 +755,25 @@
} }
} }
}, },
"node_modules/@angular/platform-browser-dynamic": {
"version": "20.3.4",
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.3.4.tgz",
"integrity": "sha512-eeScVJyZLDTNrnEDDBgF/WZpZrjEszFFkuEzNQ43sbPjc5M7Noue38Nd9QZ664ZQ3a4ZpUpritfHvc55a/fl9Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
},
"engines": {
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
},
"peerDependencies": {
"@angular/common": "20.3.4",
"@angular/compiler": "20.3.4",
"@angular/core": "20.3.4",
"@angular/platform-browser": "20.3.4"
}
},
"node_modules/@angular/platform-server": { "node_modules/@angular/platform-server": {
"version": "20.3.4", "version": "20.3.4",
"resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-20.3.4.tgz", "resolved": "https://registry.npmjs.org/@angular/platform-server/-/platform-server-20.3.4.tgz",

View File

@ -44,6 +44,7 @@
"@angular/build": "^20.3.2", "@angular/build": "^20.3.2",
"@angular/cli": "^20.3.2", "@angular/cli": "^20.3.2",
"@angular/compiler-cli": "^20.3.0", "@angular/compiler-cli": "^20.3.0",
"@angular/platform-browser-dynamic": "^20.3.4",
"@types/express": "^5.0.1", "@types/express": "^5.0.1",
"@types/jasmine": "~5.1.0", "@types/jasmine": "~5.1.0",
"@types/node": "^20.17.19", "@types/node": "^20.17.19",

View File

@ -1,4 +1,5 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { About } from './about'; import { About } from './about';
@ -8,7 +9,8 @@ describe('About', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [About] imports: [About],
providers: [provideHttpClientTesting()]
}) })
.compileComponents(); .compileComponents();

View File

@ -1,4 +1,5 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { Contact } from './contact'; import { Contact } from './contact';
@ -8,7 +9,8 @@ describe('Contact', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [Contact] imports: [Contact],
providers: [provideHttpClientTesting()]
}) })
.compileComponents(); .compileComponents();

View File

@ -1,4 +1,5 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { Projects } from './projects'; import { Projects } from './projects';
@ -8,7 +9,8 @@ describe('Projects', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [Projects] imports: [Projects],
providers: [provideHttpClientTesting()]
}) })
.compileComponents(); .compileComponents();

View File

@ -1,4 +1,5 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { Resume } from './resume'; import { Resume } from './resume';
@ -8,7 +9,8 @@ describe('Resume', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [Resume] imports: [Resume],
providers: [provideHttpClientTesting()]
}) })
.compileComponents(); .compileComponents();

View File

@ -1,4 +1,5 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { AdminService } from './admin.service'; import { AdminService } from './admin.service';
@ -6,7 +7,9 @@ describe('AdminService', () => {
let service: AdminService; let service: AdminService;
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({}); TestBed.configureTestingModule({
providers: [provideHttpClientTesting()]
});
service = TestBed.inject(AdminService); service = TestBed.inject(AdminService);
}); });

View File

@ -15,11 +15,5 @@ describe('App', () => {
const app = fixture.componentInstance; const app = fixture.componentInstance;
expect(app).toBeTruthy(); expect(app).toBeTruthy();
}); });
// title rendering test removed — template doesn't render a static H1
it('should render title', () => {
const fixture = TestBed.createComponent(App);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, portfolio-admin');
});
}); });

View File

@ -1,12 +1,16 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { provideRouter } from '@angular/router';
import { AuthService } from './auth.service'; import { AuthService } from './auth.service';
describe('AuthService', () => { describe('AuthService', () => {
let service: AuthService; let service: AuthService;
beforeEach(() => { beforeEach(async () => {
TestBed.configureTestingModule({}); await TestBed.configureTestingModule({
providers: [provideRouter([]), provideHttpClientTesting(), AuthService]
}).compileComponents();
service = TestBed.inject(AuthService); service = TestBed.inject(AuthService);
}); });

View File

@ -1,4 +1,6 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { provideRouter } from '@angular/router';
import { AuthService} from './auth.service'; import { AuthService} from './auth.service';
@ -6,7 +8,9 @@ describe('AuthService', () => {
let service: AuthService; let service: AuthService;
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({}); TestBed.configureTestingModule({
providers: [provideRouter([]), provideHttpClientTesting()]
});
service = TestBed.inject(AuthService); service = TestBed.inject(AuthService);
}); });

View File

@ -1,4 +1,6 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { provideRouter } from '@angular/router';
import { OtpComponent } from './otp.component'; import { OtpComponent } from './otp.component';
@ -8,7 +10,8 @@ describe('OtpComponent', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [OtpComponent] imports: [OtpComponent],
providers: [provideRouter([]), provideHttpClientTesting()]
}) })
.compileComponents(); .compileComponents();

View File

@ -1,16 +1,29 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { of } from 'rxjs';
import { HttpInterceptorFn } from '@angular/common/http';
import { AuthInterceptor } from './auth-interceptor'; import { AuthInterceptor } from './auth-interceptor';
import { AuthService } from '../auth/auth.service';
describe('AuthInterceptor', () => { describe('AuthInterceptor', () => {
const mockAuthService: Partial<AuthService> = {
getApiKey: () => '',
currentToken: null as unknown as string | null,
refreshToken: () => of({ accessToken: 'new-token' }),
safeSetToken: () => { /* no-op for testing */ },
logout: () => { /* no-op for testing */ }
};
const interceptor: HttpInterceptorFn = (req, next) =>
TestBed.runInInjectionContext(() => AuthInterceptor(req, next));
beforeEach(() => TestBed.configureTestingModule({ beforeEach(() => TestBed.configureTestingModule({
providers: [ providers: [
AuthInterceptor { provide: AuthService, useValue: mockAuthService }
] ]
})); }));
it('should be created', () => { it('should be created', () => {
const interceptor: AuthInterceptor = TestBed.inject(AuthInterceptor);
expect(interceptor).toBeTruthy(); expect(interceptor).toBeTruthy();
}); });
}); });

View File

@ -1,4 +1,6 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { provideRouter } from '@angular/router';
import { AdminLayout } from './admin-layout'; import { AdminLayout } from './admin-layout';
@ -8,7 +10,8 @@ describe('AdminLayout', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [AdminLayout] imports: [AdminLayout],
providers: [provideRouter([]), provideHttpClientTesting()]
}) })
.compileComponents(); .compileComponents();

View File

@ -1,19 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DynamicForm } from './dynamic-form'; import { DynamicFormComponent } from './dynamic-form';
import { DynamicFormConfig } from './dynamic-form-config';
describe('DynamicForm', () => { describe('DynamicForm', () => {
let component: DynamicForm; let component: DynamicFormComponent;
let fixture: ComponentFixture<DynamicForm>; let fixture: ComponentFixture<DynamicFormComponent>;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [DynamicForm] imports: [DynamicFormComponent]
}) })
.compileComponents(); .compileComponents();
fixture = TestBed.createComponent(DynamicForm); fixture = TestBed.createComponent(DynamicFormComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
component.config = { fields: [] } as unknown as DynamicFormConfig;
fixture.detectChanges(); fixture.detectChanges();
}); });

View File

@ -1,6 +1,8 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { DynamicPopupComponent } from './dynamic-popup'; import { DynamicPopupComponent } from './dynamic-popup';
import { DynamicFormConfig } from '../dynamic-form/dynamic-form-config';
describe('DynamicPopupComponent', () => { describe('DynamicPopupComponent', () => {
let component: DynamicPopupComponent; let component: DynamicPopupComponent;
@ -8,12 +10,15 @@ describe('DynamicPopupComponent', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [DynamicPopupComponent] imports: [DynamicPopupComponent],
providers: [provideHttpClientTesting()]
}) })
.compileComponents(); .compileComponents();
fixture = TestBed.createComponent(DynamicPopupComponent); fixture = TestBed.createComponent(DynamicPopupComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
component.config = { title: 'Test', submitLabel: 'Save', fields: [] } as DynamicFormConfig;
component.data = {};
fixture.detectChanges(); fixture.detectChanges();
}); });

19
src/test.ts Normal file
View File

@ -0,0 +1,19 @@
// Test bootstrap for Karma
import { provideZonelessChangeDetection } from '@angular/core';
import { getTestBed, TestBed } from '@angular/core/testing';
import { BrowserTestingModule, platformBrowserTesting } from '@angular/platform-browser/testing';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
declare const beforeEach: (fn: () => void) => void;
getTestBed().initTestEnvironment(
BrowserTestingModule,
platformBrowserTesting()
);
beforeEach(() => {
TestBed.configureTestingModule({
providers: [provideZonelessChangeDetection(), provideHttpClient(), provideHttpClientTesting()]
});
});