456 lines
19 KiB
Go
456 lines
19 KiB
Go
//go:build integration
|
|
|
|
package redisactivity
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/url"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"proxy-pool/internal/domain/activitypool"
|
|
extractionDomain "proxy-pool/internal/domain/extraction"
|
|
proxyDomain "proxy-pool/internal/domain/proxy"
|
|
"proxy-pool/internal/platform/credentials"
|
|
)
|
|
|
|
func TestRedisExtractAppliesEveryFilterAndBuildsCredentialURL(t *testing.T) {
|
|
fixture := newRedisTestFixture(t)
|
|
now := redisTestNow()
|
|
reference, err := fixture.Credentials.Put(context.Background(), "target", credentials.Value{
|
|
Username: "user@tenant", Password: "p:/@ss",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Credentials.Put(): %v", err)
|
|
}
|
|
target := testProxy("target", "192.0.2.10")
|
|
target.Username = "user@tenant"
|
|
target.SecretRef = reference.SecretRef
|
|
target.CredentialVersion = reference.CredentialVersion
|
|
target.Tags = map[string]string{"region": "cn", "carrier": "ct"}
|
|
seedRedisAvailable(t, fixture.Adapter, "provider-a", now, now.Add(2*time.Second), time.Minute, target)
|
|
|
|
other := testProxy("other", "192.0.2.11")
|
|
other.Scheme = proxyDomain.SchemeSOCKS5
|
|
other.Tags = map[string]string{"region": "us", "carrier": "cu"}
|
|
seedRedisAvailable(t, fixture.Adapter, "provider-b", now, now.Add(2*time.Second), time.Minute, other)
|
|
|
|
result, err := fixture.Adapter.Extract(context.Background(), extractionDomain.Command{
|
|
RequestID: "req-filter", ClientID: "client-a", Requested: 2,
|
|
Fulfillment: extractionDomain.Partial, Now: now.Add(3 * time.Second),
|
|
Protocols: []string{"http"}, Regions: []string{"cn"}, Carriers: []string{"ct"},
|
|
Upstreams: []string{"provider-a"},
|
|
})
|
|
if err != nil || result.Returned != 1 || result.Items[0].ID != "target" {
|
|
t.Fatalf("Extract(filtered) = %+v, %v", result, err)
|
|
}
|
|
parsed, err := url.Parse(result.Items[0].URL)
|
|
if err != nil {
|
|
t.Fatalf("parse extracted URL: %v", err)
|
|
}
|
|
password, hasPassword := parsed.User.Password()
|
|
if parsed.User.Username() != "user@tenant" || !hasPassword || password != "p:/@ss" {
|
|
t.Fatalf("URL credentials = (%q, %q, %t)", parsed.User.Username(), password, hasPassword)
|
|
}
|
|
}
|
|
|
|
func TestRedisExtractSupportsPartialAndAllOrNothing(t *testing.T) {
|
|
fixture := newRedisTestFixture(t)
|
|
now := redisTestNow()
|
|
candidate := testProxy("proxy-a", "192.0.2.10")
|
|
seedRedisAvailable(t, fixture.Adapter, "provider-a", now, now.Add(2*time.Second), time.Minute, candidate)
|
|
|
|
_, err := fixture.Adapter.Extract(context.Background(), extractionDomain.Command{
|
|
RequestID: "req-all", ClientID: "client-a", Requested: 2,
|
|
Fulfillment: extractionDomain.AllOrNothing, Now: now.Add(3 * time.Second),
|
|
})
|
|
if !errors.Is(err, extractionDomain.ErrInsufficientProxies) {
|
|
t.Fatalf("Extract(allOrNothing) error = %v", err)
|
|
}
|
|
result, err := fixture.Adapter.Extract(context.Background(), extractionDomain.Command{
|
|
RequestID: "req-partial", ClientID: "client-a", Requested: 2,
|
|
Fulfillment: extractionDomain.Partial, Now: now.Add(3 * time.Second),
|
|
})
|
|
if err != nil || result.Returned != 1 || result.Items[0].ID != "proxy-a" {
|
|
t.Fatalf("Extract(partial) = %+v, %v", result, err)
|
|
}
|
|
}
|
|
|
|
func TestRedisExtractReturnsEmptyItemsForValidZeroResults(t *testing.T) {
|
|
t.Run("gateway reserve", func(t *testing.T) {
|
|
fixture := newRedisTestFixture(t)
|
|
now := redisTestNow()
|
|
seedRedisAvailable(t, fixture.Adapter, "provider-a", now, now.Add(time.Second), time.Minute,
|
|
testProxy("proxy-a", "192.0.2.10"))
|
|
|
|
result, err := fixture.Adapter.Extract(context.Background(), extractionDomain.Command{
|
|
RequestID: "req-empty-reserve", ClientID: "client-a", Requested: 1,
|
|
Fulfillment: extractionDomain.Partial, Now: now.Add(2 * time.Second), ReserveForGateway: 1,
|
|
})
|
|
if err != nil || result.Returned != 0 || len(result.Items) != 0 {
|
|
t.Fatalf("Extract(reserved empty) = %+v, %v", result, err)
|
|
}
|
|
})
|
|
|
|
t.Run("zero requested with idempotency", func(t *testing.T) {
|
|
fixture := newRedisTestFixture(t)
|
|
now := redisTestNow()
|
|
command := extractionDomain.Command{
|
|
RequestID: "req-empty-zero", ClientID: "client-a", IdempotencyKey: "idem-empty-zero",
|
|
Requested: 0, Fulfillment: extractionDomain.Partial, Now: now,
|
|
}
|
|
result, err := fixture.Adapter.Extract(context.Background(), command)
|
|
if err != nil || result.Requested != 0 || result.Returned != 0 || len(result.Items) != 0 {
|
|
t.Fatalf("Extract(zero requested) = %+v, %v", result, err)
|
|
}
|
|
command.RequestID = "req-empty-zero-replay"
|
|
command.Now = now.Add(time.Second)
|
|
replayed, err := fixture.Adapter.Extract(context.Background(), command)
|
|
if err != nil || replayed.Requested != 0 || replayed.Returned != 0 || len(replayed.Items) != 0 {
|
|
t.Fatalf("Extract(zero requested replay) = %+v, %v", replayed, err)
|
|
}
|
|
command.RequestID = "req-empty-zero-conflict"
|
|
command.Requested = 1
|
|
if _, err := fixture.Adapter.Extract(context.Background(), command); !errors.Is(err, extractionDomain.ErrIdempotencyConflict) {
|
|
t.Fatalf("Extract(zero requested conflict) error = %v", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestRedisExtractAppliesTTLHealthAgeAndGatewayReserve(t *testing.T) {
|
|
fixture := newRedisTestFixture(t)
|
|
now := redisTestNow()
|
|
observedAt := now.Add(-30 * time.Second)
|
|
|
|
short := testProxy("short", "192.0.2.10")
|
|
seedRedisAvailable(t, fixture.Adapter, "provider-a", observedAt, now.Add(-time.Second), 35*time.Second, short)
|
|
stale := testProxy("stale", "192.0.2.11")
|
|
seedRedisAvailable(t, fixture.Adapter, "provider-a", observedAt, now.Add(-10*time.Second), 2*time.Minute, stale)
|
|
fresh := testProxy("fresh", "192.0.2.12")
|
|
seedRedisAvailable(t, fixture.Adapter, "provider-a", observedAt, now.Add(-time.Second), 2*time.Minute, fresh)
|
|
|
|
result, err := fixture.Adapter.Extract(context.Background(), extractionDomain.Command{
|
|
RequestID: "req-policy", ClientID: "client-a", Requested: 2,
|
|
Fulfillment: extractionDomain.Partial, Now: now,
|
|
MinRemainingTTL: 30 * time.Second, MaxHealthCheckAge: 5 * time.Second,
|
|
})
|
|
if err != nil || result.Returned != 1 || result.Items[0].ID != "fresh" {
|
|
t.Fatalf("Extract(policy) = %+v, %v", result, err)
|
|
}
|
|
|
|
reserveFixture := newRedisTestFixture(t)
|
|
for index := range 3 {
|
|
candidate := testProxy(fmt.Sprintf("reserve-%d", index), fmt.Sprintf("192.0.2.%d", index+20))
|
|
seedRedisAvailable(t, reserveFixture.Adapter, "provider-a", now, now.Add(time.Second), time.Minute, candidate)
|
|
}
|
|
reserved, err := reserveFixture.Adapter.Extract(context.Background(), extractionDomain.Command{
|
|
RequestID: "req-reserve", ClientID: "client-a", Requested: 2,
|
|
Fulfillment: extractionDomain.Partial, Now: now.Add(2 * time.Second), ReserveForGateway: 2,
|
|
})
|
|
if err != nil || reserved.Returned != 1 {
|
|
t.Fatalf("Extract(reserve) = %+v, %v", reserved, err)
|
|
}
|
|
}
|
|
|
|
func TestRedisExtractIdempotencyReplayConflictAndExpiryBound(t *testing.T) {
|
|
fixture := newRedisTestFixture(t)
|
|
now := redisTestNow()
|
|
seedRedisAvailable(t, fixture.Adapter, "provider-a", now, now.Add(time.Second), time.Minute,
|
|
testProxy("proxy-a", "192.0.2.10"))
|
|
command := extractionDomain.Command{
|
|
RequestID: "req-first", ClientID: "client-a", IdempotencyKey: "idem-12345678",
|
|
IdempotencyTTL: 5 * time.Minute, Requested: 1,
|
|
Fulfillment: extractionDomain.Partial, Now: now.Add(2 * time.Second),
|
|
}
|
|
first, err := fixture.Adapter.Extract(context.Background(), command)
|
|
if err != nil || first.Returned != 1 {
|
|
t.Fatalf("first Extract() = %+v, %v", first, err)
|
|
}
|
|
command.RequestID = "req-replay"
|
|
command.Now = now.Add(10 * time.Second)
|
|
replayed, err := fixture.Adapter.Extract(context.Background(), command)
|
|
if err != nil || replayed.Returned != 1 || !replayed.ExtractedAt.Equal(first.ExtractedAt) {
|
|
t.Fatalf("replayed Extract() = %+v, %v", replayed, err)
|
|
}
|
|
command.RequestID = "req-conflict"
|
|
command.Requested = 2
|
|
if _, err := fixture.Adapter.Extract(context.Background(), command); !errors.Is(err, extractionDomain.ErrIdempotencyConflict) {
|
|
t.Fatalf("conflicting Extract() error = %v", err)
|
|
}
|
|
|
|
expiryFixture := newRedisTestFixture(t)
|
|
realNow := time.Now().UTC().Truncate(time.Millisecond)
|
|
shortLived := testProxy("short-lived", "192.0.2.30")
|
|
shortLived.State = proxyDomain.StateAvailable
|
|
if _, err := expiryFixture.Adapter.UpsertFetched(context.Background(), "provider-a", activitypool.FetchedBatch{
|
|
ObservedAt: realNow, ConfiguredTTL: 2 * time.Second, MaxSize: 10,
|
|
Proxies: []proxyDomain.Proxy{shortLived},
|
|
}); err != nil {
|
|
t.Fatalf("UpsertFetched(short-lived): %v", err)
|
|
}
|
|
expiryCommand := extractionDomain.Command{
|
|
RequestID: "req-expiry-first", ClientID: "client-a", IdempotencyKey: "idem-expiry",
|
|
IdempotencyTTL: time.Minute, Requested: 1,
|
|
Fulfillment: extractionDomain.Partial, Now: realNow.Add(time.Millisecond),
|
|
}
|
|
original, err := expiryFixture.Adapter.Extract(context.Background(), expiryCommand)
|
|
if err != nil || original.Returned != 1 {
|
|
t.Fatalf("Extract(short-lived) = %+v, %v", original, err)
|
|
}
|
|
time.Sleep(2200 * time.Millisecond)
|
|
replacementAt := time.Now().UTC().Truncate(time.Millisecond)
|
|
if _, err := expiryFixture.Adapter.UpsertFetched(context.Background(), "provider-a", activitypool.FetchedBatch{
|
|
ObservedAt: replacementAt, ConfiguredTTL: time.Minute, MaxSize: 10,
|
|
Proxies: []proxyDomain.Proxy{shortLived},
|
|
}); err != nil {
|
|
t.Fatalf("UpsertFetched(replacement): %v", err)
|
|
}
|
|
expiryCommand.RequestID = "req-expiry-second"
|
|
expiryCommand.Now = replacementAt.Add(time.Millisecond)
|
|
again, err := expiryFixture.Adapter.Extract(context.Background(), expiryCommand)
|
|
if err != nil || again.Returned != 1 || again.ExtractedAt.Equal(original.ExtractedAt) {
|
|
t.Fatalf("Extract(after idempotency expiry) = %+v, %v", again, err)
|
|
}
|
|
}
|
|
|
|
func TestRedisExtractIdempotencyDigestCanonicalizesBusinessFilters(t *testing.T) {
|
|
fixture := newRedisTestFixture(t)
|
|
now := redisTestNow()
|
|
candidate := testProxy("proxy-a", "192.0.2.10")
|
|
candidate.Tags = map[string]string{"region": "cn", "carrier": "ct"}
|
|
seedRedisAvailable(t, fixture.Adapter, "provider-a", now, now.Add(time.Second), time.Minute, candidate)
|
|
|
|
command := extractionDomain.Command{
|
|
RequestID: "req-canonical-first", ClientID: "client-a", SourceIP: "192.0.2.100",
|
|
IdempotencyKey: "idem-canonical", IdempotencyTTL: time.Minute,
|
|
Requested: 1, Fulfillment: extractionDomain.Partial, Now: now.Add(2 * time.Second),
|
|
MinRemainingTTL: time.Second, MaxHealthCheckAge: time.Minute,
|
|
Protocols: []string{"http", "http"}, Regions: []string{"cn", "cn"},
|
|
Carriers: []string{"ct", "ct"}, Upstreams: []string{"provider-a", "provider-a"},
|
|
}
|
|
first, err := fixture.Adapter.Extract(context.Background(), command)
|
|
if err != nil || first.Returned != 1 {
|
|
t.Fatalf("first Extract() = %+v, %v", first, err)
|
|
}
|
|
|
|
command.RequestID = "req-canonical-replay"
|
|
command.SourceIP = "198.51.100.200"
|
|
command.Now = now.Add(10 * time.Second)
|
|
command.IdempotencyTTL = 2 * time.Minute
|
|
command.MinRemainingTTL = 2 * time.Second
|
|
command.MaxHealthCheckAge = 2 * time.Minute
|
|
command.Protocols = []string{"http"}
|
|
command.Regions = []string{"cn"}
|
|
command.Carriers = []string{"ct"}
|
|
command.Upstreams = []string{"provider-a"}
|
|
replayed, err := fixture.Adapter.Extract(context.Background(), command)
|
|
if err != nil || replayed.Returned != 1 || !replayed.ExtractedAt.Equal(first.ExtractedAt) ||
|
|
replayed.Items[0].ID != first.Items[0].ID {
|
|
t.Fatalf("replayed Extract() = %+v, %v", replayed, err)
|
|
}
|
|
}
|
|
|
|
func TestRedisExtractReplaysTheCommittedRequestOperation(t *testing.T) {
|
|
fixture := newRedisTestFixture(t)
|
|
now := redisTestNow()
|
|
seedRedisAvailable(t, fixture.Adapter, "provider-a", now, now.Add(time.Second), time.Minute,
|
|
testProxy("proxy-a", "192.0.2.10"))
|
|
command := extractionDomain.Command{
|
|
RequestID: "req-operation-replay", ClientID: "client-a", Requested: 1,
|
|
Fulfillment: extractionDomain.Partial, Now: now.Add(2 * time.Second),
|
|
}
|
|
|
|
first, err := fixture.Adapter.Extract(context.Background(), command)
|
|
if err != nil || first.Returned != 1 {
|
|
t.Fatalf("first Extract() = %+v, %v", first, err)
|
|
}
|
|
command.Now = now.Add(3 * time.Second)
|
|
replayed, err := fixture.Adapter.Extract(context.Background(), command)
|
|
if err != nil || replayed.Returned != 1 || !replayed.ExtractedAt.Equal(first.ExtractedAt) ||
|
|
replayed.Items[0].ID != first.Items[0].ID {
|
|
t.Fatalf("replayed Extract() = %+v, %v", replayed, err)
|
|
}
|
|
}
|
|
|
|
func TestRedisExtractIsExclusiveUnderConcurrentRaces(t *testing.T) {
|
|
fixture := newRedisTestFixture(t)
|
|
now := redisTestNow()
|
|
for iteration := range 100 {
|
|
candidate := testProxy(fmt.Sprintf("race-%d", iteration), fmt.Sprintf("198.51.100.%d", iteration+1))
|
|
candidate.State = proxyDomain.StateAvailable
|
|
if _, err := fixture.Adapter.UpsertFetched(context.Background(), "provider-a", activitypool.FetchedBatch{
|
|
ObservedAt: now, ConfiguredTTL: 10 * time.Minute, MaxSize: 10,
|
|
Proxies: []proxyDomain.Proxy{candidate},
|
|
}); err != nil {
|
|
t.Fatalf("iteration %d UpsertFetched(): %v", iteration, err)
|
|
}
|
|
|
|
results := make(chan extractionDomain.Result, 2)
|
|
errorsCh := make(chan error, 2)
|
|
var workers sync.WaitGroup
|
|
for worker := range 2 {
|
|
workers.Add(1)
|
|
go func(worker int) {
|
|
defer workers.Done()
|
|
result, err := fixture.Adapter.Extract(context.Background(), extractionDomain.Command{
|
|
RequestID: fmt.Sprintf("req-race-%d-%d", iteration, worker),
|
|
ClientID: fmt.Sprintf("client-%d", worker), Requested: 1,
|
|
Fulfillment: extractionDomain.Partial, Now: now.Add(time.Second),
|
|
})
|
|
results <- result
|
|
errorsCh <- err
|
|
}(worker)
|
|
}
|
|
workers.Wait()
|
|
close(results)
|
|
close(errorsCh)
|
|
for err := range errorsCh {
|
|
if err != nil {
|
|
t.Fatalf("iteration %d Extract(): %v", iteration, err)
|
|
}
|
|
}
|
|
returned := 0
|
|
for result := range results {
|
|
returned += result.Returned
|
|
}
|
|
if returned != 1 {
|
|
t.Fatalf("iteration %d total returned = %d, want 1", iteration, returned)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRedisExtractScanBudgetExhaustionDoesNotMutateCandidates(t *testing.T) {
|
|
fixture := newRedisTestFixture(t)
|
|
now := redisTestNow()
|
|
for index, ttl := range []time.Duration{3 * time.Minute, 2 * time.Minute, time.Minute} {
|
|
candidate := testProxy(fmt.Sprintf("scan-%d", index), fmt.Sprintf("203.0.113.%d", index+1))
|
|
candidate.State = proxyDomain.StateAvailable
|
|
candidate.Tags["region"] = "none"
|
|
if index == 2 {
|
|
candidate.Tags["region"] = "target"
|
|
}
|
|
if _, err := fixture.Adapter.UpsertFetched(context.Background(), "provider-a", activitypool.FetchedBatch{
|
|
ObservedAt: now, ConfiguredTTL: ttl, MaxSize: 10, Proxies: []proxyDomain.Proxy{candidate},
|
|
}); err != nil {
|
|
t.Fatalf("UpsertFetched(scan-%d): %v", index, err)
|
|
}
|
|
}
|
|
bounded, err := New(fixture.Client, Options{
|
|
Namespace: fixture.Namespace, Credentials: fixture.Credentials,
|
|
OperationTTL: time.Minute, MaxCandidateScan: 2, CleanupLimit: 16,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("New(bounded): %v", err)
|
|
}
|
|
_, err = bounded.Extract(context.Background(), extractionDomain.Command{
|
|
RequestID: "req-scan", ClientID: "client-a", Requested: 1,
|
|
Fulfillment: extractionDomain.AllOrNothing, Now: now.Add(time.Second),
|
|
Regions: []string{"target", "other"},
|
|
})
|
|
if !errors.Is(err, extractionDomain.ErrStoreUnavailable) {
|
|
t.Fatalf("Extract(scan exhausted) error = %v", err)
|
|
}
|
|
|
|
remaining, err := fixture.Adapter.Extract(context.Background(), extractionDomain.Command{
|
|
RequestID: "req-after-scan", ClientID: "client-a", Requested: 3,
|
|
Fulfillment: extractionDomain.AllOrNothing, Now: now.Add(time.Second),
|
|
})
|
|
if err != nil || remaining.Returned != 3 {
|
|
t.Fatalf("Extract(after scan exhaustion) = %+v, %v", remaining, err)
|
|
}
|
|
}
|
|
|
|
func TestRedisExtractDoesNotConsumeIncompleteRecords(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
mutate func(map[string]any)
|
|
}{
|
|
{name: "empty host", mutate: func(record map[string]any) { record["host"] = "" }},
|
|
{name: "missing creation time", mutate: func(record map[string]any) { record["createdAtMs"] = float64(0) }},
|
|
{name: "negative latency", mutate: func(record map[string]any) { record["latencyNs"] = float64(-1) }},
|
|
{name: "negative concurrency", mutate: func(record map[string]any) { record["maxConcurrency"] = float64(-1) }},
|
|
{name: "invalid state", mutate: func(record map[string]any) { record["state"] = "BROKEN" }},
|
|
{name: "invalid credential version", mutate: func(record map[string]any) { record["credentialVersion"] = float64(1) }},
|
|
{name: "invalid last success", mutate: func(record map[string]any) { record["lastSuccessAtMs"] = float64(-1) }},
|
|
{name: "foreign index key", mutate: func(record map[string]any) { record["indexKeys"] = []any{"pp:{other}:available"} }},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
fixture := newRedisTestFixture(t)
|
|
now := redisTestNow()
|
|
seedRedisAvailable(t, fixture.Adapter, "provider-a", now, now.Add(time.Second), time.Minute,
|
|
testProxy("corrupt", "192.0.2.10"))
|
|
|
|
raw, err := fixture.Client.HGet(context.Background(), fixture.Adapter.keys.records, "corrupt").Result()
|
|
if err != nil {
|
|
t.Fatalf("HGet(corrupt): %v", err)
|
|
}
|
|
var stored map[string]any
|
|
if err := json.Unmarshal([]byte(raw), &stored); err != nil {
|
|
t.Fatalf("decode stored record: %v", err)
|
|
}
|
|
test.mutate(stored)
|
|
expectedState := stored["state"]
|
|
corrupted, err := json.Marshal(stored)
|
|
if err != nil {
|
|
t.Fatalf("encode corrupt record: %v", err)
|
|
}
|
|
if err := fixture.Client.HSet(context.Background(), fixture.Adapter.keys.records, "corrupt", corrupted).Err(); err != nil {
|
|
t.Fatalf("HSet(corrupt): %v", err)
|
|
}
|
|
|
|
result, err := fixture.Adapter.Extract(context.Background(), extractionDomain.Command{
|
|
RequestID: "req-corrupt", ClientID: "client-a", Requested: 1,
|
|
Fulfillment: extractionDomain.Partial, Now: now.Add(2 * time.Second),
|
|
})
|
|
if err != nil || result.Returned != 0 || len(result.Items) != 0 {
|
|
t.Fatalf("Extract(corrupt) = %+v, %v", result, err)
|
|
}
|
|
|
|
after, err := fixture.Client.HGet(context.Background(), fixture.Adapter.keys.records, "corrupt").Result()
|
|
if err != nil {
|
|
t.Fatalf("HGet(corrupt after extraction): %v", err)
|
|
}
|
|
if err := json.Unmarshal([]byte(after), &stored); err != nil {
|
|
t.Fatalf("decode record after extraction: %v", err)
|
|
}
|
|
if stored["state"] != expectedState {
|
|
t.Fatalf("corrupt record state = %q, want unchanged %q", stored["state"], expectedState)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func seedRedisAvailable(
|
|
t *testing.T,
|
|
adapter *Adapter,
|
|
upstreamID string,
|
|
observedAt time.Time,
|
|
availableAt time.Time,
|
|
ttl time.Duration,
|
|
candidate proxyDomain.Proxy,
|
|
) {
|
|
t.Helper()
|
|
candidate.State = proxyDomain.StateFetched
|
|
if _, err := adapter.UpsertFetched(context.Background(), upstreamID, activitypool.FetchedBatch{
|
|
ObservedAt: observedAt, ConfiguredTTL: ttl, MaxSize: 100,
|
|
Proxies: []proxyDomain.Proxy{candidate},
|
|
}); err != nil {
|
|
t.Fatalf("UpsertFetched(%s): %v", candidate.ID, err)
|
|
}
|
|
if _, err := adapter.ApplyHealth(context.Background(), activitypool.HealthUpdate{
|
|
ProxyID: candidate.ID, CheckedAt: availableAt.Add(-time.Millisecond), NextState: proxyDomain.StateChecking,
|
|
}); err != nil {
|
|
t.Fatalf("ApplyHealth(%s, checking): %v", candidate.ID, err)
|
|
}
|
|
if _, err := adapter.ApplyHealth(context.Background(), activitypool.HealthUpdate{
|
|
ProxyID: candidate.ID, CheckedAt: availableAt, NextState: proxyDomain.StateAvailable,
|
|
}); err != nil {
|
|
t.Fatalf("ApplyHealth(%s, available): %v", candidate.ID, err)
|
|
}
|
|
}
|