eda033ff9b
Addresses errcheck, gosec, revive, staticcheck, and unused findings
across the codebase (unchecked error returns, unsafe file inclusion
warnings on operator/test-controlled paths, missing package comments,
unused parameters, deprecated API usage). Also fixes two suppression
comments that were silently no-ops due to wrong syntax (#nosec needs
a leading '#', nolint reasons need '//' not '--').
With the backlog clear, drop continue-on-error from the CI lint job
per the plan left in b4b4806.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
196 lines
4.8 KiB
Go
196 lines
4.8 KiB
Go
package e2e
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/igodwin/notifier/pkg/client"
|
|
"github.com/testcontainers/testcontainers-go"
|
|
"github.com/testcontainers/testcontainers-go/wait"
|
|
)
|
|
|
|
// TestSuite holds shared test state
|
|
type TestSuite struct {
|
|
Container testcontainers.Container
|
|
Client *client.RESTClient
|
|
BaseURL string
|
|
T *testing.T
|
|
}
|
|
|
|
// SetupSuite creates and starts the notifier container
|
|
func SetupSuite(t *testing.T, retention ...string) *TestSuite {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
|
defer cancel()
|
|
|
|
// Find project root
|
|
cwd, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatalf("Failed to get working directory: %v", err)
|
|
}
|
|
|
|
// Walk up to find go.mod (project root)
|
|
projectRoot := cwd
|
|
for {
|
|
if _, err := os.Stat(filepath.Join(projectRoot, "go.mod")); err == nil {
|
|
break
|
|
}
|
|
parent := filepath.Dir(projectRoot)
|
|
if parent == projectRoot {
|
|
t.Fatalf("Could not find project root (go.mod)")
|
|
}
|
|
projectRoot = parent
|
|
}
|
|
|
|
// Build the docker image from project root
|
|
buildCmd := exec.Command("docker", "build", "-t", "notifier:test", ".")
|
|
buildCmd.Dir = projectRoot
|
|
if output, err := buildCmd.CombinedOutput(); err != nil {
|
|
t.Logf("Docker build output: %s", string(output))
|
|
t.Fatalf("Failed to build docker image: %v", err)
|
|
}
|
|
|
|
// Prepare environment variables for container
|
|
env := map[string]string{
|
|
"NOTIFIER_LOGGING_LEVEL": "debug",
|
|
"NOTIFIER_LOGGING_FORMAT": "json",
|
|
"NOTIFIER_NOTIFIERS_STDOUT": "true",
|
|
}
|
|
|
|
// Add retention config if specified
|
|
if len(retention) > 0 && retention[0] != "" {
|
|
// Parse retention string (format: "KEY1=VAL1|KEY2=VAL2|...")
|
|
parts := strings.Split(retention[0], "|")
|
|
for _, part := range parts {
|
|
kv := strings.Split(part, "=")
|
|
if len(kv) == 2 {
|
|
env[kv[0]] = kv[1]
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create container
|
|
req := testcontainers.ContainerRequest{
|
|
Image: "notifier:test",
|
|
ExposedPorts: []string{"8080/tcp"},
|
|
Env: env,
|
|
WaitingFor: wait.ForHTTP("/health").WithStartupTimeout(30 * time.Second),
|
|
}
|
|
|
|
container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
|
|
ContainerRequest: req,
|
|
Started: true,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Failed to create container: %v", err)
|
|
}
|
|
|
|
// Get container port
|
|
host, err := container.Host(ctx)
|
|
if err != nil {
|
|
if termErr := container.Terminate(ctx); termErr != nil {
|
|
t.Logf("Failed to terminate container during cleanup: %v", termErr)
|
|
}
|
|
t.Fatalf("Failed to get container host: %v", err)
|
|
}
|
|
|
|
port, err := container.MappedPort(ctx, "8080")
|
|
if err != nil {
|
|
if termErr := container.Terminate(ctx); termErr != nil {
|
|
t.Logf("Failed to terminate container during cleanup: %v", termErr)
|
|
}
|
|
t.Fatalf("Failed to get container port: %v", err)
|
|
}
|
|
|
|
baseURL := fmt.Sprintf("http://%s:%s", host, port.Port())
|
|
|
|
// Create client
|
|
cfg := client.ClientConfig{
|
|
BaseURL: baseURL,
|
|
Timeout: 30 * time.Second,
|
|
TLSInsecure: true,
|
|
}
|
|
c := client.NewRESTClient(cfg)
|
|
|
|
// Wait for service to be ready
|
|
deadline := time.Now().Add(30 * time.Second)
|
|
for {
|
|
if time.Now().After(deadline) {
|
|
if termErr := container.Terminate(ctx); termErr != nil {
|
|
t.Logf("Failed to terminate container during cleanup: %v", termErr)
|
|
}
|
|
t.Fatalf("Service failed to become ready")
|
|
}
|
|
|
|
healthy, err := c.HealthCheck(ctx)
|
|
if err == nil && healthy {
|
|
break
|
|
}
|
|
|
|
time.Sleep(100 * time.Millisecond)
|
|
}
|
|
|
|
return &TestSuite{
|
|
Container: container,
|
|
Client: c,
|
|
BaseURL: baseURL,
|
|
T: t,
|
|
}
|
|
}
|
|
|
|
// TeardownSuite stops and removes the container
|
|
func (s *TestSuite) TeardownSuite(ctx context.Context) {
|
|
if s.Container != nil {
|
|
if err := s.Container.Terminate(ctx); err != nil {
|
|
if s.T != nil {
|
|
s.T.Logf("Failed to terminate container during cleanup: %v", err)
|
|
} else {
|
|
fmt.Printf("e2e: failed to terminate container during cleanup: %v\n", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// WaitForCleanup waits for cleanup to have run and notifications to be removed
|
|
func (s *TestSuite) WaitForCleanup(maxWait time.Duration) error {
|
|
ctx, cancel := context.WithTimeout(context.Background(), maxWait)
|
|
defer cancel()
|
|
|
|
deadline := time.Now().Add(maxWait)
|
|
for {
|
|
if time.Now().After(deadline) {
|
|
return fmt.Errorf("cleanup did not complete within %v", maxWait)
|
|
}
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
case <-time.After(100 * time.Millisecond):
|
|
// Check if cleanup has happened by checking logs or stats
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
// GetLogs retrieves container logs for debugging
|
|
func (s *TestSuite) GetLogs(ctx context.Context) string {
|
|
reader, err := s.Container.Logs(ctx)
|
|
if err != nil {
|
|
return fmt.Sprintf("error reading logs: %v", err)
|
|
}
|
|
defer func() { _ = reader.Close() }()
|
|
|
|
logs, err := io.ReadAll(reader)
|
|
if err != nil {
|
|
return fmt.Sprintf("error reading logs: %v", err)
|
|
}
|
|
|
|
return string(logs)
|
|
}
|