A SimpleX Chat bot that answers arithmetic (closes #1)
check / check (push) Successful in 54s

Remove the template's HTTP service, database and fx wiring. Add exact
arithmetic on go/parser and go/constant, a client that runs simplex-chat
as a child process and drives its WebSocket API, and the bot, which keeps
an auto-accepting address and replies to each message. The image adds the
checksum-pinned simplex-chat v7.0.2 on Ubuntu 22.04.

Model: opus-5-5
This commit is contained in:
clawbot
2026-09-26 22:01:51 +00:00
parent f8ce8cef83
commit 16649e0f2f
66 changed files with 2119 additions and 5059 deletions
+37 -198
View File
@@ -2,20 +2,12 @@ package config_test
import (
"errors"
"strings"
"testing"
"time"
"github.com/spf13/viper"
"sneak.berlin/go/simplexcalc/internal/config"
)
// Credentials used by the metrics-auth cases.
const (
testUser = "scraper"
testPass = "hunter2"
)
// env builds a viper instance holding exactly the given keys, so a test
// describes one environment without touching the process's.
func env(kv map[string]string) *viper.Viper {
@@ -33,63 +25,40 @@ func env(kv map[string]string) *viper.Viper {
func TestAbsentValuesTakeDefaults(t *testing.T) {
t.Parallel()
c, err := config.Load(env(nil))
if err != nil {
t.Fatalf("empty environment must be valid, got: %v", err)
}
if c.Port != int(config.DefaultPort) {
t.Errorf("Port = %d, want %d", c.Port, config.DefaultPort)
}
if c.MaxRequestBody != config.DefaultMaxRequestBody {
t.Errorf("MaxRequestBody = %d, want %d",
c.MaxRequestBody, config.DefaultMaxRequestBody)
}
if c.RequestTimeout != config.DefaultRequestTimeout {
t.Errorf("RequestTimeout = %s, want %s",
c.RequestTimeout, config.DefaultRequestTimeout)
}
if !c.HSTS {
t.Error("HSTS must default on when DEBUG is not set")
}
if !c.CSRFKeyEphemeral {
t.Error("an absent CSRF_KEY must mark the config for an ephemeral key")
}
}
// TestSetButUnparseableAborts is the central contract of this package.
// Every case is a value an operator plausibly types, and every one of
// them must fail startup rather than be replaced by the default.
func TestSetButUnparseableAborts(t *testing.T) {
t.Parallel()
cases := map[string]map[string]string{
"port is not a number": {config.EnvPort: "eighty"},
"port is zero": {config.EnvPort: "0"},
"port is above the range": {config.EnvPort: "70000"},
"port is a float": {config.EnvPort: "8080.0"},
"debug is yes": {config.EnvDebug: "yes"},
"hsts is on": {config.EnvHSTS: "on"},
"body cap is nonsense": {config.EnvMaxRequestBody: "big"},
"body cap is too large": {config.EnvMaxRequestBody: "1TiB"},
"body cap is too small": {config.EnvMaxRequestBody: "10"},
"timeout has no unit": {config.EnvRequestTimeout: "30"},
"timeout is out of range": {config.EnvRequestTimeout: "1h"},
"grace is nonsense": {config.EnvShutdownGrace: "soon"},
"sentry dsn is not a url": {config.EnvSentryDSN: "not a dsn"},
"csrf key is not hex": {config.EnvCSRFKey: "not-hex-at-all"},
"csrf key is wrong length": {config.EnvCSRFKey: "abcdef"},
}
for name, kv := range cases {
for name, kv := range map[string]map[string]string{
"unset": nil,
"whitespace only": {config.EnvDataDir: " ", config.EnvDebug: " "},
} {
t.Run(name, func(t *testing.T) {
t.Parallel()
c, err := config.Load(env(kv))
if err != nil {
t.Fatalf("absent values must be valid, got: %v", err)
}
if c.DataDir != config.DefaultDataDir {
t.Errorf("DataDir = %q, want %q", c.DataDir, config.DefaultDataDir)
}
if c.Debug {
t.Error("Debug must default off")
}
})
}
}
// TestSetButUnparseableAborts is the central contract of this package:
// a value an operator plausibly types, and that does not parse, fails
// startup rather than being replaced by the default.
func TestSetButUnparseableAborts(t *testing.T) {
t.Parallel()
for _, raw := range []string{"yes", "on", "enabled", "2"} {
t.Run(raw, func(t *testing.T) {
t.Parallel()
c, err := config.Load(env(map[string]string{config.EnvDebug: raw}))
if err == nil {
t.Fatalf("wanted a startup failure, got a Config: %+v", c)
}
@@ -105,153 +74,23 @@ func TestSetButUnparseableAborts(t *testing.T) {
}
}
// TestSecretsAreNotEchoed: a rejected CSRF key must not appear in the
// error, because errors are logged and a log is not a place to put a
// key.
func TestSecretsAreNotEchoed(t *testing.T) {
t.Parallel()
const secret = "00112233445566778899aabbccdd" // valid hex, wrong length
_, err := config.Load(env(map[string]string{config.EnvCSRFKey: secret}))
if err == nil {
t.Fatal("wanted a failure for a short CSRF key")
}
if strings.Contains(err.Error(), secret) {
t.Errorf("the rejected key was echoed in the error: %v", err)
}
}
// TestHalfSetMetricsAuthAborts covers the case the issue calls out
// explicitly: auth config that is half-set must fail loudly, in both
// directions.
func TestHalfSetMetricsAuthAborts(t *testing.T) {
t.Parallel()
cases := map[string]map[string]string{
"user without password": {config.EnvMetricsUser: testUser},
"password without user": {config.EnvMetricsPassword: testPass},
}
for name, kv := range cases {
t.Run(name, func(t *testing.T) {
t.Parallel()
_, err := config.Load(env(kv))
if err == nil {
t.Fatal("half-set metrics credentials must abort startup")
}
if !errors.Is(err, config.ErrInvalidConfig) {
t.Errorf("error does not wrap ErrInvalidConfig: %v", err)
}
})
}
both, err := config.Load(env(map[string]string{
config.EnvMetricsUser: testUser, config.EnvMetricsPassword: testPass,
}))
if err != nil {
t.Fatalf("both credentials set must be valid, got: %v", err)
}
if both.MetricsUser != testUser || both.MetricsPassword != testPass {
t.Error("credentials did not survive parsing")
}
neither, err := config.Load(env(nil))
if err != nil {
t.Fatalf("neither credential set must be valid, got: %v", err)
}
if neither.MetricsUser != "" || neither.MetricsPassword != "" {
t.Error("credentials appeared from nowhere")
}
}
// TestEveryFailureIsReported: one restart should surface the whole list,
// not just the first problem.
func TestEveryFailureIsReported(t *testing.T) {
t.Parallel()
_, err := config.Load(env(map[string]string{
config.EnvPort: "eighty",
config.EnvDebug: "yes",
config.EnvRequestTimeout: "soon",
}))
if err == nil {
t.Fatal("wanted failures")
}
for _, key := range []string{
config.EnvPort, config.EnvDebug, config.EnvRequestTimeout,
} {
if !strings.Contains(err.Error(), key) {
t.Errorf("%s is broken but is not named in the error: %v", key, err)
}
}
}
// TestValidValuesAreUsed proves the parsers accept what they document.
func TestValidValuesAreUsed(t *testing.T) {
t.Parallel()
const key = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
c, err := config.Load(env(map[string]string{
config.EnvPort: "9000",
config.EnvDebug: "true",
config.EnvMaxRequestBody: "2MiB",
config.EnvRequestTimeout: "45s",
config.EnvShutdownGrace: "5s",
config.EnvDataDir: "/var/lib/example",
config.EnvCSRFKey: key,
}))
if err != nil {
t.Fatalf("valid environment was rejected: %v", err)
}
if c.Port != 9000 {
t.Errorf("Port = %d, want 9000", c.Port)
}
if c.MaxRequestBody != 2<<20 {
t.Errorf("MaxRequestBody = %d, want %d", c.MaxRequestBody, 2<<20)
}
if c.RequestTimeout != 45*time.Second {
t.Errorf("RequestTimeout = %s, want 45s", c.RequestTimeout)
}
if c.HSTS {
t.Error("HSTS must default off when DEBUG is true")
}
if len(c.CSRFKey) != config.CSRFKeyBytes || c.CSRFKeyEphemeral {
t.Errorf("CSRFKey not decoded: len=%d ephemeral=%v",
len(c.CSRFKey), c.CSRFKeyEphemeral)
}
if c.DBPath != "/var/lib/example/simplexcalc.db" {
t.Errorf("DBPath = %q, want it derived from DATA_DIR", c.DBPath)
}
}
// TestExplicitOverridesDerivedDBPath: DB_PATH wins over the DATA_DIR
// derivation, which is the only reason it exists.
func TestExplicitOverridesDerivedDBPath(t *testing.T) {
t.Parallel()
c, err := config.Load(env(map[string]string{
config.EnvDataDir: "/var/lib/example",
config.EnvDBPath: "/srv/other.db",
config.EnvDebug: "true",
}))
if err != nil {
t.Fatalf("valid environment was rejected: %v", err)
}
if c.DBPath != "/srv/other.db" {
t.Errorf("DBPath = %q, want /srv/other.db", c.DBPath)
if c.DataDir != "/var/lib/example" {
t.Errorf("DataDir = %q, want /var/lib/example", c.DataDir)
}
if !c.Debug {
t.Error("Debug = false, want true")
}
}