11
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"superwork-tui/internal/config"
|
||||
)
|
||||
|
||||
// ManagedSession is a Claude session created via the session manager tab.
|
||||
type ManagedSession struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
ProfilePath string `json:"profilePath,omitempty"`
|
||||
CreatedAt int64 `json:"createdAt"`
|
||||
}
|
||||
|
||||
// ManagedSessionsData is the workspace-level managed sessions list.
|
||||
type ManagedSessionsData struct {
|
||||
Sessions []ManagedSession `json:"sessions"`
|
||||
}
|
||||
|
||||
func sessionsFile(workspaceRoot string) string {
|
||||
return filepath.Join(config.SpxDir(workspaceRoot), "session-names.json")
|
||||
}
|
||||
|
||||
// ReadManagedSessions reads the managed sessions list.
|
||||
// A missing or malformed file returns an empty list without error.
|
||||
func ReadManagedSessions(workspaceRoot string) (ManagedSessionsData, error) {
|
||||
raw, err := os.ReadFile(sessionsFile(workspaceRoot))
|
||||
if err != nil {
|
||||
return ManagedSessionsData{Sessions: []ManagedSession{}}, nil
|
||||
}
|
||||
var data ManagedSessionsData
|
||||
if err := json.Unmarshal(raw, &data); err != nil {
|
||||
return ManagedSessionsData{Sessions: []ManagedSession{}}, nil
|
||||
}
|
||||
if data.Sessions == nil {
|
||||
data.Sessions = []ManagedSession{}
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// WriteManagedSessions persists the sessions list. Creates .spx if needed.
|
||||
func WriteManagedSessions(workspaceRoot string, data ManagedSessionsData) error {
|
||||
if err := os.MkdirAll(config.SpxDir(workspaceRoot), 0o755); err != nil {
|
||||
return fmt.Errorf("create .spx dir: %w", err)
|
||||
}
|
||||
b, err := json.MarshalIndent(data, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal sessions: %w", err)
|
||||
}
|
||||
if err := os.WriteFile(sessionsFile(workspaceRoot), append(b, '\n'), 0o644); err != nil {
|
||||
return fmt.Errorf("write sessions: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddSession appends s to data and returns the new data.
|
||||
func AddSession(data ManagedSessionsData, s ManagedSession) ManagedSessionsData {
|
||||
out := make([]ManagedSession, len(data.Sessions), len(data.Sessions)+1)
|
||||
copy(out, data.Sessions)
|
||||
return ManagedSessionsData{Sessions: append(out, s)}
|
||||
}
|
||||
|
||||
// RenameSession sets the Name of the session with the given id.
|
||||
func RenameSession(data ManagedSessionsData, id, name string) ManagedSessionsData {
|
||||
out := make([]ManagedSession, len(data.Sessions))
|
||||
copy(out, data.Sessions)
|
||||
for i := range out {
|
||||
if out[i].ID == id {
|
||||
out[i].Name = name
|
||||
break
|
||||
}
|
||||
}
|
||||
return ManagedSessionsData{Sessions: out}
|
||||
}
|
||||
|
||||
// DeleteSession removes the session with the given id.
|
||||
func DeleteSession(data ManagedSessionsData, id string) ManagedSessionsData {
|
||||
out := make([]ManagedSession, 0, len(data.Sessions))
|
||||
for _, s := range data.Sessions {
|
||||
if s.ID != id {
|
||||
out = append(out, s)
|
||||
}
|
||||
}
|
||||
data.Sessions = out
|
||||
return data
|
||||
}
|
||||
|
||||
// FindSession returns the session with the given id, if present.
|
||||
func FindSession(data ManagedSessionsData, id string) (ManagedSession, bool) {
|
||||
for _, s := range data.Sessions {
|
||||
if s.ID == id {
|
||||
return s, true
|
||||
}
|
||||
}
|
||||
return ManagedSession{}, false
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReadManagedSessions_missingFile(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
data, err := ReadManagedSessions(dir)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if data.Sessions == nil {
|
||||
t.Error("Sessions should be non-nil empty slice, got nil")
|
||||
}
|
||||
if len(data.Sessions) != 0 {
|
||||
t.Errorf("want 0 sessions, got %d", len(data.Sessions))
|
||||
}
|
||||
}
|
||||
|
||||
func TestManagedSessions_roundtrip(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
original := ManagedSessionsData{
|
||||
Sessions: []ManagedSession{
|
||||
{ID: "s1", Name: "Session One", ProfilePath: "/path/to/profile.json", CreatedAt: 1000},
|
||||
{ID: "s2", Name: "Session Two", ProfilePath: "", CreatedAt: 2000},
|
||||
},
|
||||
}
|
||||
if err := WriteManagedSessions(dir, original); err != nil {
|
||||
t.Fatalf("WriteManagedSessions: %v", err)
|
||||
}
|
||||
got, err := ReadManagedSessions(dir)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadManagedSessions: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(got, original) {
|
||||
t.Errorf("roundtrip mismatch:\n got %+v\n want %+v", got, original)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteManagedSessions_createsSpxDir(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
data := ManagedSessionsData{Sessions: []ManagedSession{}}
|
||||
if err := WriteManagedSessions(dir, data); err != nil {
|
||||
t.Fatalf("WriteManagedSessions: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(dir, ".spx", "session-names.json")); err != nil {
|
||||
t.Errorf("session-names.json not created: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddSession(t *testing.T) {
|
||||
data := ManagedSessionsData{Sessions: []ManagedSession{}}
|
||||
s := ManagedSession{ID: "a1", Name: "Alpha", CreatedAt: 100}
|
||||
data = AddSession(data, s)
|
||||
if len(data.Sessions) != 1 {
|
||||
t.Fatalf("want 1 session, got %d", len(data.Sessions))
|
||||
}
|
||||
if data.Sessions[0] != s {
|
||||
t.Errorf("session mismatch: got %+v, want %+v", data.Sessions[0], s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenameSession(t *testing.T) {
|
||||
data := ManagedSessionsData{Sessions: []ManagedSession{
|
||||
{ID: "a1", Name: "Old Name", CreatedAt: 100},
|
||||
{ID: "a2", Name: "Other", CreatedAt: 200},
|
||||
}}
|
||||
data = RenameSession(data, "a1", "New Name")
|
||||
if data.Sessions[0].Name != "New Name" {
|
||||
t.Errorf("want 'New Name', got %q", data.Sessions[0].Name)
|
||||
}
|
||||
// other session unchanged
|
||||
if data.Sessions[1].Name != "Other" {
|
||||
t.Errorf("other session name changed: got %q", data.Sessions[1].Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenameSession_notFound(t *testing.T) {
|
||||
data := ManagedSessionsData{Sessions: []ManagedSession{
|
||||
{ID: "a1", Name: "Alpha", CreatedAt: 100},
|
||||
}}
|
||||
result := RenameSession(data, "nonexistent", "X")
|
||||
if result.Sessions[0].Name != "Alpha" {
|
||||
t.Error("renaming nonexistent id should not change data")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteSession(t *testing.T) {
|
||||
data := ManagedSessionsData{Sessions: []ManagedSession{
|
||||
{ID: "a1", Name: "Alpha", CreatedAt: 100},
|
||||
{ID: "a2", Name: "Beta", CreatedAt: 200},
|
||||
{ID: "a3", Name: "Gamma", CreatedAt: 300},
|
||||
}}
|
||||
data = DeleteSession(data, "a2")
|
||||
if len(data.Sessions) != 2 {
|
||||
t.Fatalf("want 2 sessions after delete, got %d", len(data.Sessions))
|
||||
}
|
||||
for _, s := range data.Sessions {
|
||||
if s.ID == "a2" {
|
||||
t.Error("deleted session still present")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteSession_notFound(t *testing.T) {
|
||||
data := ManagedSessionsData{Sessions: []ManagedSession{
|
||||
{ID: "a1", Name: "Alpha", CreatedAt: 100},
|
||||
}}
|
||||
result := DeleteSession(data, "nonexistent")
|
||||
if len(result.Sessions) != 1 {
|
||||
t.Error("deleting nonexistent id should not change session count")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindSession(t *testing.T) {
|
||||
s := ManagedSession{ID: "a1", Name: "Alpha", CreatedAt: 100}
|
||||
data := ManagedSessionsData{Sessions: []ManagedSession{s}}
|
||||
|
||||
found, ok := FindSession(data, "a1")
|
||||
if !ok {
|
||||
t.Fatal("expected to find session a1")
|
||||
}
|
||||
if found != s {
|
||||
t.Errorf("found session mismatch: got %+v, want %+v", found, s)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindSession_notFound(t *testing.T) {
|
||||
data := ManagedSessionsData{Sessions: []ManagedSession{
|
||||
{ID: "a1", Name: "Alpha", CreatedAt: 100},
|
||||
}}
|
||||
_, ok := FindSession(data, "nope")
|
||||
if ok {
|
||||
t.Error("expected not found")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"superwork-tui/internal/config"
|
||||
)
|
||||
|
||||
// ProfileRow is one row in the profiles grid: a key and its per-profile values.
|
||||
type ProfileRow struct {
|
||||
Key string `json:"key"`
|
||||
Values map[string]string `json:"values"`
|
||||
}
|
||||
|
||||
// ProfilesData is the workspace-level profiles grid persisted to profiles.json.
|
||||
type ProfilesData struct {
|
||||
Profiles []string `json:"profiles"`
|
||||
Rows []ProfileRow `json:"rows"`
|
||||
}
|
||||
|
||||
func profilesFile(workspaceRoot string) string {
|
||||
return filepath.Join(config.SpxDir(workspaceRoot), "profiles.json")
|
||||
}
|
||||
|
||||
// ReadProfiles reads the workspace profiles grid.
|
||||
// A missing or malformed file returns default data without error.
|
||||
func ReadProfiles(workspaceRoot string) (ProfilesData, error) {
|
||||
raw, err := os.ReadFile(profilesFile(workspaceRoot))
|
||||
if err != nil {
|
||||
return ProfilesData{
|
||||
Profiles: []string{"dev", "prod"},
|
||||
Rows: []ProfileRow{},
|
||||
}, nil
|
||||
}
|
||||
var data ProfilesData
|
||||
if err := json.Unmarshal(raw, &data); err != nil {
|
||||
return ProfilesData{
|
||||
Profiles: []string{"dev", "prod"},
|
||||
Rows: []ProfileRow{},
|
||||
}, nil
|
||||
}
|
||||
if len(data.Profiles) == 0 {
|
||||
data.Profiles = []string{"dev", "prod"}
|
||||
}
|
||||
if data.Rows == nil {
|
||||
data.Rows = []ProfileRow{}
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// WriteProfiles persists the profiles grid. Creates .spx if needed.
|
||||
func WriteProfiles(workspaceRoot string, data ProfilesData) error {
|
||||
if err := os.MkdirAll(config.SpxDir(workspaceRoot), 0o755); err != nil {
|
||||
return fmt.Errorf("create .spx dir: %w", err)
|
||||
}
|
||||
b, err := json.MarshalIndent(data, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal profiles: %w", err)
|
||||
}
|
||||
if err := os.WriteFile(profilesFile(workspaceRoot), append(b, '\n'), 0o644); err != nil {
|
||||
return fmt.Errorf("write profiles: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestReadProfiles_missingFile(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
data, err := ReadProfiles(dir)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
wantProfiles := []string{"dev", "prod"}
|
||||
if !reflect.DeepEqual(data.Profiles, wantProfiles) {
|
||||
t.Errorf("Profiles = %v, want %v", data.Profiles, wantProfiles)
|
||||
}
|
||||
if len(data.Rows) != 0 {
|
||||
t.Errorf("Rows should be empty, got %v", data.Rows)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProfiles_roundtrip(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
original := ProfilesData{
|
||||
Profiles: []string{"staging", "prod", "dev"},
|
||||
Rows: []ProfileRow{
|
||||
{Key: "DB_URL", Values: map[string]string{"dev": "localhost", "prod": "prod.db"}},
|
||||
{Key: "API_KEY", Values: map[string]string{"dev": "dev-key", "staging": "stage-key"}},
|
||||
},
|
||||
}
|
||||
if err := WriteProfiles(dir, original); err != nil {
|
||||
t.Fatalf("WriteProfiles: %v", err)
|
||||
}
|
||||
got, err := ReadProfiles(dir)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadProfiles: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(got, original) {
|
||||
t.Errorf("roundtrip mismatch:\n got %+v\n want %+v", got, original)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProfiles_jsonTags(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
spxDir := filepath.Join(dir, ".spx")
|
||||
if err := os.MkdirAll(spxDir, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
raw := `{"profiles":["x"],"rows":[{"key":"K","values":{"x":"v"}}]}` + "\n"
|
||||
if err := os.WriteFile(filepath.Join(spxDir, "profiles.json"), []byte(raw), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
data, err := ReadProfiles(dir)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadProfiles: %v", err)
|
||||
}
|
||||
if len(data.Profiles) != 1 || data.Profiles[0] != "x" {
|
||||
t.Errorf("Profiles = %v, want [x]", data.Profiles)
|
||||
}
|
||||
if len(data.Rows) != 1 || data.Rows[0].Key != "K" || data.Rows[0].Values["x"] != "v" {
|
||||
t.Errorf("Rows = %v, unexpected", data.Rows)
|
||||
}
|
||||
|
||||
// verify written JSON uses lowercase tags
|
||||
if err := WriteProfiles(dir, data); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
written, _ := os.ReadFile(filepath.Join(spxDir, "profiles.json"))
|
||||
var m map[string]any
|
||||
if err := json.Unmarshal(written, &m); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, ok := m["profiles"]; !ok {
|
||||
t.Error("written JSON missing 'profiles' key")
|
||||
}
|
||||
if _, ok := m["rows"]; !ok {
|
||||
t.Error("written JSON missing 'rows' key")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteProfiles_createsSpxDir(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
// .spx does not exist yet
|
||||
if err := WriteProfiles(dir, ProfilesData{Profiles: []string{"a"}, Rows: nil}); err != nil {
|
||||
t.Fatalf("WriteProfiles: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(filepath.Join(dir, ".spx", "profiles.json")); err != nil {
|
||||
t.Errorf("profiles.json not created: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// Package store persists workspace-level PR review confirmation state.
|
||||
//
|
||||
// Confirmations live in <workspace>/.spx/pr-review-confirmed.json as a
|
||||
// map[string][]string. File-level confirmations use key "<issueNumber>:<sha>"
|
||||
// and hold the confirmed file paths of that commit; commit-level confirmations
|
||||
// use key "<issueNumber>:commits" and hold the confirmed commit SHAs. Directories
|
||||
// are not tracked separately. A missing or malformed file reads as empty.
|
||||
package store
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"superwork-tui/internal/config"
|
||||
)
|
||||
|
||||
type confirmedMap = map[string][]string
|
||||
|
||||
func confirmedFile(workspaceRoot string) string {
|
||||
return filepath.Join(config.SpxDir(workspaceRoot), "pr-review-confirmed.json")
|
||||
}
|
||||
|
||||
func fileKey(issueNumber int, sha string) string {
|
||||
return strconv.Itoa(issueNumber) + ":" + sha
|
||||
}
|
||||
|
||||
func commitsKey(issueNumber int) string {
|
||||
return strconv.Itoa(issueNumber) + ":commits"
|
||||
}
|
||||
|
||||
// readMap returns the whole confirmation map. A missing or unparseable file
|
||||
// reads as an empty map rather than an error.
|
||||
func readMap(workspaceRoot string) confirmedMap {
|
||||
raw, err := os.ReadFile(confirmedFile(workspaceRoot))
|
||||
if err != nil {
|
||||
return confirmedMap{}
|
||||
}
|
||||
var parsed confirmedMap
|
||||
if err := json.Unmarshal(raw, &parsed); err != nil {
|
||||
return confirmedMap{}
|
||||
}
|
||||
return parsed
|
||||
}
|
||||
|
||||
// writeMap persists the confirmation map, creating .spx if needed.
|
||||
func writeMap(workspaceRoot string, m confirmedMap) error {
|
||||
if err := os.MkdirAll(config.SpxDir(workspaceRoot), 0o755); err != nil {
|
||||
return fmt.Errorf("create .spx dir: %w", err)
|
||||
}
|
||||
data, err := json.MarshalIndent(m, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("marshal confirmations: %w", err)
|
||||
}
|
||||
if err := os.WriteFile(confirmedFile(workspaceRoot), append(data, '\n'), 0o644); err != nil {
|
||||
return fmt.Errorf("write confirmations: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadConfirmed returns the confirmed file paths for a (issueNumber, sha) commit.
|
||||
func ReadConfirmed(workspaceRoot string, issueNumber int, sha string) ([]string, error) {
|
||||
return readMap(workspaceRoot)[fileKey(issueNumber, sha)], nil
|
||||
}
|
||||
|
||||
// WriteConfirmed sets the confirmed file paths for a (issueNumber, sha) commit.
|
||||
// An empty slice deletes the key.
|
||||
func WriteConfirmed(workspaceRoot string, issueNumber int, sha string, paths []string) error {
|
||||
m := readMap(workspaceRoot)
|
||||
key := fileKey(issueNumber, sha)
|
||||
if len(paths) > 0 {
|
||||
m[key] = paths
|
||||
} else {
|
||||
delete(m, key)
|
||||
}
|
||||
return writeMap(workspaceRoot, m)
|
||||
}
|
||||
|
||||
// ReadConfirmedCommits returns the confirmed commit SHAs for an issue.
|
||||
func ReadConfirmedCommits(workspaceRoot string, issueNumber int) ([]string, error) {
|
||||
return readMap(workspaceRoot)[commitsKey(issueNumber)], nil
|
||||
}
|
||||
|
||||
// WriteConfirmedCommits sets the confirmed commit SHAs for an issue.
|
||||
// An empty slice deletes the key.
|
||||
func WriteConfirmedCommits(workspaceRoot string, issueNumber int, shas []string) error {
|
||||
m := readMap(workspaceRoot)
|
||||
key := commitsKey(issueNumber)
|
||||
if len(shas) > 0 {
|
||||
m[key] = shas
|
||||
} else {
|
||||
delete(m, key)
|
||||
}
|
||||
return writeMap(workspaceRoot, m)
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
package store_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"superwork-tui/internal/store"
|
||||
)
|
||||
|
||||
func TestReadConfirmed_MissingFile(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
got, err := store.ReadConfirmed(root, 12, "abc")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(got) != 0 {
|
||||
t.Errorf("got %v, want empty", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteConfirmed_Roundtrip(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
want := []string{"src/a.go", "src/b.go"}
|
||||
if err := store.WriteConfirmed(root, 12, "abc", want); err != nil {
|
||||
t.Fatalf("write: %v", err)
|
||||
}
|
||||
got, err := store.ReadConfirmed(root, 12, "abc")
|
||||
if err != nil {
|
||||
t.Fatalf("read: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteConfirmed_EmptyDeletesKey(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
if err := store.WriteConfirmed(root, 12, "abc", []string{"x.go"}); err != nil {
|
||||
t.Fatalf("write: %v", err)
|
||||
}
|
||||
if err := store.WriteConfirmed(root, 12, "abc", nil); err != nil {
|
||||
t.Fatalf("write empty: %v", err)
|
||||
}
|
||||
m := readRawMap(t, root)
|
||||
if _, ok := m["12:abc"]; ok {
|
||||
t.Errorf("key 12:abc still present after empty write: %v", m)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyConvention(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
if err := store.WriteConfirmed(root, 7, "deadbeef", []string{"f.go"}); err != nil {
|
||||
t.Fatalf("write file: %v", err)
|
||||
}
|
||||
if err := store.WriteConfirmedCommits(root, 7, []string{"deadbeef", "cafe"}); err != nil {
|
||||
t.Fatalf("write commits: %v", err)
|
||||
}
|
||||
m := readRawMap(t, root)
|
||||
if _, ok := m["7:deadbeef"]; !ok {
|
||||
t.Errorf("missing file-level key 7:deadbeef: %v", m)
|
||||
}
|
||||
if _, ok := m["7:commits"]; !ok {
|
||||
t.Errorf("missing commit-level key 7:commits: %v", m)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfirmedCommits_Roundtrip(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
want := []string{"sha1", "sha2"}
|
||||
if err := store.WriteConfirmedCommits(root, 9, want); err != nil {
|
||||
t.Fatalf("write: %v", err)
|
||||
}
|
||||
got, err := store.ReadConfirmedCommits(root, 9)
|
||||
if err != nil {
|
||||
t.Fatalf("read: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(got, want) {
|
||||
t.Errorf("got %v, want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfirmedCommits_EmptyDeletesKey(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
if err := store.WriteConfirmedCommits(root, 9, []string{"sha1"}); err != nil {
|
||||
t.Fatalf("write: %v", err)
|
||||
}
|
||||
if err := store.WriteConfirmedCommits(root, 9, nil); err != nil {
|
||||
t.Fatalf("write empty: %v", err)
|
||||
}
|
||||
m := readRawMap(t, root)
|
||||
if _, ok := m["9:commits"]; ok {
|
||||
t.Errorf("key 9:commits still present after empty write: %v", m)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadConfirmed_MalformedFile(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
spx := filepath.Join(root, ".spx")
|
||||
if err := os.MkdirAll(spx, 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(spx, "pr-review-confirmed.json"), []byte("not json"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := store.ReadConfirmed(root, 1, "x")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(got) != 0 {
|
||||
t.Errorf("got %v, want empty for malformed file", got)
|
||||
}
|
||||
}
|
||||
|
||||
func readRawMap(t *testing.T, root string) map[string][]string {
|
||||
t.Helper()
|
||||
raw, err := os.ReadFile(filepath.Join(root, ".spx", "pr-review-confirmed.json"))
|
||||
if err != nil {
|
||||
t.Fatalf("read raw: %v", err)
|
||||
}
|
||||
var m map[string][]string
|
||||
if err := json.Unmarshal(raw, &m); err != nil {
|
||||
t.Fatalf("unmarshal raw: %v", err)
|
||||
}
|
||||
return m
|
||||
}
|
||||
Reference in New Issue
Block a user