100 lines
3.0 KiB
Go
100 lines
3.0 KiB
Go
package github
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// Creating a watch kicks off an initial poll in the background, and that poll
|
|
// stamps LastPolledAt. The watch handed back to the caller is JSON-encoded by
|
|
// the HTTP/WS layer, so the goroutine must work on its own copy — otherwise the
|
|
// encoder reads LastPolledAt while the poll writes it (a real data race), and
|
|
// the caller sees a "last polled" time for a watch it just created.
|
|
//
|
|
// Both tests below wait for the goroutine to persist its timestamp, then assert
|
|
// the returned struct was left alone. That is deterministic: sharing the
|
|
// pointer again makes them fail on every run, not just under -race.
|
|
|
|
func TestCreateIssueWatch_InitialCheckDoesNotMutateTheReturnedWatch(t *testing.T) {
|
|
svc, store := setupWatchServiceTest(t)
|
|
ctx := context.Background()
|
|
|
|
watch, err := svc.CreateIssueWatch(ctx, &CreateIssueWatchRequest{
|
|
WorkspaceID: "ws-1",
|
|
Repos: []RepoFilter{{Owner: "acme", Name: "widget"}},
|
|
Prompt: "fix it",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create issue watch: %v", err)
|
|
}
|
|
|
|
waitForIssueWatchPolled(t, store, watch.ID)
|
|
if watch.LastPolledAt != nil {
|
|
t.Errorf("returned watch has LastPolledAt = %v, want it untouched by the "+
|
|
"background check", watch.LastPolledAt)
|
|
}
|
|
}
|
|
|
|
func TestCreateReviewWatch_InitialCheckDoesNotMutateTheReturnedWatch(t *testing.T) {
|
|
svc, store := setupWatchServiceTest(t)
|
|
ctx := context.Background()
|
|
|
|
watch, err := svc.CreateReviewWatch(ctx, &CreateReviewWatchRequest{
|
|
WorkspaceID: "ws-1",
|
|
Repos: []RepoFilter{{Owner: "acme", Name: "widget"}},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("create review watch: %v", err)
|
|
}
|
|
|
|
waitForReviewWatchPolled(t, store, watch.ID)
|
|
if watch.LastPolledAt != nil {
|
|
t.Errorf("returned watch has LastPolledAt = %v, want it untouched by the "+
|
|
"background check", watch.LastPolledAt)
|
|
}
|
|
}
|
|
|
|
// waitForIssueWatchPolled blocks until the background check has persisted a
|
|
// LastPolledAt for the watch. Real subprocess-free goroutine work, so a short
|
|
// polling wait rather than synctest (which cannot advance a real DB write).
|
|
func waitForIssueWatchPolled(t *testing.T, store *Store, id string) {
|
|
t.Helper()
|
|
waitForWatchPolled(t, func() (bool, error) {
|
|
stored, err := store.GetIssueWatch(context.Background(), id)
|
|
if err != nil || stored == nil {
|
|
return false, err
|
|
}
|
|
return stored.LastPolledAt != nil, nil
|
|
})
|
|
}
|
|
|
|
func waitForReviewWatchPolled(t *testing.T, store *Store, id string) {
|
|
t.Helper()
|
|
waitForWatchPolled(t, func() (bool, error) {
|
|
stored, err := store.GetReviewWatch(context.Background(), id)
|
|
if err != nil || stored == nil {
|
|
return false, err
|
|
}
|
|
return stored.LastPolledAt != nil, nil
|
|
})
|
|
}
|
|
|
|
func waitForWatchPolled(t *testing.T, polled func() (bool, error)) {
|
|
t.Helper()
|
|
deadline := time.Now().Add(5 * time.Second)
|
|
for {
|
|
done, err := polled()
|
|
if err != nil {
|
|
t.Fatalf("read watch: %v", err)
|
|
}
|
|
if done {
|
|
return
|
|
}
|
|
if time.Now().After(deadline) {
|
|
t.Fatal("initial check never recorded LastPolledAt")
|
|
}
|
|
time.Sleep(5 * time.Millisecond)
|
|
}
|
|
}
|