Restore private prompt history with idempotent user-scoped imports

Hop-State: A_06FNBBSMHQ9HVEQCC70EPA8
Hop-Proposal: R_06FNBBRHSSJTCFN79G3KWJ0
Hop-Task: T_06FNB93F0NJANQV08BA6VAG
Hop-Attempt: AT_06FNB93F0PSZWAGR81JCWBG
This commit is contained in:
2026-07-12 02:36:13 -07:00
committed by Hop
parent ff25f26b6c
commit 65da71bb80
4 changed files with 76 additions and 17 deletions
@@ -169,7 +169,7 @@ func TestListPromptsRejectsUnauthenticatedViewer(t *testing.T) {
func TestCreatePrompt(t *testing.T) {
data := &fakeStore{}
handler := testServer(data, fakeGitea{user: gitea.User{ID: 42, Login: "alice"}})
req := httptest.NewRequest(http.MethodPost, "/api/v1/repositories/hop/demo/prompts", bytes.NewBufferString(`{"user_login":"alice","prompt":"Ship the prompt view","agent_name":"codex","status":"completed","metadata":{"duration_ms":1200}}`))
req := httptest.NewRequest(http.MethodPost, "/api/v1/repositories/hop/demo/prompts", bytes.NewBufferString(`{"user_login":"alice","state_id":"P_TEST","prompt":"Ship the prompt view","agent_name":"codex","status":"completed","metadata":{"duration_ms":1200}}`))
req.Header.Set("Authorization", "Bearer admin-secret")
response := httptest.NewRecorder()
handler.ServeHTTP(response, req)
@@ -184,9 +184,46 @@ func TestCreatePrompt(t *testing.T) {
}
}
func TestCreatePromptRequiresOwner(t *testing.T) {
func TestCreatePromptWithImmutableGiteaUserID(t *testing.T) {
data := &fakeStore{}
handler := testServer(data, fakeGitea{err: errors.New("user lookup must not run")})
req := httptest.NewRequest(http.MethodPost, "/api/v1/repositories/hop/demo/prompts", bytes.NewBufferString(`{"gitea_user_id":42,"state_id":"P_TEST","prompt":"Restore my prompt history","status":"completed","created_at":"2026-07-11T19:26:01Z"}`))
req.Header.Set("Authorization", "Bearer admin-secret")
response := httptest.NewRecorder()
handler.ServeHTTP(response, req)
if response.Code != http.StatusCreated {
t.Fatalf("status = %d, want %d; body=%s", response.Code, http.StatusCreated, response.Body.String())
}
if len(data.prompts) != 1 || data.prompts[0].GiteaUserID != 42 {
t.Fatalf("prompt not attributed to immutable user ID: %#v", data.prompts)
}
}
func TestCreatePromptRejectsAmbiguousOwner(t *testing.T) {
handler := testServer(&fakeStore{}, fakeGitea{})
req := httptest.NewRequest(http.MethodPost, "/api/v1/repositories/hop/demo/prompts", bytes.NewBufferString(`{"prompt":"private work","status":"completed"}`))
req := httptest.NewRequest(http.MethodPost, "/api/v1/repositories/hop/demo/prompts", bytes.NewBufferString(`{"gitea_user_id":42,"user_login":"alice","prompt":"private work","status":"completed"}`))
req.Header.Set("Authorization", "Bearer admin-secret")
response := httptest.NewRecorder()
handler.ServeHTTP(response, req)
if response.Code != http.StatusBadRequest {
t.Fatalf("status = %d, want %d; body=%s", response.Code, http.StatusBadRequest, response.Body.String())
}
}
func TestCreatePromptRequiresOwner(t *testing.T) {
handler := testServer(&fakeStore{}, fakeGitea{})
req := httptest.NewRequest(http.MethodPost, "/api/v1/repositories/hop/demo/prompts", bytes.NewBufferString(`{"state_id":"P_TEST","prompt":"private work","status":"completed"}`))
req.Header.Set("Authorization", "Bearer admin-secret")
response := httptest.NewRecorder()
handler.ServeHTTP(response, req)
if response.Code != http.StatusBadRequest {
t.Fatalf("status = %d, want %d; body=%s", response.Code, http.StatusBadRequest, response.Body.String())
}
}
func TestCreatePromptRequiresStateID(t *testing.T) {
handler := testServer(&fakeStore{}, fakeGitea{})
req := httptest.NewRequest(http.MethodPost, "/api/v1/repositories/hop/demo/prompts", bytes.NewBufferString(`{"gitea_user_id":42,"prompt":"private work","status":"completed"}`))
req.Header.Set("Authorization", "Bearer admin-secret")
response := httptest.NewRecorder()
handler.ServeHTTP(response, req)