feat(core): add configuration, logging and sqlite persistence
DB_PATH, HTTP_ADDR, HTTP_PORT and LOG_LEVEL parsing with validation; slog text logger with a TRACE level and token redaction; embedded idempotent schema on CGO-free SQLite (WAL, busy timeout); token store (create, list, deactivate, authenticate) and append-only audit store with ordered, limited and windowed queries; shared unit test helpers.
This commit is contained in:
1 parent
fe0cd4bf6d
commit
88d7d6d2b9
11 files changed
+2426
No files matched your search
+202
@@ -0,0 +1,202 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright (C) 2026 Bryan Joshua Pedini
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoadConfigDefaults(t *testing.T) {
|
||||
t.Parallel()
|
||||
cfg, err := LoadConfig(envMap(map[string]string{"DB_PATH": "/data/dpsapi.db"}))
|
||||
if err != nil {
|
||||
t.Fatalf("LoadConfig: %v", err)
|
||||
}
|
||||
want := Config{DBPath: "/data/dpsapi.db", HTTPAddr: "0.0.0.0", HTTPPort: 80, LogLevel: slog.LevelInfo}
|
||||
if cfg != want {
|
||||
t.Fatalf("got %+v, want %+v", cfg, want)
|
||||
}
|
||||
if got := cfg.ListenAddress(); got != "0.0.0.0:80" {
|
||||
t.Fatalf("ListenAddress = %q", got)
|
||||
}
|
||||
if got := cfg.ListenNetwork(); got != "tcp4" {
|
||||
t.Fatalf("ListenNetwork = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadConfigExplicitValues(t *testing.T) {
|
||||
t.Parallel()
|
||||
cfg, err := LoadConfig(envMap(map[string]string{
|
||||
"DB_PATH": "rel/path.db",
|
||||
"HTTP_ADDR": "::1",
|
||||
"HTTP_PORT": "8080",
|
||||
"LOG_LEVEL": "trace",
|
||||
}))
|
||||
if err != nil {
|
||||
t.Fatalf("LoadConfig: %v", err)
|
||||
}
|
||||
want := Config{DBPath: "rel/path.db", HTTPAddr: "::1", HTTPPort: 8080, LogLevel: LevelTrace}
|
||||
if cfg != want {
|
||||
t.Fatalf("got %+v, want %+v", cfg, want)
|
||||
}
|
||||
if got := cfg.ListenAddress(); got != "[::1]:8080" {
|
||||
t.Fatalf("ListenAddress = %q", got)
|
||||
}
|
||||
if got := cfg.ListenNetwork(); got != "tcp" {
|
||||
t.Fatalf("ListenNetwork = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadConfigEveryLogLevel(t *testing.T) {
|
||||
t.Parallel()
|
||||
cases := map[string]slog.Level{
|
||||
"ERROR": slog.LevelError, "error": slog.LevelError, "Error": slog.LevelError,
|
||||
"WARN": slog.LevelWarn, "warn": slog.LevelWarn, "wArN": slog.LevelWarn,
|
||||
"INFO": slog.LevelInfo, "info": slog.LevelInfo,
|
||||
"DEBUG": slog.LevelDebug, "debug": slog.LevelDebug,
|
||||
"TRACE": LevelTrace, "trace": LevelTrace, "TrAcE": LevelTrace,
|
||||
}
|
||||
for in, want := range cases {
|
||||
cfg, err := LoadConfig(envMap(map[string]string{"DB_PATH": "x", "LOG_LEVEL": in}))
|
||||
if err != nil {
|
||||
t.Errorf("LOG_LEVEL=%q: %v", in, err)
|
||||
continue
|
||||
}
|
||||
if cfg.LogLevel != want {
|
||||
t.Errorf("LOG_LEVEL=%q: got %v, want %v", in, cfg.LogLevel, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadConfigInvalidLogLevels(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, in := range []string{
|
||||
"WARNING", "ERR", "FATAL", "VERBOSE", "0", "-4", "INFO ", " INFO", "IN FO", "INFO\n",
|
||||
"ınfo", // dotless i: Unicode case folding must not apply
|
||||
"DEBUG-4", "trace1", "\x00", "INFO",
|
||||
} {
|
||||
_, err := LoadConfig(envMap(map[string]string{"DB_PATH": "x", "LOG_LEVEL": in}))
|
||||
if !errors.Is(err, errConfig) {
|
||||
t.Errorf("LOG_LEVEL=%q: expected config error, got %v", in, err)
|
||||
continue
|
||||
}
|
||||
if !strings.Contains(err.Error(), "invalid LOG_LEVEL") {
|
||||
t.Errorf("LOG_LEVEL=%q: unexpected message %q", in, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadConfigMissingDBPath(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := LoadConfig(envMap(map[string]string{"HTTP_PORT": "8080"}))
|
||||
if !errors.Is(err, errConfig) || err.Error() != "configuration error: DB_PATH is required" {
|
||||
t.Fatalf("got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadConfigEmptyValues(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, name := range []string{"DB_PATH", "HTTP_ADDR", "HTTP_PORT", "LOG_LEVEL"} {
|
||||
env := map[string]string{"DB_PATH": "x"}
|
||||
env[name] = ""
|
||||
_, err := LoadConfig(envMap(env))
|
||||
want := "configuration error: " + name + " must not be empty"
|
||||
if !errors.Is(err, errConfig) || err.Error() != want {
|
||||
t.Errorf("%s empty: got %v, want %q", name, err, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadConfigInvalidPorts(t *testing.T) {
|
||||
t.Parallel()
|
||||
for _, in := range []string{
|
||||
"0", "-1", "-80", "+80", "65536", "70000", "99999999999999999999999", "abc", "80a", "8 0",
|
||||
" 80", "80 ", "0x50", "1e3", "80.0", "80", "\t80",
|
||||
} {
|
||||
_, err := LoadConfig(envMap(map[string]string{"DB_PATH": "x", "HTTP_PORT": in}))
|
||||
if !errors.Is(err, errConfig) || !strings.Contains(err.Error(), "invalid HTTP_PORT") {
|
||||
t.Errorf("HTTP_PORT=%q: expected invalid port error, got %v", in, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadConfigValidEdgePorts(t *testing.T) {
|
||||
t.Parallel()
|
||||
cases := map[string]int{"1": 1, "80": 80, "1024": 1024, "65535": 65535, "0080": 80, "00001": 1}
|
||||
for in, want := range cases {
|
||||
cfg, err := LoadConfig(envMap(map[string]string{"DB_PATH": "x", "HTTP_PORT": in}))
|
||||
if err != nil {
|
||||
t.Errorf("HTTP_PORT=%q: %v", in, err)
|
||||
continue
|
||||
}
|
||||
if cfg.HTTPPort != want {
|
||||
t.Errorf("HTTP_PORT=%q: got %d, want %d", in, cfg.HTTPPort, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePortEmpty(t *testing.T) {
|
||||
t.Parallel()
|
||||
if _, err := parsePort(""); err == nil {
|
||||
t.Fatal("expected error for empty port")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadConfigAddresses(t *testing.T) {
|
||||
t.Parallel()
|
||||
valid := map[string]string{
|
||||
"0.0.0.0": "tcp4",
|
||||
"127.0.0.1": "tcp4",
|
||||
"192.168.1.10": "tcp4",
|
||||
"::": "tcp",
|
||||
"::1": "tcp",
|
||||
"fe80::1": "tcp",
|
||||
"2001:db8::1": "tcp",
|
||||
"::ffff:127.0.0.1": "tcp4",
|
||||
}
|
||||
for in, network := range valid {
|
||||
cfg, err := LoadConfig(envMap(map[string]string{"DB_PATH": "x", "HTTP_ADDR": in}))
|
||||
if err != nil {
|
||||
t.Errorf("HTTP_ADDR=%q: %v", in, err)
|
||||
continue
|
||||
}
|
||||
if cfg.HTTPAddr != in || cfg.ListenNetwork() != network {
|
||||
t.Errorf("HTTP_ADDR=%q: got addr %q network %q", in, cfg.HTTPAddr, cfg.ListenNetwork())
|
||||
}
|
||||
}
|
||||
for _, in := range []string{
|
||||
"localhost", "example.com", "256.0.0.1", "1.2.3", "01.2.3.4", " 127.0.0.1", "127.0.0.1 ",
|
||||
"127.0.0.1:80", "[::1]", "fe80::1%eth0", "*", "0", "::g",
|
||||
} {
|
||||
_, err := LoadConfig(envMap(map[string]string{"DB_PATH": "x", "HTTP_ADDR": in}))
|
||||
if !errors.Is(err, errConfig) || !strings.Contains(err.Error(), "invalid HTTP_ADDR") {
|
||||
t.Errorf("HTTP_ADDR=%q: expected invalid address error, got %v", in, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadConfigValidationOrder(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Every variable invalid: DB_PATH is reported first, then HTTP_ADDR, HTTP_PORT, LOG_LEVEL
|
||||
env := map[string]string{"HTTP_ADDR": "bad", "HTTP_PORT": "bad", "LOG_LEVEL": "bad"}
|
||||
steps := []struct {
|
||||
fix func()
|
||||
want string
|
||||
}{
|
||||
{func() {}, "DB_PATH"},
|
||||
{func() { env["DB_PATH"] = "x" }, "HTTP_ADDR"},
|
||||
{func() { env["HTTP_ADDR"] = "127.0.0.1" }, "HTTP_PORT"},
|
||||
{func() { env["HTTP_PORT"] = "1" }, "LOG_LEVEL"},
|
||||
}
|
||||
for _, step := range steps {
|
||||
step.fix()
|
||||
_, err := LoadConfig(envMap(env))
|
||||
if err == nil || !strings.Contains(err.Error(), step.want) {
|
||||
t.Fatalf("expected error about %s, got %v", step.want, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user