Skip to content

test: comprehensive test coverage improvement (60% → 82.5%)#502

Merged
CybotTM merged 16 commits intomainfrom
feat/test-coverage-all
Feb 26, 2026
Merged

test: comprehensive test coverage improvement (60% → 82.5%)#502
CybotTM merged 16 commits intomainfrom
feat/test-coverage-all

Conversation

@CybotTM
Copy link
Member

@CybotTM CybotTM commented Feb 26, 2026

Summary

  • Increase overall statement coverage from ~60% to 82.5% (+22.5 percentage points)
  • Add 7,561 lines of new test code across 31 test files covering all packages
  • Fix linter issues (testifylint, goconst, gofumpt, gci, misspell, dupl, copyloopvar)
  • Refactor duplicate enable/disable job handlers into shared toggleJobHandler
  • Fix concurrency issues, CSRF race condition, auth panic, and other bugs found during testing

Coverage by Package

Package Before After
core/domain ~70% 100%
core/ports ~60% 100%
metrics ~50% 100%
test ~80% 100%
config ~90% 97.8%
core/adapters/mock ~90% 97.1%
test/testutil ~80% 96.2%
core ~65% 90.1%
middlewares ~55% 88.1%
web ~60% 84.0%
cli ~50% 78.9%

New Test Files (26 files)

Phase 1 - Domain & Ports:

  • core/domain/{errors,event,exec,image,service}_test.go
  • core/ports/docker_test.go, test/testlogger_test.go

Phase 2 - Core Jobs:

  • core/{runjob,execjob,runservice}_unit_test.go
  • core/{resilient_job,shutdown_unit,clock_unit}_test.go

Phase 3 - Middlewares:

  • Extended: middlewares/{webhook,preset,dedup,sanitize,mail,overlap,save,slack}_test.go

Phase 4-6 - CLI, Web, Metrics:

  • cli/{config_ext,config_validate_ext,daemon_ext,doctor_ext,progress_ext}_test.go
  • web/{auth_secure_ext,server_ext}_test.go, metrics/prometheus_ext_test.go

Phase 7-8 - Docker Adapter & Partials:

  • core/adapters/docker/{container_convert,service_convert}_test.go
  • Extended: cli/config_decode_test.go, middlewares/{restore,preset_cache,webhook_config,webhook_security}_test.go

Test plan

  • All tests pass: go test ./... -count=1 (14 packages OK)
  • Linter clean: golangci-lint run ./... (0 issues)
  • Coverage verified: 82.5% total statement coverage
  • No race conditions: tests use t.Parallel() throughout

- Snapshot onJobComplete callback under s.mu.RLock in runWithCtx to
  prevent a data race if SetOnJobComplete is called concurrently
- Document why DisableJob/EnableJob holding s.mu while calling go-cron's
  PauseEntryByName/ResumeEntryByName is deadlock-free
- Document the concurrency semantics of concurrencySemaphore.resize
  (safe before Start, best-effort after)
- Add rationale comment for BuildWorkflowDependencies fail-open design
- Fix TestSchedulerStart_TriggeredJobStartup: remove dead code (unused
  completedJobs map and completedMu), add assertions for regularStartup
  execution and completedCount callback verification
- Fix TestStopWithTimeout_Timeout: add meaningful assertions verifying
  timeout error is returned, function returns promptly, and scheduler
  stops accepting new jobs after timeout
- Add TestCollectDependencyEdges_NonBareJobSkipped: verify non-BareJob
  types are gracefully skipped (no edges, no panic)
- Add TestCollectDependencyEdges_MixedJobTypes: verify mixed job types
  only collect edges from *BareJob instances
- Add TestWireEdges_NonCycleError: verify ErrWorkflowInvalid is returned
  for non-cycle errors (entry not found in cron)
- Add TestDisableJob_Idempotent: verify calling DisableJob twice on same
  job does not error and job state remains consistent
