mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
Audits what actually has tests, then closes the gaps it found. Full write-up with before/after numbers in docs/audit-test-coverage-2026-07-25.md. Measurement first: `go test ./... -coverprofile` (what CI runs) instruments each package only for itself, so code exercised through another package's tests reads as uncovered — `service` reported 36.7% against a real 85%. All analysis here uses -coverpkg=./..., and both views now have Makefile targets. Features that had zero coverage at every layer: - user blocking (db + service + the /api/v1/blocks routes) - auth lockout persistence — the DB round-trip that survives a restart - plugin install/enable/disable/uninstall and the plugin KV namespace - event replay bounds (GetMaxEventSeq, PruneEventsOlderThan) - LiveKit participant_joined webhook (replayed-token guard), the room-service client, and proxyWebSocket/copyWS - ws_proxy.rs and livekit_proxy.rs — pure helpers extracted, matching the existing tofu.rs pattern, so cert-pin and header-injection checks are testable Gaps that were hidden rather than absent: - Server/admin reported 0.3% coverage with 307 tests passing. TestSpawnDetached_* re-execs the test binary; the child inherited GOCOVERDIR and the parent's stdout, clobbering the profile and printing "[no tests to run]". Now 71.4%, and CI's uploaded artifact is correct. - vitest.config.ts excluded 2.2k LOC unexplained, including two files that already had tests. Trimmed to three entries, each justified inline. - api.HandleLiveKitHealthForTest re-implemented the handler it claimed to expose, so eight call sites tested a copy. Added a hook to the real one. Two bugs found and pinned rather than silently patched: logctx.WithGroup nests req_id under the group, and drag-reorder.ts takes one listener ref per channel but releases one per sidebar, so the count never reaches zero. Coverage: client 92.93% -> 94.87% statements (3371 -> 3572 tests) even after un-excluding hidden files; Rust 47 -> 74 tests; Go zero-coverage functions ~70 -> 21, with plugin 61->77%, admin 67->86%, db 76->84%, service 85->91%. Verified: go vet, all four build-tag variants, go test -race, -tags deadlock, vitest --coverage, cargo test --lib, cargo clippy --all-targets, playwright. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AEETs3Vh6sAHHb1jMBL75g
368 lines
12 KiB
YAML
368 lines
12 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
branches: [main, dev]
|
|
|
|
# Cancel in-progress runs for the same branch/PR
|
|
concurrency:
|
|
group: ci-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
server-build-test:
|
|
name: Server Build & Test (${{ matrix.os }})
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- os: windows-latest
|
|
binary: chatserver.exe
|
|
- os: ubuntu-latest
|
|
binary: chatserver
|
|
runs-on: ${{ matrix.os }}
|
|
defaults:
|
|
run:
|
|
working-directory: Server/
|
|
steps:
|
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
|
|
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
|
with:
|
|
go-version: "1.26"
|
|
cache-dependency-path: Server/go.sum
|
|
|
|
- name: Build server
|
|
run: go build -o ${{ matrix.binary }} -ldflags "-s -w" .
|
|
|
|
# Phase B + C build-tag matrix. Each tag variant must compile so the
|
|
# tag boundaries don't drift.
|
|
- name: Build with -tags otel (Phase B Step 8)
|
|
run: go build -tags otel ./...
|
|
- name: Build with -tags wazero (Phase C Step 9)
|
|
run: go build -tags wazero ./...
|
|
- name: Build with -tags otel,wazero (full community-hub build)
|
|
run: go build -tags otel,wazero ./...
|
|
|
|
- name: Go vulnerability check
|
|
run: go install golang.org/x/vuln/cmd/govulncheck@v1.1.4 && govulncheck ./...
|
|
|
|
# Generated sqlc output must never drift from db/queries/. One leg of
|
|
# the matrix is enough; make is not guaranteed on the Windows runner.
|
|
- name: Verify generated sqlc output (make sqlc-verify)
|
|
if: matrix.os == 'ubuntu-latest'
|
|
run: make sqlc-install sqlc-verify
|
|
|
|
# Protocol message-type constants (Go + TS) must never drift from
|
|
# docs/protocol-schema.json — the single source of truth.
|
|
- name: Verify generated protocol constants (make protocol-verify)
|
|
if: matrix.os == 'ubuntu-latest'
|
|
run: make protocol-verify
|
|
|
|
- name: Run tests with race detection and coverage
|
|
run: go test -race -timeout 20m ./... -coverprofile=coverage.out -cover
|
|
|
|
- name: Run tests with deadlock detection
|
|
run: go test -tags deadlock -count=1 ./...
|
|
|
|
- name: Upload Go coverage
|
|
if: always()
|
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
|
with:
|
|
name: go-coverage-${{ matrix.os }}
|
|
path: Server/coverage.out
|
|
retention-days: 7
|
|
|
|
- name: Lint
|
|
uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0
|
|
with:
|
|
version: v2.11.3
|
|
working-directory: Server/
|
|
|
|
client-check:
|
|
name: Client Static Checks
|
|
runs-on: windows-latest
|
|
defaults:
|
|
run:
|
|
working-directory: Client/tauri-client/
|
|
steps:
|
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
|
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
with:
|
|
node-version: 20
|
|
cache: npm
|
|
cache-dependency-path: Client/tauri-client/package-lock.json
|
|
|
|
- name: Install npm dependencies
|
|
run: npm ci
|
|
|
|
- name: Patch auto-generated Tauri TypeScript bindings
|
|
working-directory: Client/tauri-client/
|
|
# tauri-typegen generates an Event type that is intentionally unused in app code.
|
|
# Rename it to _Event so @typescript-eslint/no-unused-vars does not fail.
|
|
run: |
|
|
node -e "
|
|
const fs = require('fs');
|
|
const p = 'src/generated/events.ts';
|
|
if (fs.existsSync(p)) {
|
|
let c = fs.readFileSync(p, 'utf8');
|
|
c = c.replace(/^type Event\b/gm, 'type _Event').replace(/^interface Event\b/gm, 'interface _Event');
|
|
c = c.replace(/,\s*type Event\s*(?=\})/g, ' '); // unused named import from @tauri-apps/api/event
|
|
fs.writeFileSync(p, c);
|
|
console.log('Patched: renamed Event -> _Event in generated/events.ts');
|
|
} else {
|
|
console.log('src/generated/events.ts not found, skipping patch.');
|
|
}
|
|
"
|
|
|
|
- name: Security audit (npm)
|
|
run: npm audit --audit-level=high
|
|
|
|
- name: Oxlint (fast correctness checks)
|
|
run: npx oxlint src/
|
|
|
|
- name: TypeScript check
|
|
run: npx tsc --noEmit
|
|
|
|
- name: ESLint (type-aware rules)
|
|
run: npx eslint src/
|
|
|
|
- name: Prettier format check
|
|
run: npx prettier --check "src/**/*.ts" "tests/**/*.ts"
|
|
|
|
- name: Knip (unused code & deps)
|
|
run: npx knip || true
|
|
|
|
# Unit tests live in their own job so a suite failure is visible as exactly one
|
|
# failing check instead of masking the static gates above. The suite is GREEN
|
|
# and must stay green — never "fix" a failing test by editing its assertions.
|
|
client-tests:
|
|
name: Client Unit Tests
|
|
runs-on: windows-latest
|
|
defaults:
|
|
run:
|
|
working-directory: Client/tauri-client/
|
|
steps:
|
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
|
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
with:
|
|
node-version: 20
|
|
cache: npm
|
|
cache-dependency-path: Client/tauri-client/package-lock.json
|
|
|
|
- name: Install npm dependencies
|
|
run: npm ci
|
|
|
|
- name: Run unit tests with coverage
|
|
run: npx vitest run --coverage --reporter=default
|
|
|
|
- name: Upload client coverage
|
|
if: always()
|
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
|
with:
|
|
name: client-coverage
|
|
path: Client/tauri-client/coverage/
|
|
retention-days: 7
|
|
|
|
# Rust unit tests used to live inside tauri-build, which only runs on PRs to
|
|
# main — so #[cfg(test)] code never ran on pushes or on PRs to dev, and could
|
|
# rot for a whole release cycle. This job runs them on every event. Clippy is
|
|
# run with --all-targets here (tauri-build's lib-only clippy skips test code).
|
|
rust-tests:
|
|
name: Rust Unit Tests
|
|
runs-on: ubuntu-22.04
|
|
defaults:
|
|
run:
|
|
working-directory: Client/tauri-client/src-tauri/
|
|
steps:
|
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
|
|
- name: Install Linux system dependencies
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y \
|
|
libwebkit2gtk-4.1-dev \
|
|
libgtk-3-dev \
|
|
libayatana-appindicator3-dev \
|
|
libsecret-1-dev \
|
|
libasound2-dev \
|
|
libssl-dev \
|
|
librsvg2-dev
|
|
|
|
- name: Install Rust
|
|
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
|
|
with:
|
|
components: clippy
|
|
|
|
- name: Rust cache
|
|
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
|
with:
|
|
workspaces: Client/tauri-client/src-tauri
|
|
|
|
- name: Clippy lint (including test targets)
|
|
run: cargo clippy --all-targets -- -D warnings
|
|
|
|
- name: Rust unit tests
|
|
run: cargo test --lib
|
|
|
|
# Playwright e2e against the mocked-Tauri dev server. Non-blocking for now
|
|
# (backlog #10): the suite has never run in CI, so it gets a soak period
|
|
# before it becomes a gate. Remove continue-on-error to promote it.
|
|
# The native config (playwright.config.native.ts) is deliberately not wired
|
|
# up — it needs a real server and a built desktop binary.
|
|
client-e2e:
|
|
name: Client E2E (Playwright, non-blocking)
|
|
runs-on: ubuntu-latest
|
|
continue-on-error: true
|
|
defaults:
|
|
run:
|
|
working-directory: Client/tauri-client/
|
|
steps:
|
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
|
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
with:
|
|
node-version: 20
|
|
cache: npm
|
|
cache-dependency-path: Client/tauri-client/package-lock.json
|
|
|
|
- name: Install npm dependencies
|
|
run: npm ci
|
|
|
|
- name: Install Playwright browser
|
|
run: npx playwright install --with-deps chromium
|
|
|
|
- name: Run Playwright tests
|
|
run: npx playwright test --config=playwright.config.ts
|
|
|
|
- name: Upload Playwright report
|
|
if: always()
|
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
|
with:
|
|
name: playwright-report
|
|
path: |
|
|
Client/tauri-client/playwright-report/
|
|
Client/tauri-client/test-results/
|
|
retention-days: 7
|
|
|
|
server-docker-build:
|
|
name: Server Docker Build (verify)
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
|
|
|
|
- name: Build image (no push)
|
|
uses: docker/build-push-action@14487ce63c7a62a4a324b0bfb37086795e31c6c1 # v6.16.0
|
|
with:
|
|
context: Server/
|
|
push: false
|
|
build-args: VERSION=ci
|
|
cache-from: type=gha
|
|
cache-to: type=gha,mode=max
|
|
|
|
# Full Tauri build only on PRs to main (expensive: ~15 min x2 multiplier)
|
|
tauri-build:
|
|
name: Tauri Full Build (${{ matrix.os }})
|
|
needs: client-check
|
|
if: github.event_name == 'pull_request' && github.base_ref == 'main'
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- os: windows-latest
|
|
- os: ubuntu-22.04
|
|
- os: ubuntu-22.04-arm
|
|
runs-on: ${{ matrix.os }}
|
|
defaults:
|
|
run:
|
|
working-directory: Client/tauri-client/
|
|
steps:
|
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
|
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
with:
|
|
node-version: 20
|
|
cache: npm
|
|
cache-dependency-path: Client/tauri-client/package-lock.json
|
|
|
|
- name: Install Linux system dependencies
|
|
if: startsWith(matrix.os, 'ubuntu')
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y \
|
|
libwebkit2gtk-4.1-dev \
|
|
libgtk-3-dev \
|
|
libayatana-appindicator3-dev \
|
|
libsecret-1-dev \
|
|
libasound2-dev \
|
|
libssl-dev \
|
|
patchelf \
|
|
librsvg2-dev \
|
|
xdg-utils
|
|
|
|
- name: Install Rust
|
|
uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable
|
|
with:
|
|
components: clippy
|
|
|
|
- name: Rust cache
|
|
uses: swatinem/rust-cache@9d47c6ad4b02e050fd481d890b2ea34778fd09d6 # v2.7.8
|
|
with:
|
|
workspaces: Client/tauri-client/src-tauri
|
|
|
|
- name: Install npm dependencies
|
|
run: npm ci
|
|
|
|
- name: Install tauri-typegen
|
|
run: cargo install tauri-typegen@0.5.0 --quiet
|
|
|
|
- name: Generate TypeScript IPC bindings
|
|
working-directory: Client/tauri-client/
|
|
run: cargo tauri-typegen generate
|
|
|
|
- name: Fix generated TypeScript bindings (tauri-typegen 0.5.0 workaround)
|
|
working-directory: Client/tauri-client/
|
|
# tauri-typegen 0.5.0 cannot map serde_json::Value to a TS type — patch post-generation.
|
|
# Duplicate events are avoided at source by using one emit() call site per event name.
|
|
run: |
|
|
node -e "
|
|
const fs = require('fs');
|
|
const tp = fs.readFileSync('src/generated/types.ts', 'utf8');
|
|
if (!tp.includes('export type Value')) {
|
|
fs.writeFileSync('src/generated/types.ts', tp.replace(
|
|
'export interface CredentialData',
|
|
'export type Value = unknown;\n\nexport interface CredentialData'
|
|
));
|
|
}
|
|
console.log('Generated bindings patched.');
|
|
"
|
|
|
|
- name: Clippy lint (Rust)
|
|
working-directory: Client/tauri-client/src-tauri/
|
|
run: cargo clippy -- -D warnings
|
|
|
|
# Rust unit tests moved to the standalone `rust-tests` job so they run on
|
|
# every event, not just PRs to main.
|
|
|
|
- name: Security audit (Rust dependencies)
|
|
working-directory: Client/tauri-client/src-tauri/
|
|
run: |
|
|
cargo install cargo-audit@0.22.1 --quiet
|
|
cargo audit
|
|
|
|
- name: Build Tauri app
|
|
env:
|
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
|
run: npm run tauri build
|