Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
bd8629f
initial commit for verify
jasonmorais May 5, 2026
0d3d735
slight reworks for mock servers, forcing use of cellix packages for s…
jasonmorais May 5, 2026
a04b3a6
small changes for coverage and some general suggestions applied, snyk…
jasonmorais May 6, 2026
fde5f4d
test coverge and e2e
jasonmorais May 6, 2026
675eeec
added pipeline stage for e2e
jasonmorais May 6, 2026
12b0557
sourcery feedback changes and coverage script finding swtich
jasonmorais May 6, 2026
d222c6b
local setting load fix for issue
jasonmorais May 6, 2026
d916d5a
Revert "local setting load fix for issue"
jasonmorais May 6, 2026
9faa245
tried path of least resistance, simply have defaults in local-setting…
jasonmorais May 6, 2026
207afa7
another attempt at build pipeline run for portless
jasonmorais May 6, 2026
607c718
diagnostics for failure
jasonmorais May 6, 2026
b1ac531
more diagnostics
jasonmorais May 6, 2026
75edceb
last diagnostics attempt
jasonmorais May 6, 2026
e114564
pinning dev mod to see if this resolves issue
jasonmorais May 7, 2026
f7fc004
another test run
jasonmorais May 7, 2026
b95369e
more diagnostics
jasonmorais May 7, 2026
597cb6c
more diagnostics...
jasonmorais May 7, 2026
d90a284
diagnostic - go!
jasonmorais May 7, 2026
0cb97e9
diaganostics, once more
jasonmorais May 7, 2026
8bad2b6
once more - diagnose!
jasonmorais May 7, 2026
c8afffb
another diagnosis
jasonmorais May 7, 2026
2ebf1fa
once more, diagnose
jasonmorais May 7, 2026
3b337ea
adjustments for env variables
jasonmorais May 7, 2026
ce8e621
local fix for e2e broken from build pipeline changes
jasonmorais May 8, 2026
7956810
Merge remote-tracking branch 'origin/main' into jason/e2e-with-dev
jasonmorais May 8, 2026
c7d81c7
forcing commit to validate coverage of backend files due to lack fo s…
jasonmorais May 8, 2026
a77cb01
undo test coverage mock file change changes
jasonmorais May 8, 2026
148cbd8
undo actual code changes for this pr for coverage
jasonmorais May 11, 2026
07bf96e
second attempt to undo small code changes done for coverage
jasonmorais May 11, 2026
1721ea3
one more change un caught by ai process
jasonmorais May 11, 2026
6968eef
small snyk fixes and undoing format undo
jasonmorais May 11, 2026
ed7250f
merge: resolve conflicts from origin/main
jasonmorais May 15, 2026
87eb425
swapped back to turbo to ensure builds happen before tests, simplifie…
jasonmorais May 15, 2026
1c71225
small changes to handle differences between ci and local, to eliminat…
jasonmorais May 18, 2026
5841c0b
fix vuln for coverage
jasonmorais May 18, 2026
e63ff90
debug test
jasonmorais May 18, 2026
09c7878
added extra time and cleanup for acceptance api to void future errors…
jasonmorais May 18, 2026
a0beada
undid override per feedback
jasonmorais May 18, 2026
a4fe053
Merge remote-tracking branch 'origin/main' into jason/e2e-with-dev
jasonmorais May 21, 2026
03592de
added new scenario for header operations to get coverage, added staff…
jasonmorais May 21, 2026
71f50c6
small adjustments to tests for better verification
jasonmorais May 21, 2026
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
1 change: 0 additions & 1 deletion .snyk
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,3 @@ ignore:
reason: 'Requires upgrade of @opentelemetry/sdk-node to 0.217.0, which has type errors that break compilation. Created task to upgrade OTEL service to 2.x and resolve vulnerability that way.'
expires: '2026-07-28T00:00:00.000Z'
created: '2026-06-01T10:00:00.000Z'

