Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ and `docker.io/blockossreleases/spectre`.

`spectre serve` collects the quarantines that mirrors report. It listens on
`127.0.0.1:50070` by default and keeps them in memory, so a restart loses them.
To keep them in Redis instead, pass `--storage=redis` and `--redis-url`, such as
`redis://localhost:6379/0`.

Every mirror reports to a server, so `spectre supervise` requires its URL,
`--server-url`, and the mirror's identity, `--service`, `--environment`, and
Expand Down
6 changes: 3 additions & 3 deletions Procfile.integration
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
setup! : mkdir -p dist/sockets
forecasts after=setup ready=http+unix:dist/sockets/forecasts.sock:/readyz=204 timeout=30s: spectre-sample-forecasts --listen="unix:$(pwd)/dist/sockets/forecasts.sock" --log-level=debug --log-json=DEFAULT
server ready=http:50070/readyz=204 timeout=30s: spectre serve --log-level=debug --log-json=DEFAULT
supervisor after=forecasts,server ready=http+unix:dist/sockets/ingress.sock:/readyz=204 timeout=30s: spectre supervise --server-url=http://127.0.0.1:50070 --service=sample --environment=integration --revision=integration --no-limits --listen="unix:$(pwd)/dist/sockets/ingress.sock" --socket-dir="$(pwd)/dist/sockets" --reference='spectre-sample --listen=$SPECTRE_LISTEN --forecasts=$SPECTRE_EGRESS --log-level=debug --log-json=DEFAULT' --candidate='spectre-sample --listen=$SPECTRE_LISTEN --forecasts=$SPECTRE_EGRESS --data=${SPECTRE_CANDIDATE_DATA:-internal/sample/testdata/users.json} --weather=${SPECTRE_CANDIDATE_WEATHER:-internal/sample/testdata/weather.json} --revision=candidate --log-level=debug --log-json=DEFAULT' --destination="forecasts.example=http+unix:$(pwd)/dist/sockets/forecasts.sock" --scripts-dir="${SPECTRE_SCRIPTS_DIR:-internal/sample/scripts}" --schema-dir=internal/sample/schema --descriptors-dir=dist/descriptors --match-window=1s --log-level=debug --log-json=DEFAULT
forecasts after=setup ready=http+unix:dist/sockets/forecasts.sock:/readyz=204 timeout=90s: spectre-sample-forecasts --listen="unix:$(pwd)/dist/sockets/forecasts.sock" --log-level=debug --log-json=DEFAULT
server ready=http:50070/readyz=204 timeout=90s: spectre serve --log-level=debug --log-json=DEFAULT
supervisor after=forecasts,server ready=http+unix:dist/sockets/ingress.sock:/readyz=204 timeout=90s: spectre supervise --server-url=http://127.0.0.1:50070 --service=sample --environment=integration --revision=integration --no-limits --listen="unix:$(pwd)/dist/sockets/ingress.sock" --socket-dir="$(pwd)/dist/sockets" --reference='spectre-sample --listen=$SPECTRE_LISTEN --forecasts=$SPECTRE_EGRESS --log-level=debug --log-json=DEFAULT' --candidate='spectre-sample --listen=$SPECTRE_LISTEN --forecasts=$SPECTRE_EGRESS --data=${SPECTRE_CANDIDATE_DATA:-internal/sample/testdata/users.json} --weather=${SPECTRE_CANDIDATE_WEATHER:-internal/sample/testdata/weather.json} --revision=candidate --log-level=debug --log-json=DEFAULT' --destination="forecasts.example=http+unix:$(pwd)/dist/sockets/forecasts.sock" --scripts-dir="${SPECTRE_SCRIPTS_DIR:-internal/sample/scripts}" --schema-dir=internal/sample/schema --descriptors-dir=dist/descriptors --match-window=1s --log-level=debug --log-json=DEFAULT
verify! after=supervisor: if [ -n "${SPECTRE_PROCTOR_LOG:-}" ]; then response-comparison.integration.test.sh verify; fi
9 changes: 7 additions & 2 deletions cli_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/block/spectre/internal/cli"
"github.com/block/spectre/internal/server"
"github.com/block/spectre/internal/server/client"
"github.com/block/spectre/internal/server/storage"
"github.com/block/spectre/internal/supervisor"
)

