Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 94 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# GitHub Actions Test Workflow
# Runs tests with coverage on PRs and pushes to main
# - For PRs: Tests only affected packages (changed files + dependents)
# - For main: Runs full test suite
# - Enforces minimum coverage threshold

name: Test

Expand All @@ -16,6 +19,7 @@ permissions:

env:
GO_VERSION: '1.24'
COVERAGE_THRESHOLD: 60 # Minimum coverage percentage required

jobs:
test:
Expand All @@ -24,6 +28,8 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Required to get full history for diff

- name: Set up Go
uses: actions/setup-go@v5
Expand All @@ -34,7 +40,92 @@ jobs:
- name: Download dependencies
run: go mod download

- name: Run tests with coverage
- name: Get changed packages
id: changed
if: github.event_name == 'pull_request'
run: |
# Get list of changed Go files
CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD -- '*.go' | grep -v '_test.go$' | grep -v '/test/' || true)

if [ -z "$CHANGED_FILES" ]; then
echo "No Go source files changed"
echo "packages=" >> $GITHUB_OUTPUT
exit 0
fi

echo "Changed files:"
echo "$CHANGED_FILES"

# Get packages for changed files
CHANGED_PKGS=""
for file in $CHANGED_FILES; do
if [ -f "$file" ]; then
pkg=$(dirname "$file")
CHANGED_PKGS="$CHANGED_PKGS ./$pkg"
fi
done

# Deduplicate and find dependent packages
if [ -n "$CHANGED_PKGS" ]; then
UNIQUE_PKGS=$(echo "$CHANGED_PKGS" | tr ' ' '\n' | sort -u | tr '\n' ' ')
echo "Changed packages: $UNIQUE_PKGS"

# Find all packages that depend on changed packages
ALL_PKGS=$(go list ./... | grep -v '/test/e2e')
AFFECTED_PKGS="$UNIQUE_PKGS"

for pkg in $UNIQUE_PKGS; do
# Find packages that import this package
PKG_PATH=$(go list -f '{{.ImportPath}}' $pkg 2>/dev/null || true)
if [ -n "$PKG_PATH" ]; then
DEPENDENTS=$(echo "$ALL_PKGS" | xargs -I {} sh -c "go list -f '{{range .Imports}}{{.}} {{end}}' {} 2>/dev/null | grep -q '$PKG_PATH' && echo {}" || true)
AFFECTED_PKGS="$AFFECTED_PKGS $DEPENDENTS"
fi
done

# Deduplicate final list
FINAL_PKGS=$(echo "$AFFECTED_PKGS" | tr ' ' '\n' | sort -u | grep -v '^$' | tr '\n' ' ')
echo "Affected packages (including dependents): $FINAL_PKGS"
echo "packages=$FINAL_PKGS" >> $GITHUB_OUTPUT
else
echo "packages=" >> $GITHUB_OUTPUT
fi

- name: Run tests (PR - affected packages only)
if: github.event_name == 'pull_request' && steps.changed.outputs.packages != ''
run: |
echo "Testing affected packages: ${{ steps.changed.outputs.packages }}"
go test -v -race -coverprofile=coverage.out -covermode=atomic ${{ steps.changed.outputs.packages }}

- name: Run tests (full suite)
if: github.event_name == 'push' || (github.event_name == 'pull_request' && steps.changed.outputs.packages == '')
run: |
echo "Running full test suite"
go test -v -race -coverprofile=coverage.out -covermode=atomic $(go list ./... | grep -v '/test/e2e')

- name: Check coverage threshold
if: always() && hashFiles('coverage.out') != ''
run: |
go test -v -race -coverprofile=coverage.out -covermode=atomic $(go list ./... | grep -v /test/e2e)
go tool cover -func=coverage.out | tail -1
# Display coverage summary
echo "=== Coverage Summary ==="
go tool cover -func=coverage.out

# Extract total coverage percentage
COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//')
echo ""
echo "Total coverage: ${COVERAGE}%"
echo "Minimum threshold: ${COVERAGE_THRESHOLD}%"

# Check if coverage meets threshold
if [ -z "$COVERAGE" ]; then
echo "::warning::Could not determine coverage percentage"
exit 0
fi

