A zip is random-access (unlike a streamed .tgz), so a member can be rewritten in place. ServeZipWrite (handler/zipwrite.go) handles PUT (write/create a member) and DELETE (remove) inside the .zddc.zip bundle: read the whole archive, snapshot the prior member into an in-zip .history/<member>/<ts> + append a log.jsonl audit line, mutate, then write a fresh zip and atomically rename over the original (serialized on one mutex). After a write the policy cache is invalidated so .zddc policy members take effect immediately, and the apps.Bundle mtime-reload picks up tool-HTML edits. Gated to active admins and to the .zddc.zip bundle only (dispatch's bundle gate already 404s everyone else; content zips — transmittal/WORM packages — stay read-only and 405). Writing into the in-zip .history/ is refused (append-only). Also fixes a read collision: a .zddc member INSIDE a zip (e.g. a policy member, URL ".../.zddc.zip/<dir>/.zddc") was being grabbed by the raw-.zddc-view handler and 500ing; that handler now excludes ".zip/" paths so the zip intercept serves the member. Tests: writer round-trip (incl. wildcard member); dispatch create+overwrite, policy-takes-effect, in-zip history audit, read-back, non-admin 404, content-zip 405. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
31 lines
806 B
Go
31 lines
806 B
Go
package handler
|
|
|
|
import (
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestZipWriteRoundTrip(t *testing.T) {
|
|
zp := filepath.Join(t.TempDir(), ".zddc.zip")
|
|
if err := writeZipAtomic(zp, map[string][]byte{"a.txt": []byte("v1")}, []string{"a.txt"}); err != nil {
|
|
t.Fatalf("seed: %v", err)
|
|
}
|
|
m, ord, err := readZipMembers(zp)
|
|
if err != nil {
|
|
t.Fatalf("read1: %v", err)
|
|
}
|
|
addMember(m, &ord, "*/.zddc", []byte("hello-wildcard"))
|
|
if err := writeZipAtomic(zp, m, ord); err != nil {
|
|
t.Fatalf("write2: %v", err)
|
|
}
|
|
m2, _, err := readZipMembers(zp)
|
|
if err != nil {
|
|
t.Fatalf("read2: %v", err)
|
|
}
|
|
if got := string(m2["*/.zddc"]); got != "hello-wildcard" {
|
|
t.Errorf("wildcard member = %q, want hello-wildcard", got)
|
|
}
|
|
if got := string(m2["a.txt"]); got != "v1" {
|
|
t.Errorf("a.txt = %q, want v1", got)
|
|
}
|
|
}
|