forgejo-runner-optimiser/internal/receiver/store_test.go
Manuel Ganter c309bd810d
All checks were successful
ci / build (push) Successful in 2m33s
feat(receiver): add HTTP metrics receiver with SQLite storage
Add a new receiver application under cmd/receiver that accepts metrics
via HTTP POST and stores them in SQLite using GORM. The receiver expects
GitHub Actions style execution context (org, repo, workflow, job, run_id).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-06 11:40:03 +01:00

219 lines
5.6 KiB
Go

package receiver
import (
"os"
"path/filepath"
"testing"
"time"
"edp.buildth.ing/DevFW-CICD/forgejo-runner-resource-collector/internal/summary"
)
func TestNewStore(t *testing.T) {
dbPath := filepath.Join(t.TempDir(), "test.db")
store, err := NewStore(dbPath)
if err != nil {
t.Fatalf("NewStore() error = %v", err)
}
defer func() { _ = store.Close() }()
if _, err := os.Stat(dbPath); os.IsNotExist(err) {
t.Error("database file was not created")
}
}
func TestStore_SaveMetric(t *testing.T) {
store := newTestStore(t)
defer func() { _ = store.Close() }()
payload := &MetricsPayload{
Execution: ExecutionContext{
Organization: "test-org",
Repository: "test-repo",
Workflow: "ci.yml",
Job: "build",
RunID: "run-123",
},
Summary: summary.RunSummary{
StartTime: time.Now().Add(-time.Minute),
EndTime: time.Now(),
DurationSeconds: 60.0,
SampleCount: 12,
CPUTotal: summary.StatSummary{Peak: 80.5, Avg: 45.2, P95: 75.0},
MemUsedBytes: summary.StatSummary{Peak: 1024000, Avg: 512000, P95: 900000},
MemUsedPercent: summary.StatSummary{Peak: 50.0, Avg: 25.0, P95: 45.0},
},
}
id, err := store.SaveMetric(payload)
if err != nil {
t.Fatalf("SaveMetric() error = %v", err)
}
if id == 0 {
t.Error("SaveMetric() returned id = 0, want non-zero")
}
}
func TestStore_GetMetricsByRunID(t *testing.T) {
store := newTestStore(t)
defer func() { _ = store.Close() }()
// Save two metrics with same run ID
for i := 0; i < 2; i++ {
payload := &MetricsPayload{
Execution: ExecutionContext{
Organization: "test-org",
Repository: "test-repo",
Workflow: "ci.yml",
Job: "build",
RunID: "run-456",
},
Summary: summary.RunSummary{SampleCount: i + 1},
}
if _, err := store.SaveMetric(payload); err != nil {
t.Fatalf("SaveMetric() error = %v", err)
}
}
// Save one with different run ID
otherPayload := &MetricsPayload{
Execution: ExecutionContext{RunID: "run-789"},
Summary: summary.RunSummary{},
}
if _, err := store.SaveMetric(otherPayload); err != nil {
t.Fatalf("SaveMetric() error = %v", err)
}
metrics, err := store.GetMetricsByRunID("run-456")
if err != nil {
t.Fatalf("GetMetricsByRunID() error = %v", err)
}
if len(metrics) != 2 {
t.Errorf("GetMetricsByRunID() returned %d metrics, want 2", len(metrics))
}
for _, m := range metrics {
if m.RunID != "run-456" {
t.Errorf("GetMetricsByRunID() returned metric with RunID = %q, want %q", m.RunID, "run-456")
}
}
}
func TestStore_GetMetricsByRunID_NotFound(t *testing.T) {
store := newTestStore(t)
defer func() { _ = store.Close() }()
metrics, err := store.GetMetricsByRunID("nonexistent")
if err != nil {
t.Fatalf("GetMetricsByRunID() error = %v", err)
}
if len(metrics) != 0 {
t.Errorf("GetMetricsByRunID() returned %d metrics, want 0", len(metrics))
}
}
func TestStore_GetMetricsByRepository(t *testing.T) {
store := newTestStore(t)
defer func() { _ = store.Close() }()
// Save metrics for different repos
repos := []struct {
org string
repo string
}{
{"org-a", "repo-1"},
{"org-a", "repo-1"},
{"org-a", "repo-2"},
{"org-b", "repo-1"},
}
for i, r := range repos {
payload := &MetricsPayload{
Execution: ExecutionContext{
Organization: r.org,
Repository: r.repo,
RunID: "run-" + string(rune('a'+i)),
},
Summary: summary.RunSummary{},
}
if _, err := store.SaveMetric(payload); err != nil {
t.Fatalf("SaveMetric() error = %v", err)
}
}
metrics, err := store.GetMetricsByRepository("org-a", "repo-1")
if err != nil {
t.Fatalf("GetMetricsByRepository() error = %v", err)
}
if len(metrics) != 2 {
t.Errorf("GetMetricsByRepository() returned %d metrics, want 2", len(metrics))
}
for _, m := range metrics {
if m.Organization != "org-a" || m.Repository != "repo-1" {
t.Errorf("GetMetricsByRepository() returned metric with org=%q repo=%q, want org-a/repo-1",
m.Organization, m.Repository)
}
}
}
func TestStore_SaveMetric_PreservesPayload(t *testing.T) {
store := newTestStore(t)
defer func() { _ = store.Close() }()
original := &MetricsPayload{
Execution: ExecutionContext{
Organization: "test-org",
Repository: "test-repo",
Workflow: "build.yml",
Job: "test",
RunID: "run-preserve",
},
Summary: summary.RunSummary{
DurationSeconds: 123.45,
SampleCount: 50,
CPUTotal: summary.StatSummary{Peak: 99.9, Avg: 55.5, P95: 88.8},
},
}
_, err := store.SaveMetric(original)
if err != nil {
t.Fatalf("SaveMetric() error = %v", err)
}
metrics, err := store.GetMetricsByRunID("run-preserve")
if err != nil {
t.Fatalf("GetMetricsByRunID() error = %v", err)
}
if len(metrics) != 1 {
t.Fatalf("GetMetricsByRunID() returned %d metrics, want 1", len(metrics))
}
m := metrics[0]
if m.Organization != original.Execution.Organization {
t.Errorf("Organization = %q, want %q", m.Organization, original.Execution.Organization)
}
if m.Repository != original.Execution.Repository {
t.Errorf("Repository = %q, want %q", m.Repository, original.Execution.Repository)
}
if m.Workflow != original.Execution.Workflow {
t.Errorf("Workflow = %q, want %q", m.Workflow, original.Execution.Workflow)
}
if m.Job != original.Execution.Job {
t.Errorf("Job = %q, want %q", m.Job, original.Execution.Job)
}
if m.Payload == "" {
t.Error("Payload is empty")
}
}
func newTestStore(t *testing.T) *Store {
t.Helper()
dbPath := filepath.Join(t.TempDir(), "test.db")
store, err := NewStore(dbPath)
if err != nil {
t.Fatalf("NewStore() error = %v", err)
}
return store
}