Skip to main content

CI/CD Strategy

Project: Software Design Web Application  |  Team Size: 6  |  Hosting: Gitea (University Server)  |  CI/CD Platform: Gitea Actions  |  Duration: 3 Months

1. Purpose

This document defines the Continuous Integration and Continuous Deployment (CI/CD) workflow that all team members must follow throughout the project.

Objectives
  • Ensure every feature is tested before integration.
  • Detect bugs as early as possible.
  • Prevent broken code from entering the shared codebase.
  • Maintain a deployable project at all times.
  • Encourage automated testing rather than manual verification.
  • Improve software quality and team confidence.

The CI/CD pipeline is a mandatory quality gate for all code entering the repository.

2. What is CI/CD?

Automatically building and testing the project whenever code is pushed or a Pull Request is opened, instead of waiting until the end of the project to discover problems.

CI answers: "Does this new code still work with the rest of the project?"

3. Current CI Pipeline — what actually runs

CI is live via Gitea Actions, split into separate workflow files by concern rather than one monolithic pipeline — this keeps each job fast and relevant to what actually changed:

Workflow fileWhat it does
client-ci.ymlLints and builds the frontend (client workspace)
backend-ci.ymlRuns backend service tests
docker.ymlBuilds Docker images
test.ymlA minimal sanity check (runs echo) confirming the Actions runner itself is working — not a quality gate, just infrastructure verification

4. Development Workflow

  1. Pull the latest develop.
  2. Create a feature branch.
  3. Implement the assigned feature.
  4. Write tests for all new code.
  5. Run tests locally.
  6. Ensure the project builds successfully.
  7. Commit changes.
  8. Push the feature branch.
  9. Wait for CI pipeline to complete.
  10. Open a Pull Request only if all checks pass.
  11. Obtain code review.
  12. Merge into develop.

5. Testing Policy

Testing is mandatory. Every feature must include appropriate automated tests. No feature is considered complete until its tests have been written.

Required Rule

If you write code, you must write tests for that code.

Minimum expectations for every new feature: unit tests, edge case testing, error handling tests. Where appropriate, also add integration, API, and UI/component tests.

6. Types of Tests, and the tooling actually in use

Test TypePurposeTooling in this project
Unit TestsTest individual functions or classes in isolationBackend: Node's built-in node:test + node:assert/strict, per service
Integration/API TestsVerify multiple components / real HTTP routes work togetherBackend: real in-process Express servers (app.listen(0)) hit with fetch, no Supertest
Component TestsVerify frontend components behave correctlyFrontend: Vitest (vitest run)
End-to-End Tests (optional)Simulate real user behaviour through the applicationNot currently in use

Backend services follow a dependency-injection pattern — service classes/functions take their dependencies (repository calls, cross-service HTTP clients) as constructor arguments with real defaults, so tests can substitute plain fake objects instead of trying to mock live module bindings. This exists specifically because Node's node:test + tsx's ESM-style module loading don't reliably support t.mock.method() against import * as X module namespaces in this setup — worth knowing before assuming that approach will work in a new service.

7. Local Testing Before Pushing

Before pushing, run the full test suite locally. The project should build successfully and pass all tests. Don't rely on CI to catch mistakes you could've caught locally.

Pre-Push Checklist
  • Project builds successfully
  • All automated tests pass
  • No linting errors (npm run lint — oxlint on the frontend, ESLint-family tooling per backend service)
  • No debugging code remains
  • No commented-out code remains
  • New functionality includes tests
  • Existing tests continue to pass
Formatting isn't a separate check

There's no npm run format:check script — Prettier is installed at the root but only runs via Husky's pre-commit hook (lint-staged, applying prettier --write to staged files automatically). This means formatting is fixed for you at commit time locally, rather than something CI verifies after the fact. If you commit without going through the normal Husky-enabled flow (e.g. --no-verify), nothing else catches unformatted code before merge.

8. Continuous Integration Pipeline — actual stages

The stages that genuinely run, per workflow file:

WorkflowStageWhat happens
client-ci.ymlCheckout, installStandard actions/checkout + npm install
client-ci.ymlLintRuns the client's lint script
client-ci.ymlBuildtsc -b && vite build
backend-ci.ymlCheckout, installStandard actions/checkout + npm install
backend-ci.ymlTestRuns each backend service's npm run test (tsx --test)
docker.ymlBuild imagesBuilds Docker images per service, per the project's Dockerfiles

