feat: centralize Barkfile schema
Some checks failed
Test and Release Module / test (push) Failing after 8s
Test and Release Module / release (push) Has been skipped

This commit is contained in:
2026-09-15 19:44:54 -04:00
commit c7654ea72a
9 changed files with 1163 additions and 0 deletions

254
parser_test.go Normal file
View File

@@ -0,0 +1,254 @@
package barkfile
import (
"errors"
"strings"
"testing"
"time"
)
func TestParseValidConfiguration(t *testing.T) {
cfg, err := Parse([]byte(`pawsql {
listen :5432
tls {
cert "./certs/fullchain.pem"
key ./certs/privkey.pem
}
database gramps {
hostname Gramps.PawSQL.Barkstack.Dev
upstream 192.168.27.10:5432
}
}`))
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if cfg.Listen != ":5432" {
t.Errorf("Listen = %q", cfg.Listen)
}
if cfg.TLS.CertFile != "./certs/fullchain.pem" || cfg.TLS.KeyFile != "./certs/privkey.pem" {
t.Errorf("TLS = %#v", cfg.TLS)
}
if len(cfg.Databases) != 1 {
t.Fatalf("databases = %d", len(cfg.Databases))
}
if got := cfg.Databases[0]; got.Name != "gramps" || got.Hostname != "Gramps.PawSQL.Barkstack.Dev" || got.Upstream != "192.168.27.10:5432" {
t.Errorf("database = %#v", got)
}
if err := cfg.Validate(); err != nil {
t.Fatalf("Validate() error = %v", err)
}
}
func TestParseCommentsAndMultipleDatabases(t *testing.T) {
cfg, err := Parse([]byte(`# external comment
pawsql {
listen :5432 # client port
tls { cert cert.pem # inline
key key.pem }
database one { hostname one.pawsql.test
upstream postgres-one:5432 }
# route another application
database two { hostname two.pawsql.test
upstream 127.0.0.1:55432 }
}`))
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
if len(cfg.Databases) != 2 {
t.Fatalf("databases = %d, want 2", len(cfg.Databases))
}
if cfg.Databases[1].Name != "two" || cfg.Databases[1].Upstream != "127.0.0.1:55432" {
t.Errorf("second database = %#v", cfg.Databases[1])
}
}
func TestValidateMissingFieldsAndDuplicateHostname(t *testing.T) {
cfg, err := Parse([]byte(`pawsql {
listen :5432
tls { cert cert.pem }
database one {
hostname FOO.pawsql.test
}
database two {
hostname foo.pawsql.test
upstream postgres-two:5432
}
}`))
if err != nil {
t.Fatalf("Parse() error = %v", err)
}
err = cfg.Validate()
if err == nil {
t.Fatal("Validate() error = nil")
}
for _, want := range []string{"TLS private key is required", "upstream or postgres is required", "duplicate hostname"} {
if !strings.Contains(err.Error(), want) {
t.Errorf("Validate() error = %q, missing %q", err, want)
}
}
}
func TestValidateRejectsMalformedUpstreamAddress(t *testing.T) {
cfg := Config{
Listen: ":5432",
TLS: TLSConfig{CertFile: "cert.pem", KeyFile: "key.pem"},
Databases: []DatabaseConfig{{
Name: "foo",
Hostname: "foo.pawsql.test",
Upstream: "postgres-foo",
}},
}
err := cfg.Validate()
if err == nil || !strings.Contains(err.Error(), "missing port") {
t.Fatalf("Validate() error = %v, want malformed upstream address", err)
}
}
func TestValidateAllowsDatabaseWithoutHostname(t *testing.T) {
cfg := Config{
Listen: ":5432",
TLS: TLSConfig{CertFile: "cert.pem", KeyFile: "key.pem"},
Databases: []DatabaseConfig{{
Name: "analytics",
Upstream: "postgres-foo:5432",
}},
}
if err := cfg.Validate(); err != nil {
t.Fatalf("Validate() error = %v", err)
}
}
func TestValidateRejectsDuplicateDatabaseName(t *testing.T) {
cfg := Config{
Listen: ":5432",
TLS: TLSConfig{CertFile: "cert.pem", KeyFile: "key.pem"},
Databases: []DatabaseConfig{
{Name: "analytics", Upstream: "postgres-foo:5432"},
{Name: "analytics", Upstream: "postgres-bar:5432"},
},
}
err := cfg.Validate()
if err == nil || !strings.Contains(err.Error(), "duplicate database name") {
t.Fatalf("Validate() error = %v, want duplicate database name", err)
}
}
func TestParsePostgresContainer(t *testing.T) {
cfg, err := Parse([]byte(`pawsql {
listen :5432
tls {
cert cert.pem
key key.pem
}
database analytics {
postgres {
image postgres:18
volume analytics-data
password_env ANALYTICS_POSTGRES_PASSWORD
idle_timeout 15m
traffic_idle_timeout 1h
}
}
}`))
if err != nil {
t.Fatal(err)
}
database := cfg.Databases[0]
if database.Postgres == nil || database.Postgres.Image != "postgres:18" || database.Postgres.Volume != "analytics-data" || database.Postgres.PasswordEnv != "ANALYTICS_POSTGRES_PASSWORD" || database.Postgres.IdleTimeout != 15*time.Minute || database.Postgres.TrafficIdleTimeout != time.Hour {
t.Errorf("Postgres = %#v", database.Postgres)
}
if err := cfg.Validate(); err != nil {
t.Fatalf("Validate() error = %v", err)
}
}
func TestParseRejectsInvalidPostgresIdleTimeout(t *testing.T) {
_, err := Parse([]byte(`pawsql {
listen :5432
tls {
cert cert.pem
key key.pem
}
database analytics {
postgres {
image postgres:18
volume analytics-data
password_env ANALYTICS_POSTGRES_PASSWORD
idle_timeout whenever
}
}
}`))
if err == nil || !strings.Contains(err.Error(), "invalid idle_timeout") {
t.Fatalf("Parse() error = %v, want invalid idle_timeout", err)
}
}
func TestParseRejectsInvalidPostgresTrafficIdleTimeout(t *testing.T) {
_, err := Parse([]byte(`pawsql {
listen :5432
tls {
cert cert.pem
key key.pem
}
database analytics {
postgres {
image postgres:18
volume analytics-data
password_env ANALYTICS_POSTGRES_PASSWORD
traffic_idle_timeout whenever
}
}
}`))
if err == nil || !strings.Contains(err.Error(), "invalid traffic_idle_timeout") {
t.Fatalf("Parse() error = %v, want invalid traffic_idle_timeout", err)
}
}
func TestValidateAllowsSupportedPostgresImages(t *testing.T) {
for _, image := range []string{"postgres:16", "postgres:17", "postgres:18"} {
cfg := Config{
Listen: ":5432",
TLS: TLSConfig{CertFile: "cert.pem", KeyFile: "key.pem"},
Databases: []DatabaseConfig{{
Name: "analytics",
Postgres: &PostgresConfig{Image: image, Volume: "analytics-data", PasswordEnv: "ANALYTICS_POSTGRES_PASSWORD"},
}},
}
if err := cfg.Validate(); err != nil {
t.Errorf("Validate() image %q error = %v", image, err)
}
}
}
func TestValidateRejectsUnsupportedPostgresImage(t *testing.T) {
cfg := Config{
Listen: ":5432",
TLS: TLSConfig{CertFile: "cert.pem", KeyFile: "key.pem"},
Databases: []DatabaseConfig{{
Name: "analytics",
Postgres: &PostgresConfig{Image: "postgres:15", Volume: "analytics-data", PasswordEnv: "ANALYTICS_POSTGRES_PASSWORD"},
}},
}
err := cfg.Validate()
if err == nil || !strings.Contains(err.Error(), "postgres:16") {
t.Fatalf("Validate() error = %v, want unsupported PostgreSQL image", err)
}
}
func TestParseMalformedBlocksReportLine(t *testing.T) {
_, err := Parse([]byte("pawsql {\n tls {\n cert cert.pem\n"))
if err == nil {
t.Fatal("Parse() error = nil")
}
if !strings.Contains(err.Error(), "line 4") {
t.Errorf("error = %q, want line number", err)
}
_, err = Parse([]byte("pawsql {\n listen :5432 unexpected\n}"))
if err == nil {
t.Fatal("Parse() error = nil for trailing directive")
}
if errors.Is(err, nil) {
t.Fatal("unexpected nil error")
}
}