mirror of
https://github.com/Dvorinka/Containr.git
synced 2026-10-28 07:41:27 +00:00
First tranche of the ~300-site raw SQL port. Metrics: node/service/ instance rows via generated queries; aggregation moved off TimescaleDB's time_bucket onto portable date_bin (PG14+); interval uptime returns epoch seconds; per-container writes dropped (container_metrics FKs a different model — collector shape never matched). Domains: all reads/writes through sqlcdb including the create transaction via WithTx; default-domain and services.domain invariants unchanged. Both paths covered by live Postgres tests gated on METRICS_TEST_DSN. Generated with [Devin](https://devin.ai) Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
146 lines
4.3 KiB
Go
146 lines
4.3 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"containr/internal/database"
|
|
"containr/internal/database/sqlcdb"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
func testDSNString(t *testing.T) string {
|
|
t.Helper()
|
|
dsn := os.Getenv("METRICS_TEST_DSN")
|
|
if dsn == "" {
|
|
dsn = os.Getenv("DATABASE_URL")
|
|
}
|
|
if dsn == "" {
|
|
t.Skip("METRICS_TEST_DSN not set")
|
|
}
|
|
return dsn
|
|
}
|
|
|
|
func TestDNSLabel(t *testing.T) {
|
|
cases := map[string]string{
|
|
"my-app": "my-app",
|
|
"My App_2": "my-app-2",
|
|
" web ": "web",
|
|
"__weird__name": "weird-name",
|
|
"---": "",
|
|
"": "",
|
|
}
|
|
for in, want := range cases {
|
|
if got := dnsLabel(in); got != want {
|
|
t.Errorf("dnsLabel(%q) = %q, want %q", in, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDNSLabelLengthCap(t *testing.T) {
|
|
long := "a" + string(make([]byte, 0)) + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
|
|
if got := dnsLabel(long); len(got) > 63 {
|
|
t.Fatalf("label over 63 chars: %d", len(got))
|
|
}
|
|
}
|
|
|
|
// Live invariant test against real Postgres — set METRICS_TEST_DSN (or
|
|
// DATABASE_URL). Verifies the generated domain queries preserve the
|
|
// default-domain and legacy services.domain invariants.
|
|
func TestServiceDomainsLiveInvariants(t *testing.T) {
|
|
dsn := testDSNString(t)
|
|
db, err := database.NewConnection(dsn)
|
|
if err != nil {
|
|
t.Fatalf("connect: %v", err)
|
|
}
|
|
defer db.Close()
|
|
ctx := context.Background()
|
|
q := sqlcdb.New(db.DB)
|
|
|
|
userID := uuid.New()
|
|
projectID := uuid.New()
|
|
envID := uuid.New()
|
|
serviceID := uuid.New()
|
|
suffix := strings.ReplaceAll(serviceID.String()[:8], "-", "")
|
|
|
|
must := func(err error) {
|
|
t.Helper()
|
|
if err != nil {
|
|
t.Fatalf("setup: %v", err)
|
|
}
|
|
}
|
|
_, err = db.Exec(`INSERT INTO users (id, email, password_hash, name) VALUES ($1, $2, 'x', 'test')`,
|
|
userID, "domtest-"+suffix+"@test.local")
|
|
must(err)
|
|
_, err = db.Exec(`INSERT INTO projects (id, name, owner_id, is_approved) VALUES ($1, $2, $3, true)`,
|
|
projectID, "domtest-"+suffix, userID)
|
|
must(err)
|
|
_, err = db.Exec(`INSERT INTO environments (id, name, project_id) VALUES ($1, 'production', $2)`,
|
|
envID, projectID)
|
|
must(err)
|
|
_, err = db.Exec(`INSERT INTO services (id, name, project_id, environment_id, service_type, source_type)
|
|
VALUES ($1, 'domtest-`+suffix+`', $2, $3, 'web', 'image')`,
|
|
serviceID, projectID, envID)
|
|
must(err)
|
|
defer func() {
|
|
_, _ = db.Exec(`DELETE FROM users WHERE id = $1`, userID)
|
|
}()
|
|
|
|
d1, err := q.CreateServiceDomain(ctx, sqlcdb.CreateServiceDomainParams{
|
|
ServiceID: serviceID, Domain: "a-" + suffix + ".example.com", IsDefault: true})
|
|
if err != nil {
|
|
t.Fatalf("create d1: %v", err)
|
|
}
|
|
d2, err := q.CreateServiceDomain(ctx, sqlcdb.CreateServiceDomainParams{
|
|
ServiceID: serviceID, Domain: "b-" + suffix + ".example.com", IsDefault: false})
|
|
if err != nil {
|
|
t.Fatalf("create d2: %v", err)
|
|
}
|
|
|
|
syncDefaultDomain(db, serviceID)
|
|
var legacy string
|
|
if err := db.QueryRow(`SELECT domain FROM services WHERE id = $1`, serviceID).Scan(&legacy); err != nil {
|
|
t.Fatalf("read legacy: %v", err)
|
|
}
|
|
if legacy != d1.Domain {
|
|
t.Fatalf("legacy domain = %q, want %q", legacy, d1.Domain)
|
|
}
|
|
|
|
// Switch default: exactly one row stays default, legacy follows.
|
|
if err := q.ClearServiceDomainDefault(ctx, serviceID); err != nil {
|
|
t.Fatalf("clear: %v", err)
|
|
}
|
|
if _, err := q.SetServiceDomainDefault(ctx, sqlcdb.SetServiceDomainDefaultParams{
|
|
ID: d2.ID, ServiceID: serviceID}); err != nil {
|
|
t.Fatalf("set default: %v", err)
|
|
}
|
|
syncDefaultDomain(db, serviceID)
|
|
if err := db.QueryRow(`SELECT domain FROM services WHERE id = $1`, serviceID).Scan(&legacy); err != nil {
|
|
t.Fatalf("read legacy2: %v", err)
|
|
}
|
|
if legacy != d2.Domain {
|
|
t.Fatalf("legacy after switch = %q, want %q", legacy, d2.Domain)
|
|
}
|
|
|
|
doms := loadServiceDomains(db, serviceID)
|
|
if len(doms) != 2 || !doms[0].IsDefault || doms[0].Domain != d2.Domain {
|
|
t.Fatalf("list order wrong: %+v", doms)
|
|
}
|
|
|
|
// Deleting the default falls back to the remaining domain.
|
|
if _, err := q.DeleteServiceDomain(ctx, sqlcdb.DeleteServiceDomainParams{
|
|
ID: d2.ID, ServiceID: serviceID}); err != nil {
|
|
t.Fatalf("delete d2: %v", err)
|
|
}
|
|
syncDefaultDomain(db, serviceID)
|
|
if err := db.QueryRow(`SELECT domain FROM services WHERE id = $1`, serviceID).Scan(&legacy); err != nil {
|
|
t.Fatalf("read legacy3: %v", err)
|
|
}
|
|
if legacy != d1.Domain {
|
|
t.Fatalf("legacy after delete = %q, want %q", legacy, d1.Domain)
|
|
}
|
|
}
|