Files
wehub-resource-sync f99010fae1
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Windows) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:36 +08:00

209 lines
5.4 KiB
Go

package server
import (
"net/http"
"net/http/httptest"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// idleTrackerFixture bundles an IdleTracker with a buffered channel
// that receives a value whenever the tracker reports going idle.
type idleTrackerFixture struct {
tracker *IdleTracker
fired chan time.Time
}
// newIdleTrackerFixture builds a tracker whose onIdle callback signals
// the fixture's fired channel.
func newIdleTrackerFixture(
t *testing.T, timeout time.Duration,
) *idleTrackerFixture {
t.Helper()
fired := make(chan time.Time, 1)
return &idleTrackerFixture{
tracker: NewIdleTracker(timeout, func() { fired <- time.Now() }),
fired: fired,
}
}
// run starts the tracker's loop in the background using the test
// context, which is cancelled during test cleanup.
func (f *idleTrackerFixture) run(t *testing.T) {
t.Helper()
go f.tracker.Run(t.Context())
}
// requireNotFiredWithin fails if the tracker reports idle within d.
func (f *idleTrackerFixture) requireNotFiredWithin(
t *testing.T, d time.Duration, msg string,
) {
t.Helper()
select {
case <-f.fired:
require.FailNow(t, msg)
case <-time.After(d):
}
}
// requireFiredWithin fails if the tracker does not report idle within d.
func (f *idleTrackerFixture) requireFiredWithin(
t *testing.T, d time.Duration, msg string,
) time.Time {
t.Helper()
select {
case firedAt := <-f.fired:
return firedAt
case <-time.After(d):
require.FailNow(t, msg)
return time.Time{}
}
}
// serveWrappedNoContent drives a no-content handler through the
// tracker's Wrap middleware and returns the recorder.
func serveWrappedNoContent(
t *testing.T, tracker *IdleTracker,
) *httptest.ResponseRecorder {
t.Helper()
rec := httptest.NewRecorder()
tracker.Wrap(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusNoContent)
})).ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/", nil))
return rec
}
// requireBeginWork asserts that BeginWork is accepted and returns its
// done callback.
func requireBeginWork(t *testing.T, tracker *IdleTracker) func() {
t.Helper()
done, ok := tracker.BeginWork()
require.True(t, ok)
require.NotNil(t, done)
return done
}
// assertBeginWorkRejected asserts that BeginWork is refused and runs
// the returned done callback.
func assertBeginWorkRejected(t *testing.T, tracker *IdleTracker) {
t.Helper()
done, ok := tracker.BeginWork()
assert.False(t, ok)
done()
}
func TestIdleTrackerExternalRequestResetsIdle(t *testing.T) {
timeout := 40 * time.Millisecond
f := newIdleTrackerFixture(t, timeout)
entered := make(chan struct{})
release := make(chan struct{})
requestDone := make(chan *httptest.ResponseRecorder, 1)
var releaseOnce sync.Once
releaseRequest := func() { releaseOnce.Do(func() { close(release) }) }
t.Cleanup(releaseRequest)
go func() {
rec := httptest.NewRecorder()
f.tracker.Wrap(http.HandlerFunc(func(
w http.ResponseWriter, _ *http.Request,
) {
close(entered)
<-release
w.WriteHeader(http.StatusNoContent)
})).ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/", nil))
requestDone <- rec
}()
select {
case <-entered:
case <-time.After(time.Second):
require.FailNow(t, "wrapped request did not enter handler")
}
f.run(t)
f.tracker.mu.Lock()
lastExternalBeforeRelease := f.tracker.lastExternal
f.tracker.mu.Unlock()
f.requireNotFiredWithin(t, 2*timeout,
"idle fired while external request was active")
releasedAt := time.Now()
releaseRequest()
var rec *httptest.ResponseRecorder
select {
case rec = <-requestDone:
case <-time.After(time.Second):
require.FailNow(t, "wrapped request did not complete after release")
}
assert.Equal(t, http.StatusNoContent, rec.Code)
f.tracker.mu.Lock()
lastExternalAfterRelease := f.tracker.lastExternal
f.tracker.mu.Unlock()
assert.True(t, lastExternalAfterRelease.After(lastExternalBeforeRelease),
"request completion did not advance external activity timestamp")
firedAt := f.requireFiredWithin(t, time.Second,
"idle did not fire after external activity became idle")
assert.GreaterOrEqual(t, firedAt.Sub(releasedAt), timeout)
}
func TestIdleTrackerInternalWorkBlocksWithoutResettingIdle(t *testing.T) {
f := newIdleTrackerFixture(t, 20*time.Millisecond)
done := requireBeginWork(t, f.tracker)
f.run(t)
f.requireNotFiredWithin(t, 35*time.Millisecond,
"idle fired while internal work was active")
done()
f.requireFiredWithin(t, 80*time.Millisecond,
"idle did not fire after internal work ended")
}
func TestIdleTrackerTouchResetsIdleBeforeRun(t *testing.T) {
f := newIdleTrackerFixture(t, 30*time.Millisecond)
time.Sleep(45 * time.Millisecond)
f.tracker.Touch()
f.run(t)
f.requireNotFiredWithin(t, 15*time.Millisecond,
"idle fired immediately after touch")
f.requireFiredWithin(t, 80*time.Millisecond,
"idle did not fire after touched timeout elapsed")
}
func TestIdleTrackerRejectsRequestsAfterDrainStarts(t *testing.T) {
f := newIdleTrackerFixture(t, 1*time.Millisecond)
f.run(t)
f.requireFiredWithin(t, time.Second, "idle did not fire after timeout")
rec := serveWrappedNoContent(t, f.tracker)
assert.Equal(t, http.StatusServiceUnavailable, rec.Code)
assertBeginWorkRejected(t, f.tracker)
}
func TestIdleTrackerNilReceiverIsNoop(t *testing.T) {
var tracker *IdleTracker
done := requireBeginWork(t, tracker)
done()
tracker.Touch()
tracker.Run(t.Context())
rec := serveWrappedNoContent(t, tracker)
assert.Equal(t, http.StatusNoContent, rec.Code)
}