All checks were successful
ci / build (push) Successful in 2m33s
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>
219 lines
5.6 KiB
Go
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
|
|
}
|