- Add t.Parallel() to all 22 web/server_test.go top-level tests and
  verify workflow_test.go already has t.Parallel() everywhere
- Add validateJobName check to createJobHandler (was missing while
  other handlers already had it)
- Protect Server.origins map with sync.RWMutex to prevent concurrent
  map access panics from HTTP handler goroutines
- Add Collector.adjustGauge method to atomically read+write gauge
  values, fixing TOCTOU race in RecordJobStart, RecordJobComplete,
  JobMetrics.JobStarted, and JobMetrics.JobCompleted
- Guard IsRunning() against nil cron field, matching IsJobRunning() and
  EntryByName() patterns
- Remove unused wg sync.WaitGroup field and its Wait() calls from
  StopWithTimeout and StopAndWait (no Add/Done calls exist after
  migration to go-cron lifecycle management)
- Copy dockerOpsCount and dockerErrorsCount maps in GetDockerMetrics()
  before returning, preventing data races after the RLock is released
- Fix nil pointer dereference in authStatusHandler when ValidateToken
  returns nil data for expired/invalid tokens
- Add POST method enforcement on all mutation endpoints (run, disable,
  enable, create, update, delete) to reject non-POST requests with 405
- Add done channel to rateLimiter to stop cleanup goroutine on shutdown,
  preventing goroutine leaks; close old rate limiter in
  RegisterHealthEndpoints before creating a new one
- Change ValidateCSRFToken from RLock to Lock since delete() is a write
  operation, fixing a data race
- Also delete expired CSRF tokens in ValidateCSRFToken early-return path
- Add done channel and Close() method to SecureTokenManager so the
  cleanup goroutine can be stopped, preventing goroutine leaks
- Call tokenManager.Close() in Server.Shutdown for proper cleanup
- Make rateLimiter.close() idempotent via sync.Once to prevent panics
  on double-close
…ator

Add unit tests for previously uncovered easy-win targets:
- core/domain: errors (typed errors + helpers), events, image parsing,
  task states, exec hijacked response
- core/ports: Docker client functional options
- test: all testlogger Handler methods and assertion helpers
- test/testutil: T.Failed(), WithMessage, EventuallyWithT timeout,
  Never immediate-true
- config: validateSliceField with string/int/empty/nil slices

Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
Add 102 unit tests across 6 files covering core job types:

- runjob_unit_test.go: RunJob lifecycle (create, start, wait, logs, delete),
  container building, image handling, max runtime timeout
- execjob_unit_test.go: ExecJob exit code handling, RunWithStreams,
  InitializeRuntimeFields
- runservice_unit_test.go: RunServiceJob service lifecycle, build, task
  status tracking, timeout, delete with not-found handling
- resilient_job_test.go: ResilientJobExecutor with retry, rate limiting,
  non-retryable errors, metrics recording
- shutdown_unit_test.go: ShutdownManager signals, hook priorities, timeout,
  GracefulServer/Scheduler graceful stop, concurrent shutdown safety
- clock_unit_test.go: RealClock, FakeClock, CronClock timers, tickers,
  sleep edge cases (zero/negative), multiple timer coordination

All tests use table-driven subtests with t.Parallel() and the existing
mock infrastructure (mock.DockerClient with type assertions, test.Handler).

Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
Add table-driven tests for webhook, preset, dedup, sanitize middleware
packages plus ContinueOnStop method tests for all middleware types.

Coverage targets:
- webhook_test.go: WebhookManager.GetMiddlewares/GetGlobalMiddlewares,
  NewWebhookMiddleware, ContinueOnStop, Run dispatching, getJobType
  (all 5 variants), getExecutionStatus, ParseWebhookNames
- preset_test.go: AddLocalPresetDir, loadFromFile (valid/missing/invalid),
  loadFromGitHub (remote disabled/untrusted), loadFromURL (httptest server,
  non-200, remote disabled, SSRF blocked), isTrustedSource, matchGlobPattern
