textmachine/platform/internal/config/exports_test.go

108 lines
4.1 KiB
Go

package config
import (
"path/filepath"
"testing"
"time"
)
// A deployment declares no format by default, and then it builds no exports at all: the door is
// unmounted and `export_formats` is empty. The default matters because the alternative — announcing
// a format because the engine that shipped with this build happened to know it — is a promise a
// client discovers is empty on a user's first download.
func TestADeploymentBuildsNoExportsUntilItSaysWhichFormats(t *testing.T) {
t.Setenv("TM_PLATFORM_STATE_DIR", "/var/lib/tmplatform")
t.Setenv("TM_PLATFORM_ENGINE_BIN", "/opt/tm/v1/tmctl")
c, err := Load()
if err != nil {
t.Fatal(err)
}
if len(c.Export.Formats) != 0 || c.ExportsEnabled() {
t.Fatalf("a deployment that declared nothing builds %v", c.Export.Formats)
}
t.Setenv("TM_PLATFORM_EXPORT_FORMATS", " epub , txt ")
c, err = Load()
if err != nil {
t.Fatal(err)
}
if len(c.Export.Formats) != 2 || c.Export.Formats[0] != "epub" || c.Export.Formats[1] != "txt" {
t.Fatalf("formats %q: the declaration is a comma-separated list and its order is the one "+
"`GET /capabilities` announces", c.Export.Formats)
}
if !c.ExportsEnabled() {
t.Error("a deployment with formats, an engine and a directory still builds nothing")
}
// The directory falls out of the state directory: artifacts are platform state of exactly that
// kind, and an operator who already said where state lives should not have to say it twice.
if want := filepath.Join("/var/lib/tmplatform", "exports"); c.Export.Dir != want {
t.Errorf("exports dir %q, want %q", c.Export.Dir, want)
}
if c.Export.TTL != 24*time.Hour {
t.Errorf("TTL %s, want a day", c.Export.TTL)
}
}
// An instance with formats but NO ENGINE builds nothing: `build` is an engine call like every other,
// and a read replica that announced `epub` would answer a download with a failure every time.
func TestAReadReplicaAnnouncesNoFormatsEvenIfTheOperatorDeclaredSome(t *testing.T) {
t.Setenv("TM_PLATFORM_EXPORT_FORMATS", "epub")
c, err := Load()
if err != nil {
t.Fatal(err)
}
if c.ExportsEnabled() {
t.Error("an instance with no engine binary claims it can build exports")
}
}
// The three refusals that belong at BOOT, where an operator sees them, rather than at the first
// download, where they are one user's mysterious failure.
//
// ⚠ The relative directory is the sharpest of the three: the path is handed to the ENGINE as
// `--out`, and the engine runs with the BOOK's directory as its working directory — so a relative
// value would write every artifact inside somebody's book project, the one tree this platform must
// not write into (D39.110).
func TestTheExportSettingsFailAtLoadAndNotAtTheFirstDownload(t *testing.T) {
for name, env := range map[string]map[string]string{
"a format that would name a path": {"TM_PLATFORM_EXPORT_FORMATS": "../etc"},
"a format declared twice": {"TM_PLATFORM_EXPORT_FORMATS": "epub,epub"},
"a relative artifact directory": {"TM_PLATFORM_EXPORTS_DIR": "var/exports"},
"a link that expires at once": {"TM_PLATFORM_EXPORT_TTL": "0s"},
"a malformed lifetime": {"TM_PLATFORM_EXPORT_TTL": "a while"},
} {
t.Run(name, func(t *testing.T) {
for k, v := range env {
t.Setenv(k, v)
}
if _, err := Load(); err == nil {
t.Errorf("%v was accepted", env)
}
})
}
}
// Every setting of this service is printed at boot with where it came from (PD-114), and a new one
// that is not is a variable an operator hunts for and cannot find.
func TestTheExportSettingsArePrintedWithTheirSource(t *testing.T) {
t.Setenv("TM_PLATFORM_EXPORT_FORMATS", "epub")
t.Setenv("TM_PLATFORM_EXPORTS_DIR", "/srv/exports")
c, err := Load()
if err != nil {
t.Fatal(err)
}
printed := map[string]string{}
for _, s := range c.Settings {
printed[s.Key] = s.Source
}
for key, want := range map[string]string{
"TM_PLATFORM_EXPORT_FORMATS": "environment",
"TM_PLATFORM_EXPORTS_DIR": "environment",
"TM_PLATFORM_EXPORT_TTL": "default",
} {
if got, ok := printed[key]; !ok {
t.Errorf("%s is not printed at boot", key)
} else if got != want {
t.Errorf("%s came from %q, want %q", key, got, want)
}
}
}