5 changes: 4 additions & 1 deletion apps/api/start-dev.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ const childEnv = {
NODE_OPTIONS: `${process.env.NODE_OPTIONS ?? ''} --use-system-ca`.trim(),
};

const child = spawn('func', ['start', '--typescript', '--script-root', 'deploy/', '--port', envPort], {
// `--cors '*'` matches Host.CORS in local.settings.json but does not depend on
// that file existing — local.settings.json is gitignored, so CI has no CORS
// allowance otherwise and the UI's cross-origin GraphQL requests are blocked.
const child = spawn('func', ['start', '--typescript', '--script-root', 'deploy/', '--port', envPort, '--cors', '*'], {
stdio: 'inherit',
env: childEnv,
});
Expand Down
18 changes: 17 additions & 1 deletion build-pipeline/core/monorepo-build-stage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ stages:
echo "Testing affected packages only (PR/branch build)..."
export TURBO_SCM_BASE="origin/$(System.PullRequest.TargetBranch)"
export TURBO_CONCURRENCY=4
pnpm run test:coverage --affected
pnpm run test:coverage:affected
pnpm run merge-lcov-reports
else
echo "Testing all packages (main branch build)..."
Expand Down Expand Up @@ -281,6 +281,22 @@ stages:
${{ each pair in parameters.buildEnvSettings }}:
${{ pair.key }}: ${{ pair.value }}

# Run E2E tests
- task: Bash@3
displayName: 'Run E2E tests'
inputs:
targetType: 'inline'
script: |
set -euo pipefail
export NODE_OPTIONS=--max_old_space_size=16384
export PLAYWRIGHT_BROWSERS_PATH="$(PLAYWRIGHT_BROWSERS_PATH)"
echo "Running E2E tests..."
pnpm run test:e2e:ci
workingDirectory: ''
env:
TURBO_TELEMETRY_DISABLED: 1
PLAYWRIGHT_BROWSERS_PATH: $(PLAYWRIGHT_BROWSERS_PATH)

# Audit unused dependencies with knip (after packages are built)
- task: Bash@3
displayName: 'Audit unused dependencies with knip'
Expand Down
191 changes: 95 additions & 96 deletions build-pipeline/scripts/merge-coverage.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,105 +11,104 @@ const __dirname = dirname(__filename);
* Simple LCOV merger that combines multiple lcov.info files
*/
function processLcovContent(content, packagePath) {
const lines = content.split('\n');
const processedLines = [];
for (const line of lines) {
if (line.startsWith('SF:')) {
// Extract the file path after 'SF:'
const filePath = line.substring(3);
// Prefix with package path, ensuring no double slashes
const prefixedPath = path.join(packagePath, filePath).replaceAll('\\', '/');
processedLines.push(`SF:${prefixedPath}`);
} else {
processedLines.push(line);
}
}
return processedLines.join('\n');
const lines = content.split('\n');
const processedLines = [];

for (const line of lines) {
if (line.startsWith('SF:')) {
// Extract the file path after 'SF:'
const filePath = line.substring(3);
// Prefix with package path, ensuring no double slashes
const prefixedPath = path.join(packagePath, filePath).replaceAll('\\', '/');
processedLines.push(`SF:${prefixedPath}`);
} else {
processedLines.push(line);
}
}

return processedLines.join('\n');
}

function mergeLcovFiles() {
const rootDir = process.cwd();
const outputFile = path.join(rootDir, 'coverage', 'lcov.info');

// Create output directory
const outputDir = path.dirname(outputFile);
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}

// Find all lcov.info files
const lcovFiles = [];

function findLcovFiles(dir) {
const entries = fs.readdirSync(dir, { withFileTypes: true });

for (const entry of entries) {
const fullPath = path.join(dir, entry.name);

if (entry.isDirectory()) {
if (entry.name !== 'node_modules' && entry.name !== '.git') {
findLcovFiles(fullPath);
}
} else if (entry.name === 'lcov.info' && fullPath.includes('/coverage/')) {
lcovFiles.push(fullPath);
}
}
}

// Search in apps and packages directories
const searchDirs = ['apps', 'packages'].filter(dir =>
fs.existsSync(path.join(rootDir, dir))
);

for (const dir of searchDirs) {
findLcovFiles(path.join(rootDir, dir));
}

console.log(`Found ${lcovFiles.length} LCOV files:`);
lcovFiles.forEach(file => console.log(` - ${file}`));

if (lcovFiles.length === 0) {
console.log('No LCOV files found. Creating empty coverage file.');
fs.writeFileSync(outputFile, '');
return;
}

// Merge all LCOV files
let mergedContent = '';

for (const lcovFile of lcovFiles) {
try {
const content = fs.readFileSync(lcovFile, 'utf8');
if (content.trim()) {
// Compute the package path relative to monorepo root
const packageDir = path.dirname(path.dirname(lcovFile)); // Go up from coverage/ to package/
const packagePath = path.relative(rootDir, packageDir);

// Process the LCOV content to prefix SF: paths
const processedContent = processLcovContent(content, packagePath);

mergedContent += processedContent;
if (!processedContent.endsWith('\n')) {
mergedContent += '\n';
}
}
} catch (error) {
console.warn(`Warning: Could not read ${lcovFile}: ${error.message}`);
}
}

// Write merged content
fs.writeFileSync(outputFile, mergedContent);

console.log(`Merged coverage report written to: ${outputFile}`);
console.log(`Total size: ${mergedContent.length} characters`);

// Count records
const records = (mergedContent.match(/end_of_record/g) || []).length;
console.log(`Coverage records: ${records}`);
const rootDir = process.cwd();
const outputFile = path.join(rootDir, 'coverage', 'lcov.info');

// Create output directory
const outputDir = path.dirname(outputFile);
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir, { recursive: true });
}