- dedup_test.go: DedupStore.Len, InitNotificationDedup,
  StartCleanupRoutine
- sanitize_test.go: SanitizePath, SanitizeFilename package-level wrappers
- ContinueOnStop: Mail (true), Overlap (false), Save (true), Slack (true)

Signed-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>
Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
Phase 4 - CLI package:
- config_validate_ext_test.go: validateDurationGTE (above/equal/below min,
  zero boundaries, large duration), invalid param, non-struct validation
  error path, formatValidationError all tag branches
- daemon_ext_test.go: restoreJobHistory (disabled, enabled-no-files,
  nonexistent folder), waitForServerWithErrChan error channel, applyOptions
  (nil config, all fields), applyConfigDefaults, Config accessor
- doctor_ext_test.go: checkDockerImages (no RunJobs skip, config parse
  error, Docker init error), checkDocker (no Docker jobs skip, config
  parse error), checkSchedules config error, checkWebAuth (missing
  username, missing password, auth disabled), outputJSON, outputHuman
- config_ext_test.go: BuildFromFile (invalid glob, missing file, invalid
  INI, multiple files), BuildFromString (invalid syntax, unknown keys),
  resolveConfigFiles, latestChanged, iniConfigUpdate, parseGlobalAndDocker,
  getKnownKeysForJobType, logUnknownKeyWarnings, logJobUnknownKeyWarnings
- progress_ext_test.go: ProgressIndicator update/stop/start terminal and
  non-terminal modes, ProgressReporter step/complete/renderProgressBar

Phase 5 - Web package:
- auth_secure_ext_test.go: CleanupOldLimiters, ValidateCSRFToken
  (valid, one-time, missing, expired), cleanup expired tokens/CSRF,
  HashPassword, empty secret key, Close multiple, RevokeToken, getClientIP
- server_ext_test.go: csrfTokenHandler (no auth, with auth),
  authStatusHandler (unauthenticated, no auth, invalid token),
  method-not-allowed for run/disable/enable job handlers

Phase 6 - Metrics package:
- prometheus_ext_test.go: RecordJobStart counter+gauge, RecordJobComplete
  all fields including panic/zero/large duration, RecordJobScheduled,
  adjustGauge (non-existent, wrong type), full lifecycle integrity

Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
… (phases 7-8)

Phase 7: Docker adapter conversion function tests (pure, no Docker daemon)
- container_convert_test.go: convertToContainerConfig, convertToHostConfig,
  convertToNetworkingConfig, convertToEndpointSettings, convertToPortMap, convertToMount
- service_convert_test.go: convertToSwarmSpec, convertFromSwarmService, convertFromSwarmTask

Phase 8: Extend partial coverage across cli and middleware packages
- cli/config_decode_test.go: weakDecodeConsistent, extractMapstructureKeys, mergeUsedKeys
- middlewares/mail_test.go: from() formatting, ContinueOnStop, only-on-error, executionLabel
- middlewares/save_test.go: ContinueOnStop, RestoreHistory config, save-on-error with failure
- middlewares/restore_test.go: empty job name, non-JSON files, multi-job, corrupted fields
- middlewares/preset_cache_test.go: corrupted metadata, URL collision, expired disk, isMetaFile
- middlewares/webhook_config_test.go: negative values, URL-only, all triggers, preserve defaults
- middlewares/webhook_security_test.go: whitelist blocking, invalid schemes, test overrides

Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
- Remove Go 1.22+ copyloopvar in shutdown test
- Fix misspelling (sanitises -> sanitizes)
- Use testifylint-compliant assertions (InDelta, Less, Positive, Empty, require.Error)
- Fix gci/gofumpt import ordering and formatting
- Refactor duplicate enable/disable handlers into toggleJobHandler

