ZDDC/zddc/internal/zddc/validate_test.go
ZDDC e7f6334daa chore: retire mdedit tool — markdown editor lives in browse now
mdedit/ is gone. Its functionality moved into browse's preview plugin
(browse/js/preview-markdown.js) — YAML front matter editing, outline,
and on-demand DOCX/HTML/PDF download all happen there. Browse is the
default_tool for working/ + reviewing/ as of the previous commit, so
existing URLs of the form /<project>/working land on browse without
operator action.

Removed:

  • mdedit/ source tree (Toast UI app, CSS, JS, template, build.sh)
  • zddc/internal/apps/embedded/mdedit.html (//go:embed blob)
  • tests/mdedit.spec.js + the "mdedit" project in playwright.config.js
  • mdedit entries in zddc/internal/apps/embed.go (//go:embed, var,
    switch case in EmbeddedBytes)
  • "mdedit" in zddc/internal/zddc/validate.go AppNames + the matching
    error-message app list
  • "mdedit.html" branch in zddc/internal/apps/handler.go MatchAppHTML
  • mdedit case in tests (handler_test.go, validate_test.go,
    zddchandler_test.go) — test fixtures now use browse/classifier
  • mdedit from build (per-tool build.sh loop, tool-list literals,
    composer cards) and shared/build-lib.sh ZDDC_RELEASE_TOOLS
  • mdedit from freshen-channel's tool list and usage banner
  • mdedit-specific paragraphs in AGENTS.md and ARCHITECTURE.md;
    Markdown Editor section in ARCHITECTURE.md rewritten to point at
    browse/js/preview-markdown.js
  • mdedit from CLAUDE.md, README.md, zddc/README.md tool lists

Historical mdedit_v*.html / mdedit_v*.html.sig files in
/srv/zddc/releases/ on the deploy host are immutable history — they
stay where they are. The next ./build release cut will simply not
produce new mdedit_v* artifacts.
2026-05-13 10:34:31 -05:00

236 lines
6 KiB
Go

package zddc
import (
"testing"
)
func TestValidatePattern(t *testing.T) {
cases := []struct {
pattern string
ok bool
}{
{"alice@example.com", true},
{"*@example.com", true},
{"alice@*", true},
{"*", true},
{"", false},
{" alice@example.com", false},
{"alice@example.com ", false},
{"alice @example.com", false},
{"alice@ex ample.com", false},
{"alice@@example.com", false},
{"@example.com", false},
{"alice@", false},
{"@", false},
}
for _, tc := range cases {
t.Run(tc.pattern, func(t *testing.T) {
err := ValidatePattern(tc.pattern)
if tc.ok && err != nil {
t.Errorf("ValidatePattern(%q) = %v, want nil", tc.pattern, err)
}
if !tc.ok && err == nil {
t.Errorf("ValidatePattern(%q) = nil, want error", tc.pattern)
}
})
}
}
func TestValidateFile(t *testing.T) {
zf := ZddcFile{
Title: "ok",
ACL: ACLRules{Allow: []string{"good@example.com", "@bad"}, Deny: []string{"two@@ats"}},
Admins: []string{"@nobody"},
}
errs := ValidateFile(zf)
// expect 3 errors
if len(errs) != 3 {
t.Fatalf("got %d errors, want 3: %+v", len(errs), errs)
}
wantFields := map[string]bool{
"acl.allow[1]": false,
"acl.deny[0]": false,
"admins[0]": false,
}
for _, e := range errs {
if _, ok := wantFields[e.Field]; !ok {
t.Errorf("unexpected error field: %q", e.Field)
continue
}
wantFields[e.Field] = true
}
for f, seen := range wantFields {
if !seen {
t.Errorf("missing error for field %q", f)
}
}
}
func TestValidateAppSourceSpec(t *testing.T) {
cases := []struct {
spec string
ok bool
}{
// Channel shorthand (with and without leading colon)
{"stable", true},
{"beta", true},
{"alpha", true},
{":stable", true},
{":beta", true},
{":alpha", true},
// Version pin shorthand (full, partial, with/without leading 'v')
{"v0.0.4", true},
{"0.0.4", true},
{"v0.0", true},
{"0.0", true},
{"v0", true},
{"0", true},
{"v1.2.3", true},
{":v0.0.4", true},
{":0.0.4", true},
// URLs
{"https://zddc.varasys.io/releases/archive_stable.html", true},
{"http://my-fork.example.com/archive.html", true},
{"https://my-mirror.example/releases", true}, // URL-prefix only
{"https://my-mirror.example/releases:stable", true}, // URL-prefix + channel
{"https://my-mirror.example/releases:v0.0.4", true}, // URL-prefix + version
{"https://my-mirror.example:8080/releases", true}, // URL with port
{"https://my-mirror.example:8080/releases:stable", true}, // URL with port + channel
// Paths
{"/abs/path.html", true},
{"./local.html", true},
{"../sibling.html", true},
// Errors
{"", false},
{" stable", false},
{"stable ", false},
{"with space", false},
{"https://", false},
{"https://host/path/file.html:stable", false}, // .html URL with suffix
{"random-thing", false},
{":", false},
{":random", false},
{"v", false},
{"v0.", false},
{".0.0", false},
{"v0.0.0.0", false},
{"v0.a.0", false},
{"https://my-mirror.example/releases:bogus", false}, // bad channel suffix
}
for _, tc := range cases {
t.Run(tc.spec, func(t *testing.T) {
err := ValidateAppSourceSpec(tc.spec)
if tc.ok && err != nil {
t.Errorf("ValidateAppSourceSpec(%q) = %v, want nil", tc.spec, err)
}
if !tc.ok && err == nil {
t.Errorf("ValidateAppSourceSpec(%q) = nil, want error", tc.spec)
}
})
}
}
func TestIsValidAppsKey(t *testing.T) {
cases := []struct {
key string
ok bool
}{
{"default", true},
{"archive", true},
{"transmittal", true},
{"classifier", true},
{"browse", true},
{"landing", true},
{"unknown", false},
{"", false},
{"DEFAULT", false}, // case-sensitive
}
for _, tc := range cases {
t.Run(tc.key, func(t *testing.T) {
if got := IsValidAppsKey(tc.key); got != tc.ok {
t.Errorf("IsValidAppsKey(%q) = %v, want %v", tc.key, got, tc.ok)
}
})
}
}
func TestValidateFile_Apps(t *testing.T) {
zf := ZddcFile{
Apps: map[string]string{
"archive": "stable", // ok
"classifier": "v0.0.4", // ok
"default": "https://zddc.varasys.io/releases:stable", // ok (default key + URL+channel)
"transmittal": ":beta", // ok (channel-only)
"browse": "https://my-mirror.example/releases", // ok (URL-prefix only)
"unknown": "stable", // unknown app
"landing": "what is this", // bad spec
},
}
errs := ValidateFile(zf)
want := map[string]bool{
"apps.unknown": false,
"apps.landing": false,
}
for _, e := range errs {
if _, ok := want[e.Field]; ok {
want[e.Field] = true
} else {
t.Errorf("unexpected error field: %q (%s)", e.Field, e.Message)
}
}
for f, seen := range want {
if !seen {
t.Errorf("missing error for field %q (got: %+v)", f, errs)
}
}
}
func TestValidateProjectName(t *testing.T) {
cases := []struct {
name string
ok bool
}{
{"alpha", true},
{"Alpha", true},
{"a", true},
{"a1", true},
{"a-1", true},
{"a_b", true},
{"123-project", true},
{"Site-3", true},
{"", false},
{".hidden", false},
{"_template", false},
{"-leading-dash", false},
{"foo bar", false},
{"foo/bar", false},
{"foo\\bar", false},
{"foo.bar", false},
{"..", false},
{".", false},
{string(make([]byte, 65)), false},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
err := ValidateProjectName(tc.name)
if tc.ok && err != nil {
t.Errorf("ValidateProjectName(%q) = %v, want nil", tc.name, err)
}
if !tc.ok && err == nil {
t.Errorf("ValidateProjectName(%q) = nil, want error", tc.name)
}
})
}
}
func TestValidateFileTitleLength(t *testing.T) {
long := make([]byte, 201)
for i := range long {
long[i] = 'a'
}
zf := ZddcFile{Title: string(long)}
errs := ValidateFile(zf)
if len(errs) != 1 || errs[0].Field != "title" {
t.Fatalf("expected one title-length error, got %+v", errs)
}
}