// Find all lcov.info files
const lcovFiles = [];

function findLcovFiles(dir) {
const entries = fs.readdirSync(dir, { withFileTypes: true });

for (const entry of entries) {
const fullPath = path.join(dir, entry.name);

if (entry.isDirectory()) {
if (!['node_modules', '.git', '.turbo', 'dist', 'build'].includes(entry.name)) {
findLcovFiles(fullPath);
}
} else if (entry.name === 'lcov.info' && fullPath.replaceAll('\\', '/').includes('/coverage/')) {
if (fullPath !== outputFile) {
lcovFiles.push(fullPath);
}
}
}
}

const searchDirs = ['apps', 'packages'].filter((dir) => fs.existsSync(path.join(rootDir, dir)));

for (const dir of searchDirs) {
findLcovFiles(path.join(rootDir, dir));
}

console.log(`Found ${lcovFiles.length} LCOV files:`);
lcovFiles.forEach((file) => console.log(` - ${file}`));

if (lcovFiles.length === 0) {
console.log('No LCOV files found. Creating empty coverage file.');
fs.writeFileSync(outputFile, '');
return;
}

// Merge all LCOV files
let mergedContent = '';

for (const lcovFile of lcovFiles) {
try {
const content = fs.readFileSync(lcovFile, 'utf8');
if (content.trim()) {
// Compute the package path relative to monorepo root
const packageDir = path.dirname(path.dirname(lcovFile)); // Go up from coverage/ to package/
const packagePath = path.relative(rootDir, packageDir);

// Process the LCOV content to prefix SF: paths
const processedContent = processLcovContent(content, packagePath);

mergedContent += processedContent;
if (!processedContent.endsWith('\n')) {
mergedContent += '\n';
}
}
} catch (error) {
console.warn(`Warning: Could not read ${lcovFile}: ${error.message}`);
}
}

// Write merged content
fs.writeFileSync(outputFile, mergedContent);

console.log(`Merged coverage report written to: ${outputFile}`);
console.log(`Total size: ${mergedContent.length} characters`);

// Count records
const records = (mergedContent.match(/end_of_record/g) || []).length;
console.log(`Coverage records: ${records}`);
}