Signed-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>
Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
Signed-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>
Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
Copilot AI review requested due to automatic review settings February 26, 2026 00:24
@gemini-code-assist
Copy link

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@CybotTM CybotTM enabled auto-merge February 26, 2026 00:24
@github-actions
Copy link

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated approval for solo maintainer project

All CI checks passed. See SECURITY.md for compensating controls.

@github-actions
Copy link

✅ Mutation Testing Results

Mutation Score: 86.67% (threshold: 60%)

✨ Good job! Mutation score meets the threshold.

What is mutation testing?

Mutation testing measures test quality by introducing small changes (mutations) to the code and checking if tests detect them. A higher score means better test effectiveness.

  • Killed mutants: Tests caught the mutation (good!)
  • Survived mutants: Tests missed the mutation (needs improvement)

@CybotTM CybotTM added this pull request to the merge queue Feb 26, 2026
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR substantially increases automated test coverage across the Ofelia codebase while also hardening several runtime paths (web server request handling, token/rate-limiter lifecycle, scheduler concurrency, and metrics gauge updates).

Changes:

  • Add extensive new/extended unit tests across core, web, middlewares, cli, config, metrics, and adapter packages to raise total coverage to ~82.5%.
  • Harden web server behavior: validate job names, enforce HTTP methods, improve status codes, prevent auth/status panic, and add shutdown cleanup for background goroutines.
  • Improve concurrency safety in several components (origins map locking, CSRF token validation mutation locking, atomic gauge adjustments, scheduler callback snapshotting, map-copying in metrics).

Reviewed changes

Copilot reviewed 51 out of 51 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
web/server.go Adds job-name validation, method checks, safer error/status handling, origins map locking, and cleanup of token/rate-limiter goroutines on shutdown.
web/middleware.go Adds stoppable rate-limiter cleanup goroutine and documents X-Forwarded-For trust assumptions.
web/auth_secure.go Makes CSRF validation safe for one-time-use + expiry cleanup; adds Close() to stop cleanup goroutine.
web/server_test.go Marks many existing web server tests as parallel; updates expectations for new HTTP status semantics.
web/server_more_test.go Aligns run/update handler expectations with new status-code behavior.
web/server_mutation_test.go Adds validateJobName edge-case coverage.
web/server_ext_test.go Adds coverage for CSRF/auth endpoints and method-not-allowed paths.
web/auth_secure_ext_test.go Adds unit coverage for secure auth helpers (CSRF, token cleanup, hashing, IP parsing).
test/testutil/eventually_test.go Adds tests for Eventually/Never helpers and error-collecting test harness behavior.
test/testlogger_test.go Adds comprehensive tests for test logger/handler utilities.
middlewares/webhook_test.go Adds additional table-driven coverage for webhook manager/middleware helpers.
middlewares/webhook_security_test.go Adds coverage for additional validation branches and test override hooks.
middlewares/webhook_config_test.go Adds coverage for config validation/default behaviors and triggers.
middlewares/slack_test.go Adds ContinueOnStop coverage.
middlewares/save_test.go Adds ContinueOnStop + restore/save edge-case coverage.
middlewares/sanitize_test.go Adds package-level sanitizer function coverage.
middlewares/restore_test.go Adds coverage for restore edge cases (skips, multi-job matching, corrupted JSON).
middlewares/preset_test.go Adds coverage for preset loader paths (file, GitHub/url, trust matching, parsing defaults).
middlewares/preset_cache_test.go Adds coverage for cache disk error paths and isMetaFile classification.
middlewares/overlap_test.go Adds ContinueOnStop coverage.
middlewares/mail_test.go Adds ContinueOnStop + additional mail/config logic coverage.
middlewares/dedup_test.go Adds coverage for store length, init behavior, and cleanup routine.
metrics/prometheus.go Introduces atomic gauge adjustment helper to avoid TOCTOU races for running-gauge updates.
metrics/prometheus_test.go Adds unit coverage for RecordJobStart/Complete/Scheduled lifecycle hooks.
metrics/prometheus_ext_test.go Adds additional edge-case coverage for job lifecycle metrics and adjustGauge behavior.
core/scheduler.go Adds nil-safe IsRunning, improves onJobComplete callback race safety, updates concurrency comments, and enables idempotent EnableJob for already-active jobs.
core/scheduler_mutation_test.go Extends scheduler mutation coverage (startup behavior, idempotency, stop timeout determinism, job lookup fallbacks, active/any job getters).
core/workflow_test.go Adds edge-case coverage for workflow edge collection and wiring error classification.
core/performance_metrics.go Avoids post-lock data races by returning copies of internal docker ops/error maps.
core/common.go Replaces magic struct-tag string with a constant for hashing tag checks.
core/shutdown_unit_test.go Adds unit coverage for shutdown manager + graceful server/scheduler behavior.
core/clock_unit_test.go Adds coverage for real/fake/cron clock behavior.
core/execjob_unit_test.go Adds unit coverage for ExecJob behavior and exit-code handling using mocks.
core/runservice_unit_test.go Adds unit coverage for RunServiceJob behavior and error paths using mocks.
core/resilient_job_test.go Adds coverage for resilient executor behavior (retry, rate limit, metrics recording).
core/ports/docker_test.go Adds unit coverage for docker client option helpers.
core/domain/errors_test.go Adds unit coverage for domain errors and helper predicates.
core/domain/event_test.go Adds unit coverage for event helper methods.
core/domain/exec_test.go Adds unit coverage for HijackedResponse close semantics.
core/domain/image_test.go Adds unit coverage for repository/tag parsing behavior.
core/domain/service_test.go Adds unit coverage for terminal task state classification.
core/adapters/docker/service_convert_test.go Adds broad conversion coverage between domain and swarm types.
config/validator_test.go Adds coverage for slice-field validation behavior.
cli/config_ext_test.go Adds coverage for config build/update, glob resolution, warnings, and metadata handling.
cli/config_decode_test.go Adds coverage for weak decode and metadata extraction/merging edge cases.
cli/config_validate_ext_test.go Adds coverage for duration validator + error formatting branches.
cli/daemon_ext_test.go Adds coverage for daemon option application and history restore behavior.
cli/doctor_ext_test.go Adds coverage for doctor command checks and output formatting paths.
cli/progress_ext_test.go Adds coverage for terminal/non-terminal progress indicator and reporter logic.

