Implement approved specification #32 and tickets #33-#37. Keep host authentication server-verified and pin session interaction language. Compile scoped base selectors for browser compatibility and retain full gutters during CSS pruning.
140 lines
5.2 KiB
Go
140 lines
5.2 KiB
Go
package modelprojection
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"reflect"
|
|
"runtime"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/aritmolab/thothii/tools/tht/internal/config"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
func TestRenderPublicShellConfiguration(t *testing.T) {
|
|
for _, test := range []struct {
|
|
name string
|
|
shell config.Shell
|
|
want string
|
|
}{
|
|
{"legacy", config.Shell{}, `{"backendBaseUrl":"/api","shell":{"mode":"embedded","defaultLocale":"en","adapter":"omics-portal"}}`},
|
|
{"full", config.Shell{Mode: "full", Adapter: "omics-portal"}, `{"backendBaseUrl":"/api","shell":{"mode":"full","defaultLocale":"en"}}`},
|
|
{"Italian", config.Shell{Mode: "embedded", DefaultLocale: "it"}, `{"backendBaseUrl":"/api","shell":{"mode":"embedded","defaultLocale":"it","adapter":"omics-portal"}}`},
|
|
{"locale without catalog", config.Shell{Mode: "full", DefaultLocale: "fr-CA"}, `{"backendBaseUrl":"/api","shell":{"mode":"full","defaultLocale":"fr-CA"}}`},
|
|
} {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
installation := projectionFixture(t)
|
|
installation.Shell = test.shell
|
|
files, err := Render(installation)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// The entire public payload is an allowlist: no model endpoints, auth or host paths.
|
|
js := string(files["frontend/config.js"])
|
|
if !strings.HasPrefix(js, "window.__THOTHII_CONFIG__ = ") || !strings.HasSuffix(js, ";\n") {
|
|
t.Fatalf("invalid public config script: %q", js)
|
|
}
|
|
payload := strings.TrimSuffix(strings.TrimPrefix(js, "window.__THOTHII_CONFIG__ = "), ";\n")
|
|
var got, want any
|
|
if err := json.Unmarshal([]byte(payload), &got); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := json.Unmarshal([]byte(test.want), &want); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("public config = %s, want %s", payload, test.want)
|
|
}
|
|
var compose struct {
|
|
Services map[string]struct {
|
|
Volumes []struct {
|
|
Type string `yaml:"type"`
|
|
Source string `yaml:"source"`
|
|
Target string `yaml:"target"`
|
|
ReadOnly bool `yaml:"read_only"`
|
|
} `yaml:"volumes"`
|
|
} `yaml:"services"`
|
|
}
|
|
if err := yaml.Unmarshal(files[ComposeFile], &compose); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
mounts := compose.Services["frontend"].Volumes
|
|
if len(mounts) != 1 || mounts[0].Type != "bind" || !mounts[0].ReadOnly ||
|
|
mounts[0].Source != filepath.Join(installation.GeneratedDirectory(), "frontend", "config.js") ||
|
|
mounts[0].Target != "/usr/share/nginx/html/config.js" {
|
|
t.Fatalf("frontend mounts = %#v; want only public config.js, read-only", mounts)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGenerateRefreshesPublicShellConfiguration(t *testing.T) {
|
|
installation := projectionFixture(t)
|
|
if err := Generate(installation); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
publicPath := filepath.Join(installation.GeneratedDirectory(), "frontend", "config.js")
|
|
before, err := os.ReadFile(publicPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
composeBefore, err := os.ReadFile(installation.ModelProjectionComposePath())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
installation.Shell = config.Shell{Mode: "full", DefaultLocale: "it"}
|
|
drift, err := Check(installation)
|
|
if err != nil || !reflect.DeepEqual(drift, []string{"compose.models.yaml", "frontend/config.js"}) {
|
|
t.Fatalf("shell drift = %v, %v", drift, err)
|
|
}
|
|
if err := Generate(installation); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
after, err := os.ReadFile(publicPath)
|
|
if err != nil || bytes.Equal(before, after) || !bytes.Contains(after, []byte(`"mode": "full"`)) || bytes.Contains(after, []byte(`"adapter"`)) {
|
|
t.Fatalf("updated public config = %s, %v", after, err)
|
|
}
|
|
composeAfter, err := os.ReadFile(installation.ModelProjectionComposePath())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// A config revision changes Compose's service definition, ensuring active bind mounts refresh.
|
|
var oldCompose, newCompose map[string]map[string]map[string]any
|
|
if err := yaml.Unmarshal(composeBefore, &oldCompose); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := yaml.Unmarshal(composeAfter, &newCompose); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if reflect.DeepEqual(oldCompose["services"]["frontend"]["environment"], newCompose["services"]["frontend"]["environment"]) {
|
|
t.Fatal("shell change did not revise the frontend service")
|
|
}
|
|
if !reflect.DeepEqual(oldCompose["services"]["core"], newCompose["services"]["core"]) {
|
|
t.Fatal("shell-only change revised core configuration")
|
|
}
|
|
if drift, err := Check(installation); err != nil || len(drift) != 0 {
|
|
t.Fatalf("fresh generation drift = %v, %v", drift, err)
|
|
}
|
|
}
|
|
|
|
func TestGeneratedPublicShellPassesFrontendImageSmoke(t *testing.T) {
|
|
if runtime.GOOS == "windows" {
|
|
t.Skip("frontend image smoke requires a POSIX host shell")
|
|
}
|
|
for _, shell := range []config.Shell{{}, {Mode: "full"}, {Mode: "full", DefaultLocale: "it"}, {Mode: "embedded", DefaultLocale: "it"}, {Mode: "full", DefaultLocale: "fr-CA"}, {Mode: "embedded", DefaultLocale: "zh-Hant-TW-u-nu-hanidec"}} {
|
|
installation := projectionFixture(t)
|
|
installation.Shell = shell
|
|
if err := Generate(installation); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
command := exec.Command("sh", "../../../../docker/smoke/frontend-smoke.sh", installation.GeneratedFrontendConfigPath())
|
|
if output, err := command.CombinedOutput(); err != nil {
|
|
t.Fatalf("frontend smoke for %#v: %v\n%s", shell, err, output)
|
|
}
|
|
}
|
|
}
|