// Run the merger
mergeLcovFiles();
mergeLcovFiles();
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@
"start-emulator:auth-server": "pnpm --filter @cellix/mock-oauth2-server start",
"test:all": "turbo run test:all",
"test:arch": "turbo run test:arch",
"test:coverage": "turbo run test:coverage",
"test:coverage": "turbo run test:coverage test:coverage:acceptance",
"test:coverage:affected": "turbo run test:coverage test:coverage:acceptance --affected",
"test:coverage:merge": "pnpm run test:coverage && pnpm run merge-lcov-reports",
"test:e2e": "turbo run test:e2e --filter=@ocom-verification/e2e-tests",
"test:acceptance": "turbo run test:acceptance --filter=@ocom-verification/acceptance-api --filter=@ocom-verification/acceptance-ui",
"test:e2e:ci": "turbo run test:e2e:ci --filter=@ocom-verification/e2e-tests",
"test:acceptance": "turbo run test:acceptance",
"merge-lcov-reports": "node build-pipeline/scripts/merge-coverage.js",
"test:integration": "turbo run test:integration",
"test:serenity": "turbo run test:serenity",
Expand All @@ -46,7 +48,7 @@
"sonar:pr": "export PR_NUMBER=$(node build-pipeline/scripts/get-pr-number.cjs) && sonar-scanner -Dsonar.pullrequest.key=$PR_NUMBER -Dsonar.pullrequest.branch=$(git branch --show-current) -Dsonar.pullrequest.base=main",
"sonar:pr-windows": "for /f %i in ('node build-pipeline/scripts/get-pr-number.cjs') do set PR_NUMBER=%i && sonar-scanner -Dsonar.pullrequest.key=%PR_NUMBER% -Dsonar.pullrequest.branch=%BRANCH_NAME% -Dsonar.pullrequest.base=main",
"check-sonar": "node build-pipeline/scripts/check-sonar-quality-gate.cjs",
"verify": "pnpm run format:check && pnpm run test:arch && pnpm run test:coverage:merge && pnpm run knip && pnpm run audit && pnpm run snyk && pnpm run sonar:pr && pnpm run check-sonar",
"verify": "pnpm run format:check && pnpm run test:arch && pnpm run test:coverage:merge && pnpm run test:e2e && pnpm run knip && pnpm run audit && pnpm run snyk && pnpm run sonar:pr && pnpm run check-sonar",
"knip": "knip",
"snyk": "pnpm run snyk:test && pnpm run snyk:code",
"snyk:report": "pnpm run snyk:monitor && pnpm run snyk:code:report",
Expand Down
4 changes: 2 additions & 2 deletions packages/cellix/archunit-tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@
},
"scripts": {
"prebuild": "biome lint",
"build": "tsgo --build",
"watch": "tsgo --watch",
"build": "tsc --build",
"watch": "tsc --watch",
"test": "vitest run",
"test:arch": "vitest run",
"test:coverage": "pnpm run test",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ export async function startMongoMemoryReplicaSet(config: MongoMemoryReplicaSetCo
count: 1,
storageEngine: 'wiredTiger',
},
instanceOpts: [{ port: config.port }],
instanceOpts: [
{
port: config.port,
args: ['--setParameter', 'maxTransactionLockRequestTimeoutMillis=5000'],
},
],
});

