4aaf1d4f91
Implements spec #135 (spec 2 of 4, derived from wayfinder map #114; decisions settled in #120/#121/#125/#131). Blocked-by #134 is merged, so this lands on `main`. Four owner actions the dashboard can now perform, one ticket each: - **#149** — Latest Chapter correction: one numeric input, overwritten by the next machine write. - **#151** — Series URL repair: owner-typed, gated by the poller's own fetch gate. - **#150 / #153 / #154** — Cover replacement: addresses derived from bytes (`#150`), a Forced Poll replaces the Cover while an ordinary pass still only fills a blank one (`#153`), and byte reclamation is one guarded helper, file first / covers row last (`#154`). - **#155** — Orphan removal: one Series at a time, with the foreign key as the guard. Plus **#152** — Latest Chapter provenance: one derived line naming the actor class, so an owner can tell a hand-edited number from a machine read. - Migration `0015_latest_correction.sql` adds the correction/provenance columns; `0009` now derives cover addresses from bytes. - ADR `0014-cover-addresses-from-bytes.md` records the address scheme. Backend tests cover the store, poller, admin handlers, and web routes (`go test ./...`, needs Docker). Reviewed-on: #156 Co-authored-by: Sulthan Zaki <sultankiki05@gmail.com> Co-committed-by: Sulthan Zaki <sultankiki05@gmail.com>
35 lines
1.2 KiB
Go
35 lines
1.2 KiB
Go
package web
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"bookmarkmanager/backend/internal/store"
|
|
)
|
|
|
|
// seriesDetailView derives the provenance line from the three stamps the
|
|
// admin projection already carries: the correction stamp outranks a raising
|
|
// Reader, which outranks a check stamp, and a Series with none of the three
|
|
// renders no line at all — it was never read, and no actor class is true of
|
|
// it. Acquisition stamps latest_checked_at exactly as a Poll does, so an
|
|
// acquired value lands in the same "machine read" class (#152).
|
|
func TestSeriesDetailViewProvenance(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
a store.AdminSeries
|
|
want string
|
|
}{
|
|
{"correction stamp", store.AdminSeries{LatestCorrectedAt: 1}, "correction"},
|
|
{"raising reader only", store.AdminSeries{RaisedByReader: true}, "sighting"},
|
|
{"check stamp only", store.AdminSeries{LatestCheckedAt: 1}, "machine read"},
|
|
{"correction outranks sighting", store.AdminSeries{LatestCorrectedAt: 1, RaisedByReader: true}, "correction"},
|
|
{"none of the three", store.AdminSeries{}, ""},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if got := (&Handler{}).seriesDetailView(tc.a).Provenance; got != tc.want {
|
|
t.Fatalf("Provenance = %q, want %q", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|