A small Go tool that wraps the `/etc/hosts` + `once deploy --disable-tls` recipe needed to host a Once app on a single-label `<name>.local`. Adds the hosts entry, runs the deploy, polls /up, and on removal cleans the hosts line that the Once UI orphans (including the orphan-only case, where the app is already gone). Bubble Tea TUI for interactive use; non-interactive CLI for scripts. Curated apps live in /etc/once-add/apps.toml; seeded with Writebook + Campfire on first run.
94 lines
2.8 KiB
Go
94 lines
2.8 KiB
Go
package deploy
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestValidateName(t *testing.T) {
|
|
good := []string{"book", "chat", "a", "my-app", "app1", "1book"}
|
|
for _, n := range good {
|
|
if err := ValidateName(n); err != nil {
|
|
t.Errorf("ValidateName(%q) = %v, want nil", n, err)
|
|
}
|
|
}
|
|
bad := []string{"", "Book", "book.local", "-book", "book-", "a_b", "café", "two words"}
|
|
for _, n := range bad {
|
|
if err := ValidateName(n); err == nil {
|
|
t.Errorf("ValidateName(%q) = nil, want error", n)
|
|
}
|
|
}
|
|
long := make([]byte, 64)
|
|
for i := range long {
|
|
long[i] = 'a'
|
|
}
|
|
if err := ValidateName(string(long)); err == nil {
|
|
t.Errorf("ValidateName(64 chars) = nil, want error")
|
|
}
|
|
}
|
|
|
|
func TestParseHosts(t *testing.T) {
|
|
// Two app containers' `once` labels concatenated, as docker inspect emits.
|
|
blob := `{"image":"ghcr.io/basecamp/writebook","host":"Book.local","tls":false}
|
|
{"host":"chat.local","image":"once-campfire"}
|
|
{"host":"public.example.com"}`
|
|
got := parseHosts(blob)
|
|
if !got["book.local"] { // lowercased
|
|
t.Errorf("expected book.local in %v", got)
|
|
}
|
|
if !got["chat.local"] {
|
|
t.Errorf("expected chat.local in %v", got)
|
|
}
|
|
if got["public.example.com"] {
|
|
t.Errorf("non-.local host should be excluded: %v", got)
|
|
}
|
|
if len(got) != 2 {
|
|
t.Errorf("got %d hosts, want 2: %v", len(got), got)
|
|
}
|
|
}
|
|
|
|
func TestHost(t *testing.T) {
|
|
if Host("book") != "book.local" {
|
|
t.Errorf("Host(book) = %q", Host("book"))
|
|
}
|
|
}
|
|
|
|
func TestFilterHostLines(t *testing.T) {
|
|
in := "127.0.1.1\tdevbox\n" +
|
|
"127.0.0.1 book.local\n" +
|
|
"127.0.0.1 mybook.local\n" + // must NOT match book.local
|
|
"127.0.0.1 book.local2\n" + // must NOT match book.local
|
|
"127.0.0.1 chat.local\n"
|
|
out, removed := filterHostLines(in, "book.local")
|
|
if !removed {
|
|
t.Fatal("expected book.local to be removed")
|
|
}
|
|
if strings.Contains(out, "\n127.0.0.1 book.local\n") || strings.HasSuffix(out, "127.0.0.1 book.local") {
|
|
t.Errorf("book.local line still present:\n%s", out)
|
|
}
|
|
for _, keep := range []string{"devbox", "mybook.local", "book.local2", "chat.local"} {
|
|
if !strings.Contains(out, keep) {
|
|
t.Errorf("filter wrongly dropped %q:\n%s", keep, out)
|
|
}
|
|
}
|
|
if _, removed := filterHostLines(in, "absent.local"); removed {
|
|
t.Error("absent.local should report nothing removed")
|
|
}
|
|
}
|
|
|
|
func TestRemoveDoneMsg(t *testing.T) {
|
|
cases := []struct {
|
|
appRemoved, hostsRemoved bool
|
|
want string
|
|
}{
|
|
{true, true, "Removed book.local"},
|
|
{true, false, "Removed book.local (no /etc/hosts entry to clean)"},
|
|
{false, true, "Cleaned orphan /etc/hosts entry for book.local"},
|
|
{false, false, "Nothing to remove for book.local"},
|
|
}
|
|
for _, c := range cases {
|
|
if got := removeDoneMsg("book.local", c.appRemoved, c.hostsRemoved); got != c.want {
|
|
t.Errorf("removeDoneMsg(_, %v, %v) = %q, want %q", c.appRemoved, c.hostsRemoved, got, c.want)
|
|
}
|
|
}
|
|
}
|