# Compare coverage against threshold (using bc for float comparison)
if echo "$COVERAGE < $COVERAGE_THRESHOLD" | bc -l | grep -q 1; then
echo "::error::Coverage ${COVERAGE}% is below the minimum threshold of ${COVERAGE_THRESHOLD}%"
exit 1
fi

echo "::notice::Coverage ${COVERAGE}% meets the minimum threshold of ${COVERAGE_THRESHOLD}%"
119 changes: 119 additions & 0 deletions internal/adapter/dto/alertmanager_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
package dto

import (
"testing"
"time"

"github.com/altuslabsxyz/alert-bridge/internal/domain/entity"
)

func TestMapSeverity(t *testing.T) {
tests := []struct {
name string
input string
expected entity.AlertSeverity
}{
{"critical", "critical", entity.SeverityCritical},
{"page", "page", entity.SeverityCritical},
{"warning", "warning", entity.SeverityWarning},
{"warn", "warn", entity.SeverityWarning},
{"info", "info", entity.SeverityInfo},
{"empty", "", entity.SeverityInfo},
{"unknown", "unknown", entity.SeverityInfo},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := mapSeverity(tt.input)
if got != tt.expected {
t.Errorf("mapSeverity(%q) = %v, want %v", tt.input, got, tt.expected)
}
})
}
}

func TestToProcessAlertInput(t *testing.T) {
now := time.Now()

alert := AlertmanagerAlert{
Status: "firing",
Fingerprint: "abc123",
Labels: map[string]string{
"alertname": "HighCPU",
"instance": "server1:9090",
"job": "prometheus",
"severity": "critical",
},
Annotations: map[string]string{
"summary": "CPU usage is high",
"description": "CPU usage exceeded 90%",
},
StartsAt: now,
}

input := ToProcessAlertInput(alert)

if input.Fingerprint != "abc123" {
t.Errorf("Fingerprint = %v, want %v", input.Fingerprint, "abc123")
}
if input.Name != "HighCPU" {
t.Errorf("Name = %v, want %v", input.Name, "HighCPU")
}
if input.Instance != "server1:9090" {
t.Errorf("Instance = %v, want %v", input.Instance, "server1:9090")
}
if input.Target != "prometheus" {
t.Errorf("Target = %v, want %v", input.Target, "prometheus")
}
if input.Summary != "CPU usage is high" {
t.Errorf("Summary = %v, want %v", input.Summary, "CPU usage is high")
}
if input.Severity != entity.SeverityCritical {
t.Errorf("Severity = %v, want %v", input.Severity, entity.SeverityCritical)
}
if input.Status != "firing" {
t.Errorf("Status = %v, want %v", input.Status, "firing")
}
}

func TestAlertmanagerAlert_IsFiring(t *testing.T) {
tests := []struct {
name string
status string
want bool
}{
{"firing", "firing", true},
{"resolved", "resolved", false},
{"other", "pending", false},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := AlertmanagerAlert{Status: tt.status}
if got := a.IsFiring(); got != tt.want {
t.Errorf("IsFiring() = %v, want %v", got, tt.want)
}
})
}
}

func TestAlertmanagerAlert_IsResolved(t *testing.T) {
tests := []struct {
name string
status string
want bool
}{
{"resolved", "resolved", true},
{"firing", "firing", false},
{"other", "pending", false},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := AlertmanagerAlert{Status: tt.status}
if got := a.IsResolved(); got != tt.want {
t.Errorf("IsResolved() = %v, want %v", got, tt.want)
}
})
}
}
27 changes: 27 additions & 0 deletions internal/adapter/dto/pagerduty_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package dto

import "testing"

func TestIsSupportedEventType(t *testing.T) {
tests := []struct {
name string
eventType string
want bool
}{
{"acknowledged", "incident.acknowledged", true},
{"resolved", "incident.resolved", true},
{"unacknowledged", "incident.unacknowledged", true},
{"reassigned", "incident.reassigned", true},
{"triggered", "incident.triggered", false},
{"empty", "", false},
{"invalid", "invalid.event", false},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsSupportedEventType(tt.eventType); got != tt.want {
t.Errorf("IsSupportedEventType(%q) = %v, want %v", tt.eventType, got, tt.want)
}
})
}
}
Loading
Loading