const uri = replicaSet.getUri(config.dbName);
Expand Down
2 changes: 1 addition & 1 deletion packages/cellix/server-oauth2-mock-seedwork/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"test:watch": "vitest"
},
"dependencies": {
"express": "^4.22.0",
"express": "^4.22.2",
"express-rate-limit": "^8.5.1",
"jose": "^5.9.6"
},
Expand Down
6 changes: 3 additions & 3 deletions packages/ocom-verification/acceptance-api/.c8rc.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"all": true,
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "dist"],
"allowExternal": true,
"include": ["**/ocom/application-services/dist/**", "**/ocom/domain/dist/**", "**/ocom/graphql/dist/**", "**/ocom/persistence/dist/**", "**/ocom/data-sources-mongoose-models/dist/**"],
"exclude": ["**/node_modules/**"],
"reporter": ["text", "lcovonly"],
"report-dir": "coverage"
}
1 change: 1 addition & 0 deletions packages/ocom-verification/acceptance-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"scripts": {
"test:acceptance": "LOG_LEVEL=warn NODE_OPTIONS='--import tsx/esm' cucumber-js --format json:./reports/cucumber-report-api.json",
"test:acceptance:coverage": "LOG_LEVEL=warn NODE_OPTIONS='--import tsx/esm' c8 --allowExternal -- cucumber-js --format json:./reports/cucumber-report-api.json",
"test:coverage:acceptance": "LOG_LEVEL=warn NODE_OPTIONS='--import tsx/esm' c8 -- cucumber-js --format json:./reports/cucumber-report-api.json",
"verification:test:coverage:report": "c8 report --allowExternal",
"clean": "rimraf dist reports target coverage coverage-c8 coverage-vitest .c8-output"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Given, Then, When } from '@cucumber/cucumber';
import { actorCalled, notes } from '@serenity-js/core';

interface HeaderApiNotes {
identityProviderUnreachable: boolean;
signinRedirectInvoked: boolean;
fallbackTriggered: boolean;
}

let lastActorName = 'Alex';

// Header sign-in is a UI-only concern. These step bindings keep the shared
// feature in sync across layers without exercising any API behaviour.

Given('{word} visits the community site', async (actorName: string) => {
lastActorName = actorName;
const actor = actorCalled(actorName);
await actor.attemptsTo(notes<HeaderApiNotes>().set('identityProviderUnreachable', false), notes<HeaderApiNotes>().set('signinRedirectInvoked', false), notes<HeaderApiNotes>().set('fallbackTriggered', false));
});

Given('{word} visits the staff site', async (actorName: string) => {
lastActorName = actorName;
const actor = actorCalled(actorName);
await actor.attemptsTo(notes<HeaderApiNotes>().set('identityProviderUnreachable', false), notes<HeaderApiNotes>().set('signinRedirectInvoked', false), notes<HeaderApiNotes>().set('fallbackTriggered', false));
});

Given('the identity provider is unreachable', async () => {
const actor = actorCalled(lastActorName);
await actor.attemptsTo(notes<HeaderApiNotes>().set('identityProviderUnreachable', true));
});

When('{word} chooses to sign in', async (actorName: string) => {
lastActorName = actorName;
const actor = actorCalled(actorName);
const unreachable = await actor.answer(notes<HeaderApiNotes>().get('identityProviderUnreachable'));
await actor.attemptsTo(notes<HeaderApiNotes>().set('signinRedirectInvoked', !unreachable), notes<HeaderApiNotes>().set('fallbackTriggered', unreachable));
});

Then('{word} is taken to the sign-in flow', async (actorName: string) => {
const actor = actorCalled(actorName);
const invoked = await actor.answer(notes<HeaderApiNotes>().get('signinRedirectInvoked'));
if (!invoked) {
throw new Error(`Expected ${actorName} to be taken to the sign-in flow, but the sign-in handler was not invoked`);
}
});

Then('{word} can still reach the sign-in page', async (actorName: string) => {
const actor = actorCalled(actorName);
const fallback = await actor.answer(notes<HeaderApiNotes>().get('fallbackTriggered'));
if (!fallback) {
throw new Error(`Expected ${actorName} to reach the sign-in page via the fallback path, but the fallback was not triggered`);
}
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Authentication context step definitions
import './header-login.steps.ts';
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ function createNoOpApolloServerService(): ServiceApolloServer<Record<string, nev
get service(): never {
return notImplemented() as never;
},
} as unknown as ServiceApolloServer<BaseContext>;
} as unknown as ServiceApolloServer<Record<string, never>>;
}

export function createMockApplicationServicesFactory(serviceMongoose: ServiceMongoose): ApplicationServicesFactory {
Expand Down
Loading
Loading