There is currently no separate static-analysis-only stage, no coverage generation stage, and no formatting-check stage in CI — see Sections 7 and 11 for what's covered instead/elsewhere.

9. Pull Request Requirements

A Pull Request cannot be merged unless:

  • CI pipeline succeeds (client-ci.yml, backend-ci.yml, and docker.yml all pass for whatever they cover).
  • Required review is approved.

If any CI job fails, the Pull Request must remain open until resolved.

10. Branch Protection Rules

  • No direct pushes
  • Pull Requests only
  • Passing CI required
  • Review approval required

These protections ensure that all shared code has been automatically validated before it reaches either branch.

11. Testing Policy

Every PR that adds or changes backend logic should include tests covering:

  • The main success case
  • At least one meaningful failure case (not just "throws an error" — the specific failure a user could actually trigger: wrong owner, missing resource, invalid input, already-in-a-terminal-state, etc.)
  • Any ownership/permission check, tested from both sides (the allowed user succeeds, a different user is rejected)

Frontend components with real logic (not just static markup) should have at least a basic render/interaction test where practical.

What review actually checks, in place of a coverage number: does this PR's tests genuinely exercise the interesting cases for what changed, or only the happy path? Reviewers should say so explicitly in review comments if test coverage looks thin for what's being added — see Git Methodology's Code Review section.

12. Handling Failed Tests

  1. Read the error logs.
  2. Identify the failing test or build step.
  3. Fix the issue locally.
  4. Run the full test suite again.
  5. Commit the fix.
  6. Push the updated branch.
  7. Verify that the pipeline passes.
danger

Do not merge code with failing tests.

13. Testing Responsibilities

Every developer is responsible for writing tests for their own code, updating existing tests when modifying functionality, keeping tests maintainable, avoiding unnecessary duplication, and keeping tests fast and deterministic. Reviewers verify adequate tests accompany each Pull Request.

14. Actual Project Structure

Backend services are independent, each with their own tests folder — there is no single shared tests/ directory at the repo root. See the Project Plan's folder structure section for the full layout; the CI-relevant pieces:

backend/services/<service_name>/
src/
tests/ # node:test files for this service only
package.json # "test": "tsx --test src/tests/*.test.ts"

client/
src/
(component tests colocated or in a test folder, run via `vitest run`)

.gitea/
workflows/
client-ci.yml
backend-ci.yml
docker.yml
test.yml

15. Example CI Pipeline — backend

16. Actual Gitea Workflow — backend-ci.yml (representative example)

name: backend-ci

on:
push:
branches:
- feature/**
- bugfix/**
- refactor/**
- docs/**
- test/**
pull_request:
branches:
- develop
- main
paths:
- "backend/**"

jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 24

- name: Install Dependencies
run: npm install

- name: Run Lint
run: npm run lint --workspaces --if-present

- name: Run Tests
run: npm run test --workspaces --if-present

- name: Build
run: npm run build --workspaces --if-present

client-ci.yml follows the same shape, scoped to the client workspace only, without a test step (frontend tests are run separately via vitest run, not currently wired into this workflow — worth adding if frontend test coverage grows).

17. Definition of Done

A task is only complete when:

  • The feature has been implemented.
  • Tests have been written.
  • All tests pass locally.
  • CI pipeline passes (client-ci.yml / backend-ci.yml / docker.yml, as relevant).
  • Pull Request has been reviewed.
  • Pull Request has been approved.
  • Pull Request has been merged.

18. Summary

RuleRequirement
Write tests for all new code✅ Required
Run tests before pushing✅ Required
CI pipeline must pass✅ Required
Pull Request required✅ Required
Code review required✅ Required
No direct pushes to develop✅ Required
No direct pushes to main✅ Required
Merge only after passing CI✅ Required
Fix failing tests immediately✅ Required
Coverage tooling / thresholds⚠️ Not configured
Automated formatting check in CI⚠️ Not configured — enforced locally via Husky pre-commit instead

19. Team Agreement

By contributing to this repository, each team member agrees to:

  • Write automated tests for all new functionality.
  • Never merge code that fails the CI pipeline.
  • Run tests locally before pushing changes.
  • Review teammates' Pull Requests thoroughly.
  • Help maintain a stable, deployable codebase throughout the project.

Following this CI/CD strategy will help ensure a reliable development process, reduce integration issues, and maintain high software quality from the first sprint to the final release.