Expand Down Expand Up @@ -122,9 +123,13 @@ func TestServeCommandEmbedsConfig(t *testing.T) {
command := &cli.CommandLine{}
parser, err := newParser(command)
assert.NoError(t, err)
_, err = parser.Parse([]string{"serve", "--listen=127.0.0.1:50071"})
_, err = parser.Parse([]string{"serve", "--listen=127.0.0.1:50071", "--storage=redis", "--redis-url=redis://localhost:6379/0"})
assert.NoError(t, err)
assert.Equal(t, server.ServeConfig{Listen: "127.0.0.1:50071"}, command.Serve.Serve)
expected := server.ServeConfig{
Listen: "127.0.0.1:50071",
Storage: storage.Config{Storage: storage.BackendRedis, RedisURL: "redis://localhost:6379/0"},
}
assert.Equal(t, expected, command.Serve.Serve)
}

func TestSuperviseCommandRequiresFlags(t *testing.T) {
Expand Down
7 changes: 6 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ require (
github.com/alecthomas/kong v1.16.1
github.com/alecthomas/kong-toml v0.4.1
github.com/alecthomas/types v0.20.1
github.com/alicebob/miniredis/v2 v2.40.0
github.com/block/scaffolder v1.12.0
github.com/gobwas/glob v1.0.0
github.com/grafana/sobek v0.0.0-20260915160442-8a431c44cd7b
github.com/lmittmann/tint v1.2.0
github.com/microsoft/TypeScript/tsc/shim/typescript v0.0.0
github.com/pelletier/go-toml v1.9.5
github.com/redis/go-redis/v9 v9.23.0
github.com/santhosh-tekuri/jsonschema/v6 v6.0.3
go.jetify.com/typeid v1.3.0
golang.org/x/mod v0.41.0
Expand All @@ -32,6 +34,7 @@ require (
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/alecthomas/repr v0.5.2 // indirect
github.com/block/getit v0.1.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/dlclark/regexp2 v1.11.4 // indirect
github.com/dlclark/regexp2/v2 v2.8.0 // indirect
github.com/dop251/goja v0.0.0-20260311135729-065cd970411c // indirect
Expand All @@ -40,9 +43,11 @@ require (
github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect
github.com/hexops/gotextdiff v1.0.3 // indirect
github.com/klauspost/compress v1.20.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/klauspost/cpuid/v2 v2.4.0 // indirect
github.com/microsoft/TypeScript/tsc v0.0.0-20261001235638-09b1db061731 // indirect
github.com/yuin/gopher-lua v1.1.1 // indirect
github.com/zeebo/xxh3 v1.1.0 // indirect
go.uber.org/atomic v1.12.0 // indirect
golang.org/x/text v0.42.0 // indirect
)

Expand Down
30 changes: 20 additions & 10 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,18 @@ github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/alecthomas/types v0.20.1 h1:NwFTHt5VQSOa+7Q8ZgdZIZY9SgnbxZxSIut2RuK7xOE=
github.com/alecthomas/types v0.20.1/go.mod h1:FPpx/TFcz1NP/fsbg3iy4mnEFfw0YOEuhXYwZQSxL5I=
github.com/alicebob/miniredis/v2 v2.40.0 h1:6IgTQbvm2JiMYZ8yur/acNJzVjtSOWCelxXbk/LUBXc=
github.com/alicebob/miniredis/v2 v2.40.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM=
github.com/block/getit v0.1.0 h1:14J3b9xL9uKn6GIctLPr3MnSKAUXzFHTr7RDjwizLBU=
github.com/block/getit v0.1.0/go.mod h1:wldKOgqqKEfPvEQMM5DgRJqWU1ob6n940/GDFFOft+4=
github.com/block/scaffolder v1.12.0 h1:VTKV3/yxd1O7Blpx9EsZwHi2VtzoLhpD2Fkv3xu57nI=
github.com/block/scaffolder v1.12.0/go.mod h1:TmpCQ6cBwO29/TWCdPYeZ0nkefccHZeo19ZgMX5FzUk=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/dlclark/regexp2 v1.11.4 h1:rPYF9/LECdNymJufQKmri9gV604RvvABwgOA8un7yAo=
github.com/dlclark/regexp2 v1.11.4/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dlclark/regexp2/v2 v2.8.0 h1:CekDhPLGfm+GAJmPVFIG+5dqMIQPkyHJll7BbdneDfw=
Expand Down Expand Up @@ -52,8 +58,8 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/klauspost/compress v1.20.0 h1:a3C1ke2ohxFymNlb2HWAHjDeKCI90scRskErZkR0ezA=
github.com/klauspost/compress v1.20.0/go.mod h1:LUdAzn7YLVvxLpc7y3V1m40wESHTgc1422pwwBSKYuI=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
github.com/lmittmann/tint v1.2.0 h1:AogHRHy8HUJUnNJBHJlYa+fR4YY8mko2cnCp67xn9JY=
github.com/lmittmann/tint v1.2.0/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
Expand All @@ -66,20 +72,26 @@ github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3v
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/peter-evans/patience v0.3.0 h1:rX0JdJeepqdQl1Sk9c9uvorjYYzL2TfgLX1adqYm9cA=
github.com/peter-evans/patience v0.3.0/go.mod h1:Kmxu5sY1NmBLFSStvXjX1wS9mIv7wMcP/ubucyMOAu0=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/redis/go-redis/v9 v9.23.0 h1:/TTVdJa8BRNsybwwGGnmiYktHNlUXtbOQxlWpHBPL3s=
github.com/redis/go-redis/v9 v9.23.0/go.mod h1:EWP2UUk+XDaI1s5nQB5mablZN6vccXlmBQTyfnF87D0=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 h1:1EYB5IzjZawrrnELUi78f9fPu57HuXjmddZPjrls/28=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.3/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE=
github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg=
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs=
github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s=
go.jetify.com/typeid v1.3.0 h1:fuWV7oxO4mSsgpxwhaVpFXgt0IfjogR29p+XAjDCVKY=
go.jetify.com/typeid v1.3.0/go.mod h1:CtVGyt2+TSp4Rq5+ARLvGsJqdNypKBAC6INQ9TLPlmk=
go.uber.org/atomic v1.12.0 h1:BvcXdFKuviU4fTL/f+SxdQ5qJX/Jix8pAkgdUcb3XOE=
go.uber.org/atomic v1.12.0/go.mod h1:I6c4cg+6HCxRjfjSsYtApoFILnpc0CGUdGkXVqbYVNk=
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
golang.org/x/mod v0.41.0 h1:qJmnOUb4YB+FsEuM3HcWucdZASCPGhsX6uljO6pog0c=
Expand All @@ -96,8 +108,6 @@ google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fw
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
modernc.org/libc v1.66.3 h1:cfCbjTUcdsKyyZZfEUKfoHcP3S0Wkvz3jgSzByEWVCQ=
Expand Down
3 changes: 2 additions & 1 deletion internal/cli/divergences_internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ import (

"github.com/block/spectre/internal/server"
"github.com/block/spectre/internal/server/client"
"github.com/block/spectre/internal/server/storage"
pb "github.com/block/spectre/spectrepb"
"github.com/block/spectre/spectrepb/spectrepbconnect"
)

func TestDivergencesListCommand(t *testing.T) {
httpServer := httptest.NewServer(server.NewService().Handler())
httpServer := httptest.NewServer(server.NewService(storage.NewMemory()).Handler())
t.Cleanup(httpServer.Close)
rpc := spectrepbconnect.NewSpectreServiceClient(httpServer.Client(), httpServer.URL)
first := time.Date(2026, 10, 1, 12, 0, 0, 0, time.UTC)
Expand Down
10 changes: 7 additions & 3 deletions internal/server/config.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
// Package server is the SPECTRE server, which collects the quarantines that
// mirrors report. It keeps them in memory, so a restart loses them.
// mirrors report. It keeps them in a storage backend, in memory or in Redis.
package server

import "github.com/block/spectre/internal/server/client"
import (
"github.com/block/spectre/internal/server/client"
"github.com/block/spectre/internal/server/storage"
)

// ReporterConfig configures how a mirror reports quarantines to the SPECTRE server.
type ReporterConfig struct {
Expand All @@ -14,7 +17,8 @@ type ReporterConfig struct {

// ServeConfig configures the SPECTRE server.
type ServeConfig struct {
Listen string `default:"127.0.0.1:50070" help:"Address for the SPECTRE server: host:port, or unix:<path|@abstract>."`
Listen string `default:"127.0.0.1:50070" help:"Address for the SPECTRE server: host:port, or unix:<path|@abstract>."`
Storage storage.Config `embed:""`
}

// ClearConfig names the service environment whose divergences to clear.
Expand Down
60 changes: 60 additions & 0 deletions internal/server/divergences.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package server

import (
"cmp"
"slices"

"github.com/alecthomas/errors"
. "github.com/alecthomas/types/optional"

pb "github.com/block/spectre/spectrepb"
)

// validateDivergence checks that a reported divergence identifies its source
// and quarantine, so every store can rely on them.
func validateDivergence(divergence *pb.Divergence) error {
if divergence.GetService() == "" || divergence.GetEnvironment() == "" || divergence.GetRevision() == "" {
return errors.New("divergence needs a service, environment, and revision")
}
if _, err := DirectionFromMessage(divergence.GetDirection()); err != nil {
return err
}
quarantine := divergence.GetQuarantine()
if quarantine.GetSince() == nil {
return errors.New("divergence needs a quarantine with its start time")
}
if _, err := ReasonFromMessage(quarantine); err != nil {
return errors.Wrap(err, "quarantine")
}
return nil
}

// filterDivergences returns the divergences that match the request, ordered by source.
func filterDivergences(divergences []*pb.Divergence, request *pb.ListDivergencesRequest) []*pb.Divergence {
matching := []*pb.Divergence{}
for _, divergence := range divergences {
quarantine := divergence.GetQuarantine()
// Getters hide whether an optional field is set, so presence is a nil check.
if matches(From(request.GetService(), request.Service != nil), Some(divergence.GetService())) &&
matches(From(request.GetEnvironment(), request.Environment != nil), Some(divergence.GetEnvironment())) &&
matches(From(request.GetRevision(), request.Revision != nil), Some(divergence.GetRevision())) &&
matches(From(request.GetNode(), request.Node != nil), From(quarantine.GetNode(), quarantine.Node != nil)) {
matching = append(matching, divergence)
}
}
slices.SortFunc(matching, func(a, b *pb.Divergence) int {
return cmp.Or(
cmp.Compare(a.GetService(), b.GetService()),
cmp.Compare(a.GetEnvironment(), b.GetEnvironment()),
cmp.Compare(a.GetDirection(), b.GetDirection()),
)
})
return matching
}

// matches reports whether value passes a filter. An unset filter matches
// every value, including an absent one.
func matches(filter, value Option[string]) bool {
expected, ok := filter.Get()
return !ok || value == Some(expected)
}
9 changes: 5 additions & 4 deletions internal/server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ import (
"github.com/block/spectre/internal/model"
"github.com/block/spectre/internal/server"
"github.com/block/spectre/internal/server/client"
"github.com/block/spectre/internal/server/storage"
pb "github.com/block/spectre/spectrepb"
"github.com/block/spectre/spectrepb/spectrepbconnect"
)

func TestDivergencesKeepLatestQuarantine(t *testing.T) {
httpServer := httptest.NewServer(server.NewService().Handler())
httpServer := httptest.NewServer(server.NewService(storage.NewMemory()).Handler())
t.Cleanup(httpServer.Close)
rpc := spectrepbconnect.NewSpectreServiceClient(httpServer.Client(), httpServer.URL)
first := time.Date(2026, 10, 1, 12, 0, 0, 0, time.UTC)
Expand Down Expand Up @@ -76,7 +77,7 @@ func TestDivergencesKeepLatestQuarantine(t *testing.T) {
}

func TestClearDivergencesOfServiceEnvironment(t *testing.T) {
httpServer := httptest.NewServer(server.NewService().Handler())
httpServer := httptest.NewServer(server.NewService(storage.NewMemory()).Handler())
t.Cleanup(httpServer.Close)
rpc := spectrepbconnect.NewSpectreServiceClient(httpServer.Client(), httpServer.URL)
divergence := func(environment string, direction pb.Direction) *pb.Divergence {
Expand Down Expand Up @@ -109,7 +110,7 @@ func TestClearDivergencesOfServiceEnvironment(t *testing.T) {
}

func TestQuarantineRejectsInvalidDivergence(t *testing.T) {
httpServer := httptest.NewServer(server.NewService().Handler())
httpServer := httptest.NewServer(server.NewService(storage.NewMemory()).Handler())
t.Cleanup(httpServer.Close)
rpc := spectrepbconnect.NewSpectreServiceClient(httpServer.Client(), httpServer.URL)
valid := func() *pb.Divergence {
Expand Down Expand Up @@ -152,7 +153,7 @@ func TestQuarantineRejectsInvalidDivergence(t *testing.T) {
func TestReporterRetriesQuarantine(t *testing.T) {
reports := make(chan struct{}, 8)
var attempts atomic.Int32
service := server.NewService().Handler()
service := server.NewService(storage.NewMemory()).Handler()
httpServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, request *http.Request) {
if request.URL.Path == spectrepbconnect.SpectreServiceQuarantineProcedure && attempts.Add(1) == 1 {
http.Error(w, "unavailable", http.StatusServiceUnavailable)
Expand Down
Loading
Loading