@codspeed-hq
Copy link

codspeed-hq bot commented Feb 26, 2026

Merging this PR will improve performance by ×18

⚡ 1 improved benchmark
✅ 25 untouched benchmarks

Performance Changes

Benchmark BASE HEAD Efficiency
BenchmarkExecutionMemoryWithoutPool 10,960 µs 609.9 µs ×18

Comparing feat/test-coverage-all (c664b51) with main (e1d2a36)

Open in CodSpeed

@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to Branch Protection failures Feb 26, 2026
You're not authorized to push to this branch. Visit "About protected branches" for more information.
@CybotTM CybotTM enabled auto-merge February 26, 2026 04:27
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated approval for solo maintainer project

All CI checks passed. See SECURITY.md for compensating controls.

@CybotTM CybotTM added this pull request to the merge queue Feb 26, 2026
Merged via the queue into main with commit e49b2e7 Feb 26, 2026
37 checks passed
@CybotTM CybotTM deleted the feat/test-coverage-all branch February 26, 2026 04:36
@github-actions github-actions bot added the released:v0.21.0 Included in v0.21.0 release label Mar 7, 2026
@github-actions
Copy link

github-actions bot commented Mar 7, 2026

🚀 Released in v0.21.0

Thank you for your contribution! 🙏

This is now available in the latest release. Please test and verify everything works as expected in your environment.

If you encounter any issues, please open a new issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

released:v0.21.0 Included in v0.21.0 release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants