ความคืบหน้างาน ไฟล์ต้นฉบับ

tracker/usecase_approval_test.go

package main

import (
	"bytes"
	"encoding/json"
	"net/http/httptest"
	"strings"
	"sync"
	"testing"
)

func TestUseCaseApprovalTracksExactContent(t *testing.T) {
	c, _ := loadUseCases("..")
	u := c.UC["W-49"]
	ds := &diagramStore{dir: t.TempDir()}
	if _, err := ds.approve(u, 0, sourceDigest(u), "", "", true); err == nil {
		t.Fatal("reviewer required")
	}
	status, err := ds.approve(u, 0, sourceDigest(u), "Reviewer", "Verified source and diagram", true)
	if err != nil || !status.Approved {
		t.Fatal("approval failed", err)
	}
	if !(&diagramStore{dir: ds.dir}).approvalStatus(u).Approved {
		t.Fatal("approval not persisted")
	}
	changed := u
	changed.Outcome += " changed"
	if ds.approvalStatus(changed).Approved {
		t.Fatal("source change must invalidate approval")
	}
	if _, err = ds.approve(changed, 0, sourceDigest(u), "Reviewer", "", true); err != errDiagramConflict {
		t.Fatal("stale source approved")
	}
	d, _ := ds.load(u.ID, u)
	d.Nodes[0].Title = "changed"
	d, err = ds.save(u.ID, d)
	if err != nil {
		t.Fatal(err)
	}
	if ds.approvalStatus(u).Approved {
		t.Fatal("diagram change must invalidate approval")
	}
	if _, err = ds.approve(u, 0, sourceDigest(u), "Reviewer", "", true); err != errDiagramConflict {
		t.Fatal("stale diagram approved")
	}
	status, err = ds.approve(u, d.Revision, sourceDigest(u), "Reviewer 2", "", true)
	if err != nil || !status.Approved || len(status.History) != 2 {
		t.Fatal("approval history lost", err)
	}
	d, err = ds.restore(u.ID, 1, 1)
	if err != nil || ds.approvalStatus(u).Approved {
		t.Fatal("restore requires approval again", err)
	}
}
func TestConcurrentDiagramSavesKeepHistory(t *testing.T) {
	c, _ := loadUseCases("..")
	ds := &diagramStore{dir: t.TempDir()}
	d := draftDiagram(c.UC["W-49"])
	var wg sync.WaitGroup
	results := make(chan error, 2)
	for i := 0; i < 2; i++ {
		wg.Add(1)
		go func() { defer wg.Done(); _, err := ds.save("W-49", d); results <- err }()
	}
	wg.Wait()
	close(results)
	success, conflict := 0, 0
	for err := range results {
		if err == nil {
			success++
		} else if err == errDiagramConflict {
			conflict++
		} else {
			t.Fatal(err)
		}
	}
	h, _ := ds.revisions("W-49")
	if success != 1 || conflict != 1 || len(h) != 1 {
		t.Fatal("concurrent saves lost history")
	}
}

func TestUseCaseApprovalHTTPGuards(t *testing.T) {
	old := repoRoot
	repoRoot = ".."
	defer func() { repoRoot = old }()
	mux := newMux(NewStore(t.TempDir()), NewHub())
	c, _ := loadUseCases("..")
	hash := sourceDigest(c.UC["W-49"])
	for _, tc := range []struct {
		origin, reviewer, hash string
		confirmed              bool
		code                   int
	}{{"http://evil.test", "Reviewer", hash, true, 403}, {"http://example.com", "", hash, true, 400}, {"http://example.com", "Reviewer", hash, false, 400}, {"http://example.com", "Reviewer", "stale", true, 409}, {"http://example.com", "Reviewer", hash, true, 200}} {
		body, _ := json.Marshal(map[string]any{"revision": 0, "source_hash": tc.hash, "reviewer": tc.reviewer, "note": "Checked", "confirmed": tc.confirmed})
		r := httptest.NewRequest("POST", "/api/use-cases/W-49/approval", bytes.NewReader(body))
		r.Header.Set("Origin", tc.origin)
		w := httptest.NewRecorder()
		mux.ServeHTTP(w, r)
		if w.Code != tc.code {
			t.Fatalf("got %d expected %d: %s", w.Code, tc.code, w.Body.String())
		}
	}
	w := httptest.NewRecorder()
	mux.ServeHTTP(w, httptest.NewRequest("GET", "/api/use-cases/W-49/approval", nil))
	if w.Code != 200 || !strings.Contains(w.Body.String(), `"approved":true`) {
		t.Fatal("approval status not readable", w.Body.String())
	}
}