Artifact 3c9a19415d3fb705e9cdc0842c4d82acd82ce1d05db2c5fc74363f9221047f92:
- File
internal/manifest/ticket_write_fossilbin_test.go
— part of check-in
[68f2c3d930]
at
2026-10-26 03:14:14
on branch main
— feat: ticket writes, ticket reads and timeline-since
Repo.TicketAdd and Repo.TicketSet write the ticket-change artifact stock `fossil ticket add` / `fossil ticket set` write (D, J incl. +append, K, U, Z), byte for byte, and crosslink it in the same transaction so the ticket, ticketchng and event rows read back at once. TicketOpts.Comment is stored as the stock ticket forms store a remark: icomment with login, username and mimetype.
Repo.TicketList, Repo.TicketChanges and Repo.TimelineSince expose the reads the html-artifact-publisher engine needs, as JSON-ready values. manifest.Timeline gains Kinds and Since to back TimelineSince.
Tracked in #503 and #504. (user: [email protected], size: 4371) [annotate] [blame] [check-ins using] [more...]
package manifest import ( "os/exec" "path/filepath" "strings" "testing" "time" "github.com/danmestas/go-libfossil/internal/content" "github.com/danmestas/go-libfossil/internal/repo" "github.com/danmestas/go-libfossil/simio" "github.com/danmestas/go-libfossil/testutil" ) // ticketWriteCase is one `fossil ticket add|set` invocation and the // TicketWrite that must produce the same artifact bytes. type ticketWriteCase struct { name string set bool // `ticket set` on the ticket the first case created date string user string fields []string // name, value pairs as the fossil command line takes them } // TestWriteTicketMatchesFossilBytes checks the artifact WriteTicket stores // against the one stock `fossil ticket add` / `ticket set` writes for the same // ticket, fields, user and time: the bytes, and so the hash, must be equal. // The fields cover a value fossil must escape (spaces, a newline, a // backslash), an append card, and the remark fields the stock web forms // write. func TestWriteTicketMatchesFossilBytes(t *testing.T) { bin := testutil.RequireFossilBin(t) dir := t.TempDir() fossilRepo := filepath.Join(dir, "f.fossil") run := func(args ...string) string { t.Helper() out, err := exec.Command(bin, args...).CombinedOutput() if err != nil { t.Fatalf("fossil %s: %v\n%s", strings.Join(args, " "), err, out) } return string(out) } run("init", fossilRepo, "-A", "alice") run("user", "new", "bob", "Bob", "pw", "-R", fossilRepo) run("user", "default", "alice", "-R", fossilRepo) r, err := repo.Create(filepath.Join(dir, "g.fossil"), "alice", simio.CryptoRand{}, "") if err != nil { t.Fatal(err) } defer r.Close() const tkt = "0123456789abcdef0123456789abcdef01234567" cases := []ticketWriteCase{ {name: "add", date: "2026-01-02T03:04:05.678", user: "alice", fields: []string{ "title", "two words\nand a \\ backslash", "status", "Open", "type", "Code_Defect", "icomment", "first remark", "login", "alice", "username", "alice", "mimetype", "text/plain", }}, {name: "set", set: true, date: "2026-01-02T03:04:06.000", user: "bob", fields: []string{ "status", "Closed", "resolution", "Fixed", "+comment", " appended", }}, } for _, c := range cases { args := []string{"ticket", "add"} if c.set { args = []string{"ticket", "set", tkt} } args = append(args, c.fields...) args = append(args, "--date-override", c.date, "--user-override", c.user, "--uuid-override", tkt, "-R", fossilRepo) run(args...) hash := strings.TrimSpace(run("sql", "-R", fossilRepo, "SELECT uuid FROM blob WHERE rid=(SELECT max(rid) FROM blob)")) hash = strings.Trim(hash, "'") want := run("artifact", hash, "-R", fossilRepo) if got := writeTicketCase(t, r, tkt, c); got != want { t.Fatalf("%s: artifact differs from fossil's\nfossil:\n%s\nours:\n%s", c.name, want, got) } } assertFossilRederivesTicketRows(t, bin, r.Path()) } // writeTicketCase writes c to ticket tkt with WriteTicket and returns the // stored artifact. func writeTicketCase(t *testing.T, r *repo.Repo, tkt string, c ticketWriteCase) string { t.Helper() when, err := time.Parse("2006-01-02T15:04:05.000", c.date) if err != nil { t.Fatal(err) } fields := map[string]string{} for i := 0; i < len(c.fields); i += 2 { fields[c.fields[i]] = c.fields[i+1] } rid, _, err := WriteTicket(r, TicketWrite{UUID: tkt, Fields: fields, User: c.user, Time: when}) if err != nil { t.Fatalf("%s: WriteTicket: %v", c.name, err) } got, err := content.Expand(r.DB(), rid) if err != nil { t.Fatal(err) } return string(got) } // assertFossilRederivesTicketRows checks the rows WriteTicket derived as it // stored each artifact against what `fossil rebuild` derives from the same // blobs. func assertFossilRederivesTicketRows(t *testing.T, bin, path string) { t.Helper() ours := snapshotTicketDerived(t, path) if ours["count"] == "0" { t.Fatal("WriteTicket derived no ticket events") } if out, err := exec.Command(bin, "rebuild", path).CombinedOutput(); err != nil { t.Fatalf("fossil rebuild: %v\n%s", err, out) } reference := snapshotTicketDerived(t, path) for _, key := range []string{"count", "event", "ticket", "ticketchng"} { if ours[key] != reference[key] { t.Errorf("%s differs from what fossil derived\n fossil: %s\n ours: %s", key, reference[key], ours[key]) } } }