| 1 | package uihub |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "testing" |
| 6 | |
| 7 | "reasonix/internal/extension/protocol" |
| 8 | ) |
| 9 | |
| 10 | func publishTestForm(t *testing.T, h *Hub, handler UIHandler, surfaceID string) string { |
| 11 | t.Helper() |
| 12 | result, err := handler.Publish(context.Background(), protocol.UIPublishParams{ |
| 13 | SurfaceID: surfaceID, SessionID: "sess-1", Generation: 7, Kind: protocol.UISurfaceForm, |
| 14 | Payload: mustRaw(t, protocol.UIFormPayload{Title: "Form", Fields: []protocol.UIFormField{}}), |
| 15 | }) |
| 16 | if err != nil || !result.Accepted { |
| 17 | t.Fatalf("publish form = %+v, %v", result, err) |
| 18 | } |
| 19 | h.mu.Lock() |
| 20 | defer h.mu.Unlock() |
| 21 | return h.activeForms[formKey("alpha", surfaceID)].instanceID |
| 22 | } |
| 23 | |
| 24 | func TestSubmitExactPinsInstanceAndRejectsDuplicate(t *testing.T) { |
| 25 | started := make(chan struct{}, 1) |
| 26 | release := make(chan struct{}) |
| 27 | fake := &fakeActionClient{submitResult: protocol.UISubmitResult{Accepted: true}, submitStarted: started, submitRelease: release} |
| 28 | h := New(Options{SessionID: "sess-1", Generation: 7, Resolve: func(string) ActionClient { return fake }}) |
| 29 | handler := h.HandlerFor("alpha") |
| 30 | first := publishTestForm(t, h, handler, "f1") |
| 31 | second := publishTestForm(t, h, handler, "f1") |
| 32 | if second == first { |
| 33 | t.Fatal("republishing the same surface reused its form instance") |
| 34 | } |
| 35 | if _, err := h.SubmitExact(context.Background(), "alpha", "f1", "sess-1", 7, first, nil); err == nil { |
| 36 | t.Fatal("stale form instance reached the sidecar") |
| 37 | } |
| 38 | result := make(chan error, 1) |
| 39 | go func() { |
| 40 | _, err := h.SubmitExact(context.Background(), "alpha", "f1", "sess-1", 7, second, map[string]any{"v": 1}) |
| 41 | result <- err |
| 42 | }() |
| 43 | <-started |
| 44 | if _, err := h.SubmitExact(context.Background(), "alpha", "f1", "sess-1", 7, second, nil); err == nil { |
| 45 | t.Fatal("duplicate exact submission was accepted") |
| 46 | } |
| 47 | fake.mu.Lock() |
| 48 | calls := len(fake.submitParams) |
| 49 | fake.mu.Unlock() |
| 50 | if calls != 1 { |
| 51 | t.Fatalf("sidecar submit calls = %d, want 1", calls) |
| 52 | } |
| 53 | close(release) |
| 54 | if err := <-result; err != nil { |
| 55 | t.Fatalf("exact submit: %v", err) |
| 56 | } |
| 57 | } |
| 58 | |
| 59 | func TestOldSubmitCompletionDoesNotDeleteReplacement(t *testing.T) { |
| 60 | started := make(chan struct{}, 1) |
| 61 | release := make(chan struct{}) |
| 62 | fake := &fakeActionClient{submitResult: protocol.UISubmitResult{Accepted: true}, submitStarted: started, submitRelease: release} |
| 63 | h := New(Options{SessionID: "sess-1", Generation: 7, Resolve: func(string) ActionClient { return fake }}) |
| 64 | handler := h.HandlerFor("alpha") |
| 65 | oldInstance := publishTestForm(t, h, handler, "f1") |
| 66 | done := make(chan error, 1) |
| 67 | go func() { |
| 68 | _, err := h.SubmitExact(context.Background(), "alpha", "f1", "sess-1", 7, oldInstance, nil) |
| 69 | done <- err |
| 70 | }() |
| 71 | <-started |
| 72 | newInstance := publishTestForm(t, h, handler, "f1") |
| 73 | close(release) |
| 74 | if err := <-done; err != nil { |
| 75 | t.Fatalf("old submit: %v", err) |
| 76 | } |
| 77 | h.mu.Lock() |
| 78 | current, ok := h.activeForms[formKey("alpha", "f1")] |
| 79 | h.mu.Unlock() |
| 80 | if !ok || current.instanceID != newInstance { |
| 81 | t.Fatalf("replacement form was removed by old completion: %+v, present=%v", current, ok) |
| 82 | } |
| 83 | if _, err := h.SubmitExact(context.Background(), "alpha", "f1", "sess-1", 7, newInstance, nil); err != nil { |
| 84 | t.Fatalf("replacement form is not actionable: %v", err) |
| 85 | } |
| 86 | } |
| 87 |