package evidence import ( "bytes" "context" "crypto/sha256" "database/sql" "encoding/hex" "errors" "image" "image/color" "image/png" "io" "os" "path/filepath" "runtime" "strings" "sync" "testing" "time" "cmbuyer/admin/internal/deviceauth" core "cmbuyer/admin/internal/evidence" "cmbuyer/admin/internal/migrations" "cmbuyer/admin/internal/storage/sqlite" ) const ( testTaskID = "13c9f507-7473-4fa6-8d71-8786c34c6301" testAuthID = "23c9f507-7473-4fa6-8d71-8786c34c6301" testAttemptID = "33c9f507-7473-4fa6-8d71-8786c34c6301" testUploadKey = "43c9f507-7473-4fa6-8d71-8786c34c6301" testDeviceID = "53c9f507-7473-4fa6-8d71-8786c34c6301" ) func TestStageCommitReplayAndOpen(t *testing.T) { database, store := newTestStore(t) insertAttemptFixture(t, database) pngBytes := makePNG(t, 8, 6) hash := sha256Hex(pngBytes) metadata := core.UploadMetadata{UploadKey: testUploadKey, TaskID: testTaskID, AttemptID: testAttemptID, Kind: core.KindSKUPanelGate1, PrivacyTier: core.PrivacyInternalRaw, SHA256: hash, CapturedAt: time.Date(2026, 8, 4, 1, 2, 3, 0, time.UTC)} principal := deviceauth.Principal{ID: testDeviceID} staged, err := store.Stage(bytes.NewReader(pngBytes), core.PNGContentType) if err != nil { t.Fatalf("Stage: %v", err) } asset, replayed, err := store.Commit(context.Background(), principal, metadata, staged) if err != nil || replayed { t.Fatalf("Commit = replayed %t, err %v", replayed, err) } if asset.SHA256 != hash || asset.ByteSize != int64(len(pngBytes)) || asset.Width != 8 || asset.Height != 6 || asset.StorageKey != hash[:2]+"/"+hash+".png" { t.Fatalf("asset = %#v", asset) } opened, reader, err := store.Open(context.Background(), asset.ID) if err != nil { t.Fatalf("Open: %v", err) } got, err := io.ReadAll(reader) _ = reader.Close() if err != nil || !bytes.Equal(got, pngBytes) || opened.ID != asset.ID { t.Fatalf("opened asset changed: bytes=%t asset=%#v err=%v", bytes.Equal(got, pngBytes), opened, err) } replayStage, err := store.Stage(bytes.NewReader(pngBytes), core.PNGContentType) if err != nil { t.Fatalf("stage replay: %v", err) } replayedAsset, replayed, err := store.Commit(context.Background(), principal, metadata, replayStage) if err != nil || !replayed || replayedAsset.ID != asset.ID { t.Fatalf("replay = %#v, %t, %v", replayedAsset, replayed, err) } conflictStage, err := store.Stage(bytes.NewReader(pngBytes), core.PNGContentType) if err != nil { t.Fatalf("stage conflict: %v", err) } conflicting := metadata conflicting.CapturedAt = conflicting.CapturedAt.Add(time.Second) if _, _, err := store.Commit(context.Background(), principal, conflicting, conflictStage); !errors.Is(err, core.ErrConflict) { t.Fatalf("conflicting replay error = %v", err) } } func TestCommitRequiresCurrentClaimOwnerButClosedClaimKeepsHistoricalReplay(t *testing.T) { database, store := newTestStore(t) insertAttemptFixture(t, database) pngBytes := makePNG(t, 4, 3) metadata := testMetadata(sha256Hex(pngBytes)) stage := func() core.StagedFile { staged, err := store.Stage(bytes.NewReader(pngBytes), core.PNGContentType) if err != nil { t.Fatalf("Stage: %v", err) } return staged } otherDevice := deviceauth.Principal{ID: "73c9f507-7473-4fa6-8d71-8786c34c6301"} if _, _, err := store.Commit(context.Background(), otherDevice, metadata, stage()); !errors.Is(err, core.ErrInvalid) { t.Fatalf("device B upload to device A attempt error = %v", err) } principal := deviceauth.Principal{ID: testDeviceID} asset, replayed, err := store.Commit(context.Background(), principal, metadata, stage()) if err != nil || replayed { t.Fatalf("owner first Commit = replayed %v, err %v", replayed, err) } if _, err := database.Exec("UPDATE purchase_attempt_claims SET closed_at='2026-08-04T03:00:00Z' WHERE attempt_id=?", testAttemptID); err != nil { t.Fatalf("close claim: %v", err) } replayedAsset, replayed, err := store.Commit(context.Background(), principal, metadata, stage()) if err != nil || !replayed || replayedAsset.ID != asset.ID { t.Fatalf("closed claim historical replay = %#v replayed %v err %v", replayedAsset, replayed, err) } newMetadata := metadata newMetadata.UploadKey = "83c9f507-7473-4fa6-8d71-8786c34c6301" if _, _, err := store.Commit(context.Background(), principal, newMetadata, stage()); !errors.Is(err, core.ErrInvalid) { t.Fatalf("closed claim new upload error = %v", err) } } func TestConcurrentReplayCreatesOneAsset(t *testing.T) { database, store := newTestStore(t) insertAttemptFixture(t, database) pngBytes := makePNG(t, 3, 2) metadata := core.UploadMetadata{UploadKey: testUploadKey, TaskID: testTaskID, AttemptID: testAttemptID, Kind: core.KindSKUPanelGate1, PrivacyTier: core.PrivacyInternalRaw, SHA256: sha256Hex(pngBytes), CapturedAt: time.Date(2026, 8, 4, 1, 2, 3, 0, time.UTC)} staged := make([]core.StagedFile, 2) for index := range staged { var err error staged[index], err = store.Stage(bytes.NewReader(pngBytes), core.PNGContentType) if err != nil { t.Fatalf("Stage %d: %v", index, err) } } var wait sync.WaitGroup wait.Add(2) assets := make([]core.Asset, 2) replays := make([]bool, 2) errorsSeen := make([]error, 2) for index := range staged { go func(index int) { defer wait.Done() assets[index], replays[index], errorsSeen[index] = store.Commit(context.Background(), deviceauth.Principal{ID: testDeviceID}, metadata, staged[index]) }(index) } wait.Wait() if errorsSeen[0] != nil || errorsSeen[1] != nil || assets[0].ID != assets[1].ID || replays[0] == replays[1] { t.Fatalf("concurrent commits assets=%#v replays=%#v errors=%#v", assets, replays, errorsSeen) } var count int if err := database.QueryRow("SELECT COUNT(*) FROM evidence_assets").Scan(&count); err != nil || count != 1 { t.Fatalf("asset count = %d, err %v", count, err) } } func TestPlatformDirectorySync(t *testing.T) { if err := syncDirectory(t.TempDir()); err != nil { t.Fatalf("syncDirectory must either establish the durability boundary or fail closed: %v", err) } } func TestNewStoreRetriesRootParentSyncWhenRootAlreadyExists(t *testing.T) { database, _ := newTestStore(t) parent := t.TempDir() root := filepath.Join(parent, "retry-root") injected := errors.New("injected root parent sync failure") if _, err := newStore(database, root, func(string) error { return injected }); !errors.Is(err, injected) { t.Fatalf("first newStore error = %v, want injected root sync failure", err) } if info, err := os.Stat(root); err != nil || !info.IsDir() { t.Fatalf("failed parent sync must leave root for retry: info=%v err=%v", info, err) } var paths []string store, err := newStore(database, root, func(path string) error { paths = append(paths, path) return syncDirectory(path) }) if err != nil { t.Fatalf("retry newStore: %v", err) } if store == nil || len(paths) == 0 || paths[0] != parent { t.Fatalf("retry sync paths = %#v, want root parent %q first", paths, parent) } } func TestCommitSyncsShardAndRenameBeforeDatabaseWrite(t *testing.T) { database, store := newTestStore(t) insertAttemptFixture(t, database) pngBytes := makePNG(t, 4, 3) hash := sha256Hex(pngBytes) metadata := testMetadata(hash) staged, err := store.Stage(bytes.NewReader(pngBytes), core.PNGContentType) if err != nil { t.Fatalf("Stage: %v", err) } finalPath, err := store.pathForKey(storageKey(hash)) if err != nil { t.Fatalf("final path: %v", err) } finalDirectory := filepath.Dir(finalPath) var events []string store.syncDirectory = func(path string) error { var count int if err := database.QueryRow("SELECT COUNT(*) FROM evidence_assets").Scan(&count); err != nil { t.Fatalf("count evidence before directory sync: %v", err) } if count != 0 { t.Fatalf("database row became visible before directory sync: %d", count) } switch path { case store.root: events = append(events, "sync-root") if path != store.root { t.Fatalf("shard parent sync path = %q, want evidence root %q", path, store.root) } if info, err := os.Stat(finalDirectory); err != nil || !info.IsDir() { t.Fatalf("shard directory must exist before parent sync: info=%v err=%v", info, err) } if _, err := os.Stat(finalPath); !errors.Is(err, os.ErrNotExist) { t.Fatalf("final file exists before publication: %v", err) } case finalDirectory: events = append(events, "sync-shard") if info, err := os.Stat(finalPath); err != nil || !info.Mode().IsRegular() { t.Fatalf("renamed file must exist before shard sync: info=%v err=%v", info, err) } default: t.Fatalf("unexpected extra directory sync: %q", path) } return syncDirectory(path) } store.syncFile = func(file *os.File) error { if filepath.Dir(file.Name()) != finalDirectory || !strings.HasPrefix(filepath.Base(file.Name()), ".publish-") { t.Fatalf("publication temp is not inside shard: %q", file.Name()) } events = append(events, "sync-file") return file.Sync() } store.renameFile = func(oldPath, newPath string) error { if filepath.Dir(oldPath) != filepath.Dir(newPath) || newPath != finalPath { t.Fatalf("rename is not same-directory publication: %q -> %q", oldPath, newPath) } events = append(events, "rename") return os.Rename(oldPath, newPath) } if _, replayed, err := store.Commit(context.Background(), deviceauth.Principal{ID: testDeviceID}, metadata, staged); err != nil || replayed { t.Fatalf("Commit = replayed %t, err %v", replayed, err) } if got, want := strings.Join(events, ","), "sync-root,sync-root,sync-file,rename,sync-shard"; got != want { t.Fatalf("durability order = %q, want %q", got, want) } assertEvidenceCount(t, database, 1) assertNoPublishTemps(t, finalDirectory) } func TestCommitDirectorySyncFailuresNeverWriteDatabase(t *testing.T) { for _, failAt := range []int{1, 2, 3} { t.Run(map[int]string{1: "new shard parent", 2: "unconditional shard parent", 3: "rename target"}[failAt], func(t *testing.T) { database, store := newTestStore(t) insertAttemptFixture(t, database) pngBytes := makePNG(t, 4, 3) hash := sha256Hex(pngBytes) staged, err := store.Stage(bytes.NewReader(pngBytes), core.PNGContentType) if err != nil { t.Fatalf("Stage: %v", err) } finalPath, err := store.pathForKey(storageKey(hash)) if err != nil { t.Fatalf("final path: %v", err) } injected := errors.New("injected directory sync failure") calls := 0 store.syncDirectory = func(path string) error { calls++ if calls == failAt { return injected } return syncDirectory(path) } if _, _, err := store.Commit(context.Background(), deviceauth.Principal{ID: testDeviceID}, testMetadata(hash), staged); !errors.Is(err, injected) { t.Fatalf("Commit error = %v, want injected sync failure", err) } if calls != failAt { t.Fatalf("sync calls = %d, want %d", calls, failAt) } assertEvidenceCount(t, database, 0) _, statErr := os.Stat(finalPath) if failAt < 3 && !errors.Is(statErr, os.ErrNotExist) { t.Fatalf("file exists before rename durability boundary: %v", statErr) } if failAt == 3 && statErr != nil { t.Fatalf("post-rename sync failure may leave an orphan file, stat error = %v", statErr) } assertNoPublishTemps(t, filepath.Dir(finalPath)) }) } } func TestCommitRetriesShardParentSyncAfterPriorFailure(t *testing.T) { database, store := newTestStore(t) insertAttemptFixture(t, database) pngBytes := makePNG(t, 4, 3) hash := sha256Hex(pngBytes) finalPath, err := store.pathForKey(storageKey(hash)) if err != nil { t.Fatalf("final path: %v", err) } firstStage, err := store.Stage(bytes.NewReader(pngBytes), core.PNGContentType) if err != nil { t.Fatalf("first Stage: %v", err) } injected := errors.New("injected first shard parent sync failure") store.syncDirectory = func(string) error { return injected } if _, _, err := store.Commit(context.Background(), deviceauth.Principal{ID: testDeviceID}, testMetadata(hash), firstStage); !errors.Is(err, injected) { t.Fatalf("first Commit error = %v", err) } if info, err := os.Stat(filepath.Dir(finalPath)); err != nil || !info.IsDir() { t.Fatalf("failed first sync must leave the created shard for retry: info=%v err=%v", info, err) } assertEvidenceCount(t, database, 0) secondStage, err := store.Stage(bytes.NewReader(pngBytes), core.PNGContentType) if err != nil { t.Fatalf("second Stage: %v", err) } var paths []string store.syncDirectory = func(path string) error { paths = append(paths, path) return syncDirectory(path) } if _, replayed, err := store.Commit(context.Background(), deviceauth.Principal{ID: testDeviceID}, testMetadata(hash), secondStage); err != nil || replayed { t.Fatalf("retry Commit = replayed %t, err %v", replayed, err) } if len(paths) != 2 || paths[0] != store.root || paths[1] != filepath.Dir(finalPath) { t.Fatalf("retry sync paths = %#v, want root then shard", paths) } assertEvidenceCount(t, database, 1) assertNoPublishTemps(t, filepath.Dir(finalPath)) } func TestCommitPublicationFailuresCleanTempAndNeverWriteDatabase(t *testing.T) { for _, name := range []string{"file sync", "rename"} { t.Run(name, func(t *testing.T) { database, store := newTestStore(t) insertAttemptFixture(t, database) pngBytes := makePNG(t, 4, 3) hash := sha256Hex(pngBytes) staged, err := store.Stage(bytes.NewReader(pngBytes), core.PNGContentType) if err != nil { t.Fatalf("Stage: %v", err) } finalPath, err := store.pathForKey(storageKey(hash)) if err != nil { t.Fatalf("final path: %v", err) } injected := errors.New("injected publication failure") if name == "file sync" { store.syncFile = func(*os.File) error { return injected } } else { store.renameFile = func(string, string) error { return injected } } if _, _, err := store.Commit(context.Background(), deviceauth.Principal{ID: testDeviceID}, testMetadata(hash), staged); !errors.Is(err, injected) { t.Fatalf("Commit error = %v, want injected publication failure", err) } if _, err := os.Stat(finalPath); !errors.Is(err, os.ErrNotExist) { t.Fatalf("final file exists after failed publication: %v", err) } assertNoPublishTemps(t, filepath.Dir(finalPath)) assertEvidenceCount(t, database, 0) }) } } func TestCommitDatabaseFailuresAfterDurableRenameLeaveOnlyOrphan(t *testing.T) { for _, test := range []struct { name string inject func(*testing.T, *sql.DB, *Store, error) }{ { name: "insert", inject: func(t *testing.T, database *sql.DB, _ *Store, _ error) { t.Helper() if _, err := database.Exec(`CREATE TRIGGER fail_evidence_insert BEFORE INSERT ON evidence_assets BEGIN SELECT RAISE(ABORT, 'injected insert failure'); END`); err != nil { t.Fatalf("create insert failure trigger: %v", err) } }, }, { name: "commit", inject: func(_ *testing.T, _ *sql.DB, store *Store, injected error) { store.commitTx = func(*sql.Tx) error { return injected } }, }, } { t.Run(test.name, func(t *testing.T) { database, store := newTestStore(t) insertAttemptFixture(t, database) pngBytes := makePNG(t, 4, 3) hash := sha256Hex(pngBytes) staged, err := store.Stage(bytes.NewReader(pngBytes), core.PNGContentType) if err != nil { t.Fatalf("Stage: %v", err) } finalPath, err := store.pathForKey(storageKey(hash)) if err != nil { t.Fatalf("final path: %v", err) } injected := errors.New("injected database failure") test.inject(t, database, store, injected) syncCalls := 0 store.syncDirectory = func(path string) error { syncCalls++ return syncDirectory(path) } if _, _, err := store.Commit(context.Background(), deviceauth.Principal{ID: testDeviceID}, testMetadata(hash), staged); err == nil { t.Fatal("Commit unexpectedly succeeded") } if syncCalls != 3 { t.Fatalf("database failure occurred before both durability syncs: sync calls = %d", syncCalls) } if info, err := os.Stat(finalPath); err != nil || !info.Mode().IsRegular() { t.Fatalf("durable rename may leave only an orphan file: info=%v err=%v", info, err) } assertNoPublishTemps(t, filepath.Dir(finalPath)) assertEvidenceCount(t, database, 0) }) } } func TestStageRejectsUnsafeContent(t *testing.T) { _, store := newTestStore(t) largePNG := makePNG(t, core.MaxImageSide+1, 1) pngWithXML := append(makePNG(t, 1, 1), []byte("")...) for name, test := range map[string]struct { reader io.Reader contentType string }{ "wrong content type": {reader: bytes.NewReader(makePNG(t, 1, 1)), contentType: "application/octet-stream"}, "xml": {reader: bytes.NewBufferString(""), contentType: core.PNGContentType}, "png with xml tail": {reader: bytes.NewReader(pngWithXML), contentType: core.PNGContentType}, "truncated png": {reader: bytes.NewReader(pngSignature), contentType: core.PNGContentType}, "too wide": {reader: bytes.NewReader(largePNG), contentType: core.PNGContentType}, "too many bytes": {reader: io.LimitReader(zeroReader{}, core.MaxFileBytes+1), contentType: core.PNGContentType}, } { t.Run(name, func(t *testing.T) { staged, err := store.Stage(test.reader, test.contentType) if !errors.Is(err, core.ErrInvalid) && !errors.Is(err, core.ErrTooLarge) { store.Discard(staged) t.Fatalf("Stage error = %v", err) } }) } } func TestCommitRequiresAttemptOwnedByTaskAndLowercaseHash(t *testing.T) { database, store := newTestStore(t) insertAttemptFixture(t, database) pngBytes := makePNG(t, 2, 2) base := core.UploadMetadata{UploadKey: testUploadKey, TaskID: testTaskID, AttemptID: testAttemptID, Kind: core.KindSKUPanelGate1, PrivacyTier: core.PrivacyInternalRaw, SHA256: sha256Hex(pngBytes), CapturedAt: time.Date(2026, 8, 4, 1, 2, 3, 0, time.UTC)} for name, mutate := range map[string]func(*core.UploadMetadata){ "unknown attempt": func(value *core.UploadMetadata) { value.AttemptID = "53c9f507-7473-4fa6-8d71-8786c34c6301" }, "uppercase hash": func(value *core.UploadMetadata) { value.SHA256 = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "wrong kind": func(value *core.UploadMetadata) { value.Kind = "ORDER_CONFIRM" }, } { t.Run(name, func(t *testing.T) { staged, err := store.Stage(bytes.NewReader(pngBytes), core.PNGContentType) if err != nil { t.Fatalf("Stage: %v", err) } metadata := base mutate(&metadata) if _, _, err := store.Commit(context.Background(), deviceauth.Principal{ID: testDeviceID}, metadata, staged); !errors.Is(err, core.ErrInvalid) { t.Fatalf("Commit error = %v", err) } }) } var count int if err := database.QueryRow("SELECT COUNT(*) FROM evidence_assets").Scan(&count); err != nil || count != 0 { t.Fatalf("invalid commits created %d assets, err %v", count, err) } } func TestNewStoreRejectsRelativeAndFilesystemRootPaths(t *testing.T) { database, _ := newTestStore(t) if _, err := NewStore(database, "relative-evidence"); err == nil { t.Fatal("relative evidence root succeeded") } volumeRoot := filepath.VolumeName(t.TempDir()) + string(filepath.Separator) if _, err := NewStore(database, volumeRoot); err == nil { t.Fatal("filesystem root succeeded") } } type zeroReader struct{} func (zeroReader) Read(buffer []byte) (int, error) { for index := range buffer { buffer[index] = 0 } return len(buffer), nil } func newTestStore(t *testing.T) (*sql.DB, *Store) { t.Helper() database, err := sqlite.Open(filepath.Join(t.TempDir(), "evidence.db")) if err != nil { t.Fatalf("open database: %v", err) } t.Cleanup(func() { _ = database.Close() }) _, file, _, ok := runtime.Caller(0) if !ok { t.Fatal("locate test") } directory := filepath.Join(filepath.Dir(file), "..", "..", "..", "migrations") if err := migrations.Up(context.Background(), database, directory); err != nil { t.Fatalf("migrate database: %v", err) } store, err := NewStore(database, filepath.Join(t.TempDir(), "assets")) if err != nil { t.Fatalf("NewStore: %v", err) } return database, store } func insertAttemptFixture(t *testing.T, database *sql.DB) { t.Helper() timestamp := "2026-08-04T00:00:00Z" tokenHash := sha256.Sum256([]byte("evidence-device-token")) if _, err := database.Exec(`INSERT INTO device_credentials (device_id,display_name,token_sha256,status,created_at,revoked_at) VALUES (?, 'evidence device', ?, 'ACTIVE', ?, NULL)`, testDeviceID, tokenHash[:], timestamp); err != nil { t.Fatalf("insert device: %v", err) } if _, err := database.Exec(`INSERT INTO tasks (id, source, title, goods_id, sku_color, sku_size, quantity, max_total_price, status, version, created_at, updated_at) VALUES (?, 'MANUAL', 'task', '123', 'black', 'M', 1, '1.00', 'DRAFT', 1, ?, ?)`, testTaskID, timestamp, timestamp); err != nil { t.Fatalf("insert task: %v", err) } if _, err := database.Exec(`INSERT INTO order_authorizations (id, task_id, task_version, start_key, goods_id, sku_color, sku_size, quantity, total_price_cap, status, created_by, created_at, expires_at) VALUES (?, ?, 1, 'start', '123', 'black', 'M', 1, '1.00', 'ACTIVE', 'admin', ?, ?)`, testAuthID, testTaskID, timestamp, timestamp); err != nil { t.Fatalf("insert authorization: %v", err) } if _, err := database.Exec(`INSERT INTO purchase_attempts (id, task_id, authorization_id, claim_generation, status, started_at) VALUES (?, ?, ?, 1, 'CLAIMED', ?)`, testAttemptID, testTaskID, testAuthID, timestamp); err != nil { t.Fatalf("insert attempt: %v", err) } if _, err := database.Exec(`INSERT INTO purchase_attempt_claims (attempt_id,task_id,authorization_id,claimed_by_device_id,session_id,claim_generation, task_version,task_title,authorization_task_version,goods_id,sku_color,sku_size,quantity, total_price_cap,authorization_expires_at,claim_nonce,claim_token_sha256,lease_expires_at,claimed_at,closed_at) VALUES (?, ?, ?, ?, '63c9f507-7473-4fa6-8d71-8786c34c6301', 1, 1, 'task', 1, '123', 'black', 'M', 1, '1.00', ?, ?, ?, '2026-08-04T02:00:00Z', ?, NULL)`, testAttemptID, testTaskID, testAuthID, testDeviceID, timestamp, bytes.Repeat([]byte{1}, 32), bytes.Repeat([]byte{2}, 32), timestamp); err != nil { t.Fatalf("insert claim: %v", err) } } func testMetadata(hash string) core.UploadMetadata { return core.UploadMetadata{ UploadKey: testUploadKey, TaskID: testTaskID, AttemptID: testAttemptID, Kind: core.KindSKUPanelGate1, PrivacyTier: core.PrivacyInternalRaw, SHA256: hash, CapturedAt: time.Date(2026, 8, 4, 1, 2, 3, 0, time.UTC), } } func assertEvidenceCount(t *testing.T, database *sql.DB, want int) { t.Helper() var count int if err := database.QueryRow("SELECT COUNT(*) FROM evidence_assets").Scan(&count); err != nil { t.Fatalf("count evidence assets: %v", err) } if count != want { t.Fatalf("evidence asset count = %d, want %d", count, want) } } func assertNoPublishTemps(t *testing.T, directory string) { t.Helper() entries, err := os.ReadDir(directory) if err != nil { t.Fatalf("read shard directory: %v", err) } for _, entry := range entries { if strings.HasPrefix(entry.Name(), ".publish-") { t.Fatalf("publication temp leaked: %q", entry.Name()) } } } func makePNG(t *testing.T, width, height int) []byte { t.Helper() imageData := image.NewNRGBA(image.Rect(0, 0, width, height)) imageData.Set(0, 0, color.NRGBA{R: 12, G: 34, B: 56, A: 255}) var buffer bytes.Buffer if err := png.Encode(&buffer, imageData); err != nil { t.Fatalf("encode PNG: %v", err) } return buffer.Bytes() } func sha256Hex(value []byte) string { hash := sha256.Sum256(value) return hex.EncodeToString(hash[:]) }