From 12b7ac15cc18f8b66ad68d5369bdee71406aa9c8 Mon Sep 17 00:00:00 2001 From: Jackson Date: Wed, 23 Jul 2025 22:45:01 -0400 Subject: [PATCH 1/9] Add Integration Tests - Kafka token issuance - Kafka token redemption - V1 HTTP Issuer GET endpoint - V1 HTTP Token Redemption POST endpoint - V3 HTTP Token Redemption POST endpoint This checks the entire token redemption flow for all known production use cases as well as checks that duplicate redemtion fails. There are some cases with time limited issuers where a token is issued for a future issuer whose redemption fails. These tokens are tested and verified to fail as expected. --- .github/workflows/integration-tests.yml | 28 + Dockerfile.test | 21 + Makefile | 46 +- README.md | 70 +- docker-compose.integration.yml | 106 ++ go.mod | 11 +- go.sum | 142 ++- integration-tests/intergration_test.go | 1021 ++++++++++++++++++ kafka/main.go | 32 +- kafka/signed_blinded_token_issuer_handler.go | 12 +- kafka/signed_token_redeem_handler.go | 23 +- server/issuers.go | 6 +- server/server.go | 6 +- server/tokens.go | 2 + utils/test/dynamodb.go | 2 +- 15 files changed, 1503 insertions(+), 25 deletions(-) create mode 100644 .github/workflows/integration-tests.yml create mode 100644 Dockerfile.test create mode 100644 docker-compose.integration.yml create mode 100644 integration-tests/intergration_test.go diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml new file mode 100644 index 00000000..4466cd3d --- /dev/null +++ b/.github/workflows/integration-tests.yml @@ -0,0 +1,28 @@ +name: Integration Tests + +on: + pull_request: + branches: + - master + - production + types: + - opened + - synchronize + - reopened + +jobs: + integration-tests: + name: Run Integration Tests + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout code + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + + - name: Run integration tests + run: make integration-test + + - name: Cleanup + if: always() + run: make integration-test-clean diff --git a/Dockerfile.test b/Dockerfile.test new file mode 100644 index 00000000..df6b0020 --- /dev/null +++ b/Dockerfile.test @@ -0,0 +1,21 @@ +FROM rust:1.69 AS rust_builder +RUN rustup target add x86_64-unknown-linux-musl +RUN apt-get update && apt-get install -y musl-tools +RUN git clone https://github.com/brave-intl/challenge-bypass-ristretto-ffi /src +WORKDIR /src +RUN git checkout 1.0.1 +RUN CARGO_PROFILE_RELEASE_LTO=true cargo rustc --target=x86_64-unknown-linux-musl --release --crate-type staticlib + +FROM golang:1.24 +WORKDIR /app +COPY . . + +# Copy the Rust FFI library from the rust builder stage +COPY --from=rust_builder /src/target/x86_64-unknown-linux-musl/release/libchallenge_bypass_ristretto_ffi.a /usr/lib/libchallenge_bypass_ristretto_ffi.a + +RUN apt-get update && apt-get install -y postgresql-client +RUN go mod download +RUN go get github.com/stretchr/testify/assert +RUN go get github.com/segmentio/kafka-go + +CMD ["go", "test", "-tags", "integration", "./integration-tests", "-v"] diff --git a/Makefile b/Makefile index 2a655e5d..a1af1390 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,7 @@ +# Integration test configuration +INTEGRATION_COMPOSE_FILE := docker-compose.integration.yml +INTEGRATION_COMPOSE := docker compose -f $(INTEGRATION_COMPOSE_FILE) + docker-psql: docker compose exec postgres psql -U btokens @@ -13,7 +17,7 @@ docker-test: --key-schema AttributeName=id,KeyType=HASH \ --billing-mode PAY_PER_REQUEST \ --table-name redemptions --endpoint-url http://dynamodb:8000 --region us-west-2 ) \ - && go test -v ./..." + && go test -v -tags='!integration' ./..." docker-lint: docker compose -f docker-compose.yml -f docker-compose.dev.yml run --rm -p 2416:2416 challenge-bypass golangci-lint run @@ -34,3 +38,43 @@ generate-avro: lint: docker run --rm -v "$$(pwd):/app" --workdir /app golangci/golangci-lint:v2.1.6 golangci-lint run -v ./... + +# Integration test commands +.PHONY: integration-test +integration-test: integration-test-clean + @echo "๐Ÿ—๏ธ Building services..." + @$(INTEGRATION_COMPOSE) build + + @echo "๐Ÿš€ Starting services..." + @$(INTEGRATION_COMPOSE) up -d + + @echo "โณ Waiting for services to be ready..." + @for i in $$(seq 1 30); do \ + echo -n "$$i... "; \ + sleep 1; \ + done; \ + echo "" + + @echo "๐Ÿ—๏ธ Building test runner..." + @$(INTEGRATION_COMPOSE) --profile test build test-runner + + @echo "๐Ÿงช Running integration tests..." + @$(INTEGRATION_COMPOSE) --profile test run --rm test-runner || (echo "โŒ Tests failed!"; $(MAKE) integration-test-clean; exit 1) + + @echo "๐Ÿงน Cleaning up..." + @$(MAKE) integration-test-clean + + @echo "โœ… Integration tests completed successfully!" + +.PHONY: integration-test-clean +integration-test-clean: + @echo "๐Ÿงน Cleaning up containers and volumes..." + @$(INTEGRATION_COMPOSE) --profile test down -v --remove-orphans 2>/dev/null || true + +.PHONY: integration-test-logs +integration-test-logs: + @$(INTEGRATION_COMPOSE) logs -f + +# Alias for consistency with existing naming convention +.PHONY: docker-integration-test +docker-integration-test: integration-test diff --git a/README.md b/README.md index d01ea188..085f9d73 100644 --- a/README.md +++ b/README.md @@ -21,12 +21,80 @@ This project uses [golangci-lint](https://golangci-lint.run/) for linting, this To run locally use `make lint` which runs linting using docker however if you want to run it locally using a binary release (which can be faster) follow the [installation instructions for your platform](https://golangci-lint.run/usage/install/) and then run `golangci-lint run -v ./...` ## Testing -Run the below command in order to test changes, if you have an M1 / M2 Mac (or ARM based processor) follow the steps below to setup docker to be able to run the tests + +### Unit Tests + +Run the below command in order to test changes, if you have an M1 / M2 Mac (or ARM based processor) follow the steps below to setup docker to be able to run the tests ``` make docker-test ``` +### Integration Tests + +The project includes comprehensive integration tests that verify the entire system working together with all dependencies. + +#### What the Integration Tests Do + +The integration tests: +- Spin up a complete environment with PostgreSQL, Kafka, Zookeeper, LocalStack (for DynamoDB), and the application +- Test end-to-end flows including: + - Token redemption flows through Kafka + - Token signing flows through Kafka + - Database persistence and retrieval + - DynamoDB operations +- Verify the application correctly processes messages between Kafka topics +- Ensure proper communication between all services + +#### Running Integration Tests + +To run the integration tests, simply use: + +```bash +make integration-test +# or +make docker-integration-test +``` + +This command will: +1. Clean up any existing test containers +2. Build all required services +3. Start the test environment (PostgreSQL, Kafka, Zookeeper, LocalStack) +4. Wait for all services to be healthy and ready (~30 seconds) +5. Build and run the test suite +6. Automatically clean up all containers and volumes after completion + +#### Manual Cleanup + +If the tests are interrupted or you need to manually clean up the test environment: + +```bash +make integration-test-clean +``` + +This will remove all test containers, networks, and volumes created by the integration tests. + +#### Viewing Logs + +To debug issues or view what's happening during the tests: + +```bash +make integration-test-logs +``` + +This will tail the logs from all services in the integration test environment. + +#### Test Configuration + +The integration tests use a separate `docker-compose.integration.yml` file which: +- Creates isolated test topics in Kafka +- Uses a dedicated test database +- Runs LocalStack for DynamoDB emulation +- Configures all services with test-specific settings + +The test runner writes results to the `./test-results` directory for inspection after test runs. + ### Have an M1 / M2 (ARM) Mac? + 1.) In Docker Desktop, go to: `Settings -> Docker Engine`
#### Modify file to include ``` diff --git a/docker-compose.integration.yml b/docker-compose.integration.yml new file mode 100644 index 00000000..5f8f8320 --- /dev/null +++ b/docker-compose.integration.yml @@ -0,0 +1,106 @@ +services: + cbp: + build: . + depends_on: + postgres: + condition: service_healthy + kafka: + condition: service_healthy + localstack: + condition: service_healthy + environment: + - DATABASE_URL=postgresql://testuser:testpassword@postgres:5432/testdb?sslmode=disable + - VPC_KAFKA_BROKERS=kafka:9092 + - DYNAMODB_ENDPOINT=http://localstack:4566 + - ENV=test + - REDEEM_CONSUMER_TOPIC=test.consumer.redeem + - REDEEM_PRODUCER_TOPIC=test.producer.redeem + - SIGN_CONSUMER_TOPIC=test.consumer.sign + - SIGN_PRODUCER_TOPIC=test.producer.sign + - CONSUMER_GROUP=integration-service + - AWS_REGION=us-east-1 + - AWS_ACCESS_KEY_ID=test + - AWS_SECRET_ACCESS_KEY=test + ports: + - "2416:2416" + - "9090:9090" + + postgres: + image: postgres:latest + environment: + - POSTGRES_USER=testuser + - POSTGRES_PASSWORD=testpassword + - POSTGRES_DB=testdb + ports: + - "5432:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U testuser -d testdb"] + interval: 5s + timeout: 2s + retries: 10 + + zookeeper: + image: wurstmeister/zookeeper:latest + ports: + - "2181:2181" + healthcheck: + test: ["CMD-SHELL", "echo ruok | nc localhost 2181 | grep imok"] + interval: 5s + timeout: 2s + retries: 10 + + kafka: + image: wurstmeister/kafka:latest + depends_on: + zookeeper: + condition: service_healthy + ports: + - "9092:9092" + environment: + - KAFKA_ADVERTISED_HOST_NAME=kafka + - KAFKA_ADVERTISED_PORT=9092 + - KAFKA_ZOOKEEPER_CONNECT=zookeeper:2181 + - KAFKA_CREATE_TOPICS=test.consumer.redeem:1:1,test.producer.redeem:1:1,test.consumer.sign:1:1,test.producer.sign:1:1 + - KAFKA_AUTO_CREATE_TOPICS_ENABLE=true + # Reduce memory usage for testing + - KAFKA_HEAP_OPTS=-Xmx256M -Xms128M + healthcheck: + test: ["CMD-SHELL", "kafka-topics.sh --bootstrap-server localhost:9092 --list"] + interval: 10s + timeout: 5s + retries: 10 + + localstack: + image: localstack/localstack:latest + ports: + - "4566:4566" + environment: + - SERVICES=dynamodb + - DEBUG=1 + healthcheck: + test: ["CMD", "sh", "-c", "awslocal dynamodb list-tables --region us-east-1 || exit 1"] + interval: 5s + timeout: 5s + retries: 15 + + test-runner: + build: + context: . + dockerfile: Dockerfile.test + profiles: + - test + depends_on: + cbp: + condition: service_started + kafka: + condition: service_healthy + environment: + - DATABASE_URL=postgresql://testuser:testpassword@postgres:5432/testdb?sslmode=disable + - DYNAMODB_ENDPOINT=http://localstack:4566 + - ENV=test + - TEST_SHOULD_WRITE_REDEEM_REQUESTS_HERE=test.consumer.redeem + - TEST_SHOULD_READ_REDEEM_REQUESTS_HERE=test.producer.redeem + - TEST_SHOULD_WRITE_SIGNING_REQUESTS_HERE=test.consumer.sign + - TEST_SHOULD_READ_SIGNING_REQUESTS_HERE=test.producer.sign + - CONSUMER_GROUP=test + command: ["go", "test", "-tags", "integration", "./integration-tests", "-v", "-count=1"] diff --git a/go.mod b/go.mod index 6a82e1c5..f1744200 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/go-chi/httplog/v3 v3.2.2 github.com/golang-migrate/migrate/v4 v4.18.3 github.com/google/uuid v1.6.0 + github.com/jackc/pgx/v4 v4.18.2 github.com/jmoiron/sqlx v1.3.5 github.com/lib/pq v1.10.9 github.com/patrickmn/go-cache v2.1.0+incompatible @@ -23,7 +24,7 @@ require ( github.com/satori/go.uuid v1.2.0 github.com/segmentio/kafka-go v0.4.38 github.com/segmentio/kafka-go/sasl/aws_msk_iam_v2 v0.1.0 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 ) require ( @@ -34,6 +35,7 @@ require ( github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.23 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 // indirect @@ -52,6 +54,13 @@ require ( github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.14.3 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.3 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgtype v1.14.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/klauspost/compress v1.15.15 // indirect github.com/linkedin/goavro v2.1.0+incompatible // indirect diff --git a/go.sum b/go.sum index d19e13a4..aff1385b 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/actgardner/gogen-avro/v10 v10.2.1 h1:z3pOGblRjAJCYpkIJ8CmbMJdksi4rAhaygw0dyXZ930= @@ -41,6 +43,7 @@ github.com/aws/aws-sdk-go-v2/service/dynamodb v1.18.4/go.mod h1:njGV8YOTBFbXQGuo github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11/go.mod h1:iV4q2hsqtNECrfmlXyord9u4zyuFEJX9eLgLpSPzWA8= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA= +github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.23 h1:5AwQnYQT3ZX/N7hPTAx4ClWyucaiqr2esQRMNbJIby0= github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.23/go.mod h1:s8OUYECPoPpevQHmRmMBemFIx6Oc91iapsw56KiXIMY= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.13/go.mod h1:V390DK4MQxLpDdXxFqizyz8KUxuWImkW/xzgXMz0yyk= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM= @@ -76,7 +79,12 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 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/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= @@ -108,6 +116,8 @@ github.com/go-chi/httplog/v3 v3.2.2 h1:G0oYv3YYcikNjijArHFUlqfR78cQNh9fGT43i6Stq github.com/go-chi/httplog/v3 v3.2.2/go.mod h1:N/J1l5l1fozUrqIVuT8Z/HzNeSy8TF2EFyokPLe6y2w= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -116,7 +126,10 @@ github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8w github.com/go-redis/redis/v8 v8.4.2/go.mod h1:A1tbYoHSa1fXwN+//ljcCYYJeLmVrwL9hbQN45Jdy0M= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-migrate/migrate/v4 v4.18.3 h1:EYGkoOsvgHHfm5U/naS1RP/6PL/Xv3S4B/swMiAmDLs= @@ -145,6 +158,7 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -155,31 +169,90 @@ github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9 github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= +github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= +github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= +github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.18.2 h1:xVpYkNR5pk5bMCZGfClbO962UIqVABcAGt7ha1s/FeU= +github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.15.7/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/linkedin/goavro v2.1.0+incompatible h1:DV2aUlj2xZiuxQyvag8Dy7zjY69ENjS66bWkSfdpddY= github.com/linkedin/goavro v2.1.0+incompatible/go.mod h1:bBCwI2eGYpUI/4820s67MElg9tdeLbINjLjiM2xZFYM= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= @@ -214,6 +287,7 @@ github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFu github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -231,10 +305,14 @@ github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhi github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w= github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= @@ -246,16 +324,26 @@ github.com/segmentio/kafka-go/sasl/aws_msk_iam_v2 v0.1.0 h1:Fjet4CFbGyWMbvwWb42P github.com/segmentio/kafka-go/sasl/aws_msk_iam_v2 v0.1.0/go.mod h1:zk5DCsbNtQ0BhooxFaVpLBns0tArkR/xE+4oq2MvCq0= github.com/shengdoushi/base58 v1.0.0 h1:tGe4o6TmdXFJWoI31VoSWvuaKxf0Px3gqa3sUWhAxBs= github.com/shengdoushi/base58 v1.0.0/go.mod h1:m5uIILfzcKMw6238iWAhP4l3s5+uXyF3+bJKUNhAL9I= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -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.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/throttled/throttled/v2 v2.12.0 h1:IezKE1uHlYC/0Al05oZV6Ar+uN/znw3cy9J8banxhEY= github.com/throttled/throttled/v2 v2.12.0/go.mod h1:+EAvrG2hZAQTx8oMpBu8fq6Xmm+d1P2luKK7fIY1Esc= github.com/xdg/scram v1.0.5 h1:TuS0RFmt5Is5qm9Tm2SoD89OPqe4IRiFtyFY4iwWXsw= @@ -265,6 +353,7 @@ github.com/xdg/stringprep v1.0.3/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 h1:k/gmLsJDWwWqbLCur2yWnJzwQEKRcAHXo6seXGuSwWw= github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= go.opentelemetry.io/otel v0.14.0/go.mod h1:vH5xEuwy7Rts0GNtsCW3HYQoZDY+OmBJ6t1bFGGlxgw= @@ -274,18 +363,41 @@ go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2 go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -298,12 +410,20 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -317,21 +437,34 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -341,13 +474,17 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/linkedin/goavro.v1 v1.0.5 h1:BJa69CDh0awSsLUmZ9+BowBdokpduDZSM9Zk8oKHfN4= gopkg.in/linkedin/goavro.v1 v1.0.5/go.mod h1:Aw5GdAbizjOEl0kAMHV9iHmA8reZzW/OKuJAl4Hb9F0= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -356,3 +493,4 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/integration-tests/intergration_test.go b/integration-tests/intergration_test.go new file mode 100644 index 00000000..1d1f9a3d --- /dev/null +++ b/integration-tests/intergration_test.go @@ -0,0 +1,1021 @@ +//go:build integration +// +build integration + +package integration + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "os" + "testing" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/dynamodb" + crypto "github.com/brave-intl/challenge-bypass-ristretto-ffi" + avroSchema "github.com/brave-intl/challenge-bypass-server/avro/generated" + "github.com/brave-intl/challenge-bypass-server/utils/test" + "github.com/google/uuid" + kafka "github.com/segmentio/kafka-go" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// RedeemEndpoint identifies which redemption API to use +type RedeemEndpoint string + +const ( + RedeemV1 RedeemEndpoint = "v1" + RedeemV3 RedeemEndpoint = "v3" + // Kafka settings + kafkaHost = "kafka:9092" + maxRetries = 10 + retryInterval = 2 * time.Second + responseWaitDuration = 30 * time.Second +) + +var ( + connectionTestTopicName = "test.connection." + uuid.New().String() + requestRedeemTopicName = os.Getenv("TEST_SHOULD_WRITE_REDEEM_REQUESTS_HERE") + responseRedeemTopicName = os.Getenv("TEST_SHOULD_READ_REDEEM_REQUESTS_HERE") + requestIssuanceTopicName = os.Getenv("TEST_SHOULD_WRITE_SIGNING_REQUESTS_HERE") + responseIssuanceTopicName = os.Getenv("TEST_SHOULD_READ_SIGNING_REQUESTS_HERE") + testIssuerSuffix = uuid.New() +) + +// TokenInfo represents an unblinded token and its signing key +type TokenInfo struct { + UnblindedToken *crypto.UnblindedToken + SignedKey string +} + +// API request/response types +type issuerResponse struct { + ID string `json:"id"` + Name string `json:"name"` + PublicKey *crypto.PublicKey `json:"public_key"` + ExpiresAt string `json:"expires_at,omitempty"` + Cohort int16 `json:"cohort"` +} +type issuerV3CreateRequest struct { + Name string `json:"name"` + Cohort int16 `json:"cohort"` + MaxTokens int `json:"max_tokens"` + ExpiresAt *time.Time `json:"expires_at"` + ValidFrom *time.Time `json:"valid_from"` + Duration string `json:"duration"` + Overlap int `json:"overlap"` + Buffer int `json:"buffer"` +} +type blindedTokenRedeemRequest struct { + Payload string `json:"payload"` + TokenPreimage *crypto.TokenPreimage `json:"t"` + Signature *crypto.VerificationSignature `json:"signature"` +} +type blindedTokenRedeemResponse struct { + Cohort int16 `json:"cohort"` +} +type BlindedTokenIssueRequestV2 struct { + BlindedTokens []*crypto.BlindedToken `json:"blinded_tokens"` + IssuerCohort int16 `json:"cohort"` +} +type blindedTokenIssueResponse struct { + BatchProof *crypto.BatchDLEQProof `json:"batch_proof"` + SignedTokens []*crypto.SignedToken `json:"signed_tokens"` + PublicKey *crypto.PublicKey `json:"public_key"` +} + +// TestMain runs setup before all tests +func TestMain(m *testing.M) { + setup() + result := m.Run() + os.Exit(result) +} +func setup() { + logger := log.New(os.Stdout, "[TEST SETUP] ", log.Ldate|log.Ltime) + logger.Println("Starting test environment setup...") + waitForKafka(logger) + ensureTopicsExist(logger) + inspectKafkaSetup(logger) + checkNetworkConnectivity(logger) + initializeLocalStack(logger) + createTestIssuer(logger) + logger.Println("Test environment setup completed successfully") +} +func TestBasicKafkaConnection(t *testing.T) { + t.Log("TESTING BASIC KAFKA CONNECTION") + testMessage := "Hello Kafka " + uuid.New().String() + + t.Log("Connecting to Kafka for writing...") + conn, err := kafka.DialLeader(context.Background(), "tcp", kafkaHost, connectionTestTopicName, 0) + require.NoError(t, err, "Failed to connect to Kafka for writing") + defer conn.Close() + + t.Log("Writing test message to Kafka...") + conn.SetWriteDeadline(time.Now().Add(30 * time.Second)) + _, err = conn.WriteMessages( + kafka.Message{Value: []byte(testMessage)}, + ) + require.NoError(t, err, "Failed to write test message") + t.Logf("Successfully wrote message: %s", testMessage) + + t.Log("Creating reader to verify message...") + conn.SetReadDeadline(time.Now().Add(30 * time.Second)) + reader := kafka.NewReader(kafka.ReaderConfig{ + Brokers: []string{kafkaHost}, + Topic: connectionTestTopicName, + }) + defer reader.Close() + + t.Log("Attempting to read back the message...") + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + message, err := reader.ReadMessage(ctx) + if err != nil { + t.Fatalf("Failed to read message: %v", err) + } + t.Logf("Successfully read message: %s", string(message.Value)) + assert.Equal(t, testMessage, string(message.Value), "Message content doesn't match") + t.Log("BASIC KAFKA CONNECTION TEST PASSED") +} +func TestKafkaTokenIssuanceAndRedeemFlow(t *testing.T) { + t.Log("TESTING KAFKA TOKEN ISSUANCE AND REDEMPTION FLOW") + requestID := fmt.Sprintf("test-request-%d", time.Now().UnixNano()) + testMetadata := []byte(`{"user_id": "test-user", "timestamp": "2025-07-30T12:00:00Z"}`) + testID := uuid.New().String() + + t.Logf("Test parameters: RequestID=%s, TestID=%s", requestID, testID) + t.Log("Step 1: Issuing tokens via Kafka...") + tokens, blindedTokens, signingResultSet := issueTokensViaKafka( + t, + requestID, + testMetadata, + testID, + ) + t.Logf("Successfully received signing results with %d tokens", len(tokens)) + + // Unblind tokens and attempt to redeem each one + t.Log("Step 2: Processing signing results and attempting redemption...") + for i, result := range signingResultSet.Data { + t.Logf("Processing signing result %d/%d", i+1, len(signingResultSet.Data)) + + require.Equal(t, avroSchema.SigningResultV2StatusOk, result.Status, "Signing failed for result %d", i) + require.NotEmpty(t, result.Signed_tokens, "No signed tokens in result %d", i) + require.NotEmpty(t, result.Issuer_public_key, "No public key in result %d", i) + require.NotEmpty(t, result.Proof, "No proof in result %d", i) + t.Logf("Successfully received %d signed tokens for request %d", len(result.Signed_tokens), i+1) + + validFrom, _ := time.Parse(time.RFC3339, result.Valid_from.String) + // Skip tokens signed with future keys + if validFrom.After(time.Now()) { + t.Logf("SKIPPING: Token signed with future key %s (valid from %s) - this is expected", + result.Issuer_public_key, validFrom.Format(time.RFC3339)) + continue + } + + t.Log("Processing signed tokens...") + var signedTokens []*crypto.SignedToken + for _, signedTokenString := range result.Signed_tokens { + var signedToken crypto.SignedToken + err := signedToken.UnmarshalText([]byte(signedTokenString)) + require.NoError(t, err, "failed to unmarshal signed token") + signedTokens = append(signedTokens, &signedToken) + } + + t.Log("Processing blinded tokens...") + var responseBlindedTokens []*crypto.BlindedToken + for _, blindedTokenString := range result.Blinded_tokens { + var blindedToken crypto.BlindedToken + err := blindedToken.UnmarshalText([]byte(blindedTokenString)) + require.NoError(t, err, "failed to unmarshal blinded token") + responseBlindedTokens = append(responseBlindedTokens, &blindedToken) + } + + t.Log("Unmarshaling cryptographic parameters...") + var ( + batchDLEQProof crypto.BatchDLEQProof + issuerPublicKey crypto.PublicKey + ) + err := issuerPublicKey.UnmarshalText([]byte(result.Issuer_public_key)) + require.NoError(t, err, "failed to unmarshal issuer public key") + err = batchDLEQProof.UnmarshalText([]byte(result.Proof)) + require.NoError(t, err, "failed to unmarshal batch DLEQ proof") + + // Verify signed tokens + t.Log("Verifying signed tokens with DLEQ proof...") + verifyResult, err := batchDLEQProof.Verify( + responseBlindedTokens, + signedTokens, + &issuerPublicKey, + ) + require.NoError(t, err, "failed to verify signed tokens") + require.Equal(t, verifyResult, true, "DLEQ proof should be valid") + t.Log("Successfully verified tokens with DLEQ proof") + + // REDEMPTION PHASE - Use the specific token that was signed with this key + t.Log("Preparing for token redemption...") + tokenIndex := 0 + if i > 0 { + tokenIndex = 1 // For the second result, use the second token + t.Log("Using second token for this redemption (i > 0)") + } else { + t.Log("Using first token for this redemption (i = 0)") + } + + // Get the specific token that was signed with this issuer's key + specificToken := tokens[tokenIndex] + specificBlindedTokenText, err := blindedTokens[tokenIndex].MarshalText() + require.NoError(t, err, "failed to marshal specific blinded tokens") + + // Find the signed token that corresponds to our blinded token + t.Log("Finding the corresponding signed token...") + var signedUnblindedToken *crypto.UnblindedToken + var matchFound bool + + for j, respBlindedToken := range responseBlindedTokens { + respBlindedTokenText, err := respBlindedToken.MarshalText() + require.NoError(t, err, "failed to marshal response blinded tokens") + + if bytes.Equal(respBlindedTokenText, specificBlindedTokenText) { + t.Logf("Found matching token at index %d", j) + matchFound = true + + // This is our token - unblind it with the corresponding signed token + t.Log("Unblinding token...") + unblindedTokens, err := batchDLEQProof.VerifyAndUnblind( + []*crypto.Token{specificToken}, + []*crypto.BlindedToken{respBlindedToken}, + []*crypto.SignedToken{signedTokens[j]}, + &issuerPublicKey, + ) + require.NoError(t, err, "failed to verify and unblind specific token") + require.Len(t, unblindedTokens, 1, "expected exactly one unblinded token") + + signedUnblindedToken = unblindedTokens[0] + break + } + } + + require.True(t, matchFound, "Should find a matching token") + require.NotNil(t, signedUnblindedToken, "failed to find and unblind the specific token") + + t.Log("Preparing redemption request...") + tokenPreimage, err := signedUnblindedToken.Preimage().MarshalText() + require.NoError(t, err, "failed to marshal preimage") + + signature, err := signedUnblindedToken.DeriveVerificationKey().Sign("test") + require.NoError(t, err, "failed to create signature") + + stringSignature, err := signature.MarshalText() + require.NoError(t, err, "failed to marshal signature") + + redeemRequest := avroSchema.RedeemRequest{ + Associated_data: testMetadata, + Public_key: result.Issuer_public_key, + Token_preimage: string(tokenPreimage), + Binding: "test", + Signature: string(stringSignature), + } + + requestSet := &avroSchema.RedeemRequestSet{ + Request_id: requestID, + Data: []avroSchema.RedeemRequest{redeemRequest}, + } + + var requestSetBuffer bytes.Buffer + err = requestSet.Serialize(&requestSetBuffer) + require.NoError(t, err, "Failed to serialize redeem request to binary") + + t.Log("Setting up Kafka writer and reader for redemption...") + writer := kafka.NewWriter(kafka.WriterConfig{ + Brokers: []string{kafkaHost}, + Topic: requestRedeemTopicName, + }) + defer writer.Close() + + reader := kafka.NewReader(kafka.ReaderConfig{ + Brokers: []string{kafkaHost}, + Topic: responseRedeemTopicName, + GroupID: fmt.Sprintf("test-signing-%s-%d", testID, i), + StartOffset: kafka.LastOffset, + MinBytes: 1, + MaxBytes: 10e6, + MaxWait: 100 * time.Millisecond, + }) + defer reader.Close() + + t.Log("Sending redemption request to Kafka...") + err = writer.WriteMessages(context.Background(), + kafka.Message{ + Key: []byte(requestID), + Value: requestSetBuffer.Bytes(), + }, + ) + require.NoError(t, err, "Failed to write redeem request to Kafka") + t.Logf("Successfully sent token redemption request to Kafka (RequestID: %s)", requestID) + + t.Log("Waiting for redemption response from Kafka...") + ctx, cancel := context.WithTimeout(context.Background(), responseWaitDuration) + defer cancel() + + var resultSet avroSchema.RedeemResultSet + message, err := reader.ReadMessage(ctx) + if err != nil { + t.Fatalf("Failed to read redemption response from Kafka: %v", err) + } + t.Log("Successfully received redemption message from Kafka") + + t.Log("Deserializing redemption response...") + resultSet, err = avroSchema.DeserializeRedeemResultSet( + bytes.NewReader(message.Value), + ) + + if err != nil { + t.Logf("WARNING: Cannot deserialize redemption message: %v", err) + t.Logf("Raw message content: %s", string(message.Value)) + } + + if resultSet.Request_id == requestID { + t.Logf("Found matching redemption response for request ID: %s", requestID) + t.Log("Validating response...") + validateResponse(t, resultSet) + } else { + t.Logf("WARNING: Received redemption message for different request ID: %s (expected: %s)", + resultSet.Request_id, requestID) + } + + require.NotNil(t, resultSet, "Redemption response set should not be nil") + } + + t.Log("KAFKA TOKEN ISSUANCE AND REDEMPTION FLOW TEST COMPLETED") +} +func TestHTTPIssuerGetEndpoint(t *testing.T) { + t.Log("TESTING HTTP ISSUER GET ENDPOINT") + issuerName := "TestIssuer-" + testIssuerSuffix.String() + + t.Logf("Retrieving issuer information for '%s'...", issuerName) + req, err := http.NewRequest("GET", fmt.Sprintf("http://cbp:2416/v1/issuer/%s", issuerName), nil) + require.NoError(t, err, "failed to create GET issuer HTTP request") + + client := &http.Client{Timeout: 30 * time.Second} + resp, err := client.Do(req) + require.NoError(t, err, "failed to make GET issuer HTTP request") + defer resp.Body.Close() + + t.Logf("Issuer GET response status: %s", resp.Status) + require.Equal(t, http.StatusOK, resp.StatusCode, "GET issuer request should succeed") + + var issuerResp issuerResponse + err = json.NewDecoder(resp.Body).Decode(&issuerResp) + require.NoError(t, err, "failed to decode issuer response") + + t.Logf("Issuer details: ID=%s, Name=%s, Cohort=%d", + issuerResp.ID, issuerResp.Name, issuerResp.Cohort) + + assert.NotEmpty(t, issuerResp.ID, "issuer ID should not be empty") + assert.Equal(t, issuerName, issuerResp.Name, "issuer name should match") + assert.NotNil(t, issuerResp.PublicKey, "public key should not be nil") + assert.Equal(t, int16(1), issuerResp.Cohort, "cohort should be 1") + + if issuerResp.ExpiresAt != "" { + expiresAt, err := time.Parse(time.RFC3339, issuerResp.ExpiresAt) + require.NoError(t, err, "failed to parse expires_at timestamp") + + if expiresAt.After(time.Now()) { + t.Logf("Expiration is correctly set in the future: %s", expiresAt.Format(time.RFC3339)) + } else { + t.Logf("WARNING: Expiration is in the past: %s", expiresAt.Format(time.RFC3339)) + } + + assert.True(t, expiresAt.After(time.Now()), "expiration should be in the future") + } else { + t.Log("Note: Issuer doesn't have an expiration time set") + } + + t.Log("Successfully validated issuer response") + + t.Log("Testing retrieval of non-existent issuer...") + req, err = http.NewRequest("GET", "http://cbp:2416/v1/issuer/NonExistentIssuer", nil) + require.NoError(t, err, "failed to create GET issuer HTTP request for non-existent issuer") + + resp, err = client.Do(req) + require.NoError(t, err, "failed to make GET issuer HTTP request for non-existent issuer") + defer resp.Body.Close() + + t.Logf("Non-existent issuer GET response status: %s", resp.Status) + assert.Equal(t, http.StatusNotFound, resp.StatusCode, "non-existent issuer should return 404") + t.Log("Non-existent issuer correctly returned 404 Not Found") + + t.Log("HTTP ISSUER GET ENDPOINT TEST PASSED") +} +func TestHTTPTokenIssuanceViaKafkaAndRedeemFlow(t *testing.T) { + t.Log("TESTING TOKEN ISSUANCE VIA KAFKA AND REDEMPTION VIA HTTP") + issuerName := "TestIssuer-" + testIssuerSuffix.String() + requestID := fmt.Sprintf("test-request-%d", time.Now().UnixNano()) + testMetadata := []byte(`{"user_id": "test-user", "timestamp": "2025-07-30T12:00:00Z"}`) + testID := uuid.New().String() + + t.Logf("Test parameters: IssuerName=%s, RequestID=%s, TestID=%s", + issuerName, requestID, testID) + + t.Log("Step 1: Issuing tokens via Kafka...") + tokens, blindedTokens, signingResultSet := issueTokensViaKafka( + t, + requestID, + testMetadata, + testID, + ) + t.Logf("Successfully received signing results with %d tokens and %d result entries", + len(tokens), len(signingResultSet.Data)) + + t.Log("Step 2: Mapping original blinded tokens to unblinded tokens and signing keys...") + // Map each original blinded token to its unblinded token and signing key + var allTokenInfos []TokenInfo + for i, originalBlindedToken := range blindedTokens { + t.Logf("Processing token %d/%d...", i+1, len(blindedTokens)) + + originalBlindedTokenBytes, _ := originalBlindedToken.MarshalText() + originalBlindedTokenStr := string(originalBlindedTokenBytes) + + var foundMatch bool + for j, result := range signingResultSet.Data { + t.Logf("Checking against result %d/%d...", j+1, len(signingResultSet.Data)) + + for k, resultBlindedTokenStr := range result.Blinded_tokens { + if resultBlindedTokenStr == originalBlindedTokenStr { + t.Logf("Found matching blinded token in result %d at position %d", j+1, k+1) + foundMatch = true + + // Correct batch-proof for this token + t.Log("Unmarshaling cryptographic parameters...") + var batchDLEQProof crypto.BatchDLEQProof + _ = batchDLEQProof.UnmarshalText([]byte(result.Proof)) + + var issuerPublicKey crypto.PublicKey + _ = issuerPublicKey.UnmarshalText([]byte(result.Issuer_public_key)) + + var signedToken crypto.SignedToken + _ = signedToken.UnmarshalText([]byte(result.Signed_tokens[k])) + + var blindedToken crypto.BlindedToken + _ = blindedToken.UnmarshalText([]byte(resultBlindedTokenStr)) + + t.Log("Verifying and unblinding token...") + resultUnblindedTokens, err := batchDLEQProof.VerifyAndUnblind( + []*crypto.Token{tokens[i]}, + []*crypto.BlindedToken{&blindedToken}, + []*crypto.SignedToken{&signedToken}, + &issuerPublicKey) + + if err != nil { + t.Logf("WARNING: Error verifying and unblinding token: %v", err) + } else { + t.Log("Successfully verified and unblinded token") + } + + require.Len(t, resultUnblindedTokens, 1, "Should get exactly one unblinded token") + + allTokenInfos = append(allTokenInfos, TokenInfo{ + UnblindedToken: resultUnblindedTokens[0], + SignedKey: result.Issuer_public_key, + }) + + break + } + } + + if foundMatch { + break + } + } + + require.True(t, foundMatch, "token %d should have a matching result", i+1) + } + + require.Len(t, allTokenInfos, len(tokens), + "Should have the same number of token infos as original tokens") + + t.Logf("Successfully mapped %d tokens to their unblinded versions", len(allTokenInfos)) + + t.Log("Step 3: Testing redemption via HTTP v1 endpoint...") + doRedemptionHTTPTest(t, issuerName, RedeemV1, allTokenInfos) + + t.Log("Step 4: Testing redemption via HTTP v3 endpoint...") + doRedemptionHTTPTest(t, issuerName, RedeemV3, allTokenInfos) + + t.Log("TOKEN ISSUANCE VIA KAFKA AND REDEMPTION VIA HTTP TEST COMPLETED") +} +func waitForKafka(logger *log.Logger) { + var conn *kafka.Conn + var err error + + logger.Printf("Attempting to connect to Kafka at %s (max %d attempts)...", kafkaHost, maxRetries) + + for i := range maxRetries { + conn, err = kafka.DialLeader(context.Background(), "tcp", kafkaHost, "dummy", 0) + if err == nil { + conn.Close() + logger.Printf("SUCCESS: Connected to Kafka after %d/%d attempts", i+1, maxRetries) + return + } + + logger.Printf("WARNING: Attempt %d/%d: Failed to connect to Kafka: %v", i+1, maxRetries, err) + logger.Printf("Retrying in %s...", retryInterval) + time.Sleep(retryInterval) + } + + logger.Fatalf("FATAL: Failed to connect to Kafka after %d attempts: %v", maxRetries, err) +} +func ensureTopicsExist(logger *log.Logger) { + logger.Printf("Connecting to Kafka broker at %s to create topics...", kafkaHost) + + conn, err := kafka.Dial("tcp", kafkaHost) + if err != nil { + logger.Fatalf("FATAL: Failed to connect to Kafka: %v", err) + } + defer conn.Close() + + logger.Print("Finding Kafka controller...") + controller, err := conn.Controller() + if err != nil { + logger.Fatalf("FATAL: Failed to get Kafka controller: %v", err) + } + + controllerAddr := fmt.Sprintf("%s:%d", controller.Host, controller.Port) + logger.Printf("Connecting to Kafka controller at %s...", controllerAddr) + + controllerConn, err := kafka.Dial("tcp", controllerAddr) + if err != nil { + logger.Fatalf("FATAL: Failed to connect to Kafka controller: %v", err) + } + defer controllerConn.Close() + + topicNames := []string{ + connectionTestTopicName, + requestRedeemTopicName, + responseRedeemTopicName, + requestIssuanceTopicName, + responseIssuanceTopicName, + } + + logger.Print("Creating the following topics if they don't exist:") + for _, topicName := range topicNames { + logger.Printf(" - %s", topicName) + err = controllerConn.CreateTopics(kafka.TopicConfig{ + Topic: topicName, + NumPartitions: 1, + ReplicationFactor: 1, + }) + + if err != nil { + logger.Printf(" WARNING: Topic creation error (may already exist): %v", err) + } else { + logger.Printf(" SUCCESS: Topic created successfully") + } + } +} +func inspectKafkaSetup(logger *log.Logger) { + logger.Printf("Connecting to Kafka at %s to inspect configuration...", kafkaHost) + + conn, err := kafka.Dial("tcp", kafkaHost) + if err != nil { + logger.Printf("ERROR: Failed to connect to Kafka for inspection: %v", err) + return + } + defer conn.Close() + + logger.Print("Reading Kafka partitions...") + partitions, err := conn.ReadPartitions() + if err != nil { + logger.Printf("ERROR: Failed to read Kafka partitions: %v", err) + return + } + + logger.Print("=== Available Kafka topics ===") + topics := make(map[string]bool) + for _, p := range partitions { + topics[p.Topic] = true + } + + if len(topics) == 0 { + logger.Print("WARNING: No topics found in Kafka!") + } else { + for topic := range topics { + logger.Printf(" - %s", topic) + } + } + + requiredTopics := []string{ + connectionTestTopicName, + requestRedeemTopicName, + responseRedeemTopicName, + requestIssuanceTopicName, + responseIssuanceTopicName, + } + + logger.Print("=== Checking required topics ===") + for _, topic := range requiredTopics { + if topics[topic] { + logger.Printf(" SUCCESS: Topic %s - Found", topic) + } else { + logger.Printf(" ERROR: Topic %s - MISSING", topic) + } + } +} +func checkNetworkConnectivity(logger *log.Logger) { + hosts := []string{ + kafkaHost, + "kafka:9092", + "localhost:9092", + "127.0.0.1:9092", + } + + logger.Print("=== Testing Kafka connectivity with different host configurations ===") + + for _, host := range hosts { + logger.Printf("Trying to connect to: %s", host) + conn, err := kafka.Dial("tcp", host) + + if err != nil { + logger.Printf(" FAILED: %s - Error: %v", host, err) + } else { + logger.Printf(" SUCCESS: %s - Connected successfully!", host) + conn.Close() + } + } +} +func initializeLocalStack(logger *log.Logger) { + logger.Print("Creating AWS session for LocalStack...") + + sess, err := session.NewSession(&aws.Config{ + Region: aws.String("us-west-2"), + Endpoint: aws.String("http://localstack:4566"), + Credentials: credentials.NewStaticCredentials("test", "test", ""), + DisableSSL: aws.Bool(true), + S3ForcePathStyle: aws.Bool(true), + }) + + if err != nil { + logger.Fatalf("FATAL: Failed to create AWS session: %v", err) + } + + logger.Print("Creating DynamoDB client...") + svc := dynamodb.New(sess) + if svc == nil { + logger.Fatal("FATAL: Failed to create DynamoDB client") + } + + logger.Print("Setting up DynamoDB tables...") + err = test.SetupDynamodbTables(svc) + if err != nil { + logger.Fatalf("FATAL: Failed to initialize LocalStack DynamoDB tables: %v", err) + } + + logger.Print("SUCCESS: LocalStack DynamoDB setup completed successfully") +} +func createTestIssuer(logger *log.Logger) { + issuerName := "TestIssuer-" + testIssuerSuffix.String() + logger.Printf("Creating test issuer '%s'...", issuerName) + + now := time.Now() + expires := now.Add(1 * time.Hour) + + request := issuerV3CreateRequest{ + Name: issuerName, + Cohort: 1, + MaxTokens: 500, + ExpiresAt: &expires, + ValidFrom: &now, + Duration: "PT1H30M", + Overlap: 1, + Buffer: 1, + } + + logger.Printf("Issuer configuration: MaxTokens=%d, ValidFrom=%s, ExpiresAt=%s, Duration=%s, Overlap=%d, Buffer=%d", + request.MaxTokens, now.Format(time.RFC3339), expires.Format(time.RFC3339), request.Duration, request.Overlap, request.Buffer) + + jsonData, err := json.Marshal(request) + if err != nil { + logger.Fatalf("FATAL: Failed to marshal issuer creation request: %v", err) + return + } + + logger.Print("Sending POST request to create issuer...") + req, err := http.NewRequest("POST", "http://cbp:2416/v3/issuer/", bytes.NewBuffer(jsonData)) + if err != nil { + logger.Fatalf("FATAL: Failed to create HTTP request: %v", err) + return + } + + req.Header.Set("Content-Type", "application/json") + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + logger.Fatalf("FATAL: Failed to make HTTP request: %v", err) + return + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + logger.Fatalf("FATAL: Failed to read response body: %v", err) + return + } + + if resp.StatusCode >= 200 && resp.StatusCode < 300 { + logger.Printf("SUCCESS: Issuer creation succeeded with status: %s", resp.Status) + } else { + logger.Printf("WARNING: Issuer creation returned status: %s", resp.Status) + } + + if string(body) != "" { + logger.Printf("Response body: %s", string(body)) + } +} + +// Helper functions +func validateResponse(t *testing.T, response avroSchema.RedeemResultSet) { + t.Logf("Validating redemption result set with %d results...", len(response.Data)) + + for i, result := range response.Data { + t.Logf("Validating result %d/%d...", i+1, len(response.Data)) + + if result.Issuer_name == "" { + t.Logf("WARNING: Result %d is missing issuer_name", i+1) + } else { + t.Logf("Result %d has issuer_name: %s", i+1, result.Issuer_name) + } + assert.NotEmpty(t, result.Issuer_name, "Result should have an issuer_name") + + if result.Issuer_cohort < 0 { + t.Logf("WARNING: Result %d has invalid issuer_cohort: %d", i+1, result.Issuer_cohort) + } else { + t.Logf("Result %d has issuer_cohort: %d", i+1, result.Issuer_cohort) + } + assert.Greater(t, result.Issuer_cohort, int32(-1), "Issuer cohort should be a valid value") + + validStatuses := []avroSchema.RedeemResultStatus{ + avroSchema.RedeemResultStatusOk, + avroSchema.RedeemResultStatusDuplicate_redemption, + avroSchema.RedeemResultStatusUnverified, + avroSchema.RedeemResultStatusError, + avroSchema.RedeemResultStatusIdempotent_redemption, + } + + t.Logf("Result %d status: %s", i+1, result.Status) + + assert.Contains(t, validStatuses, result.Status, "Status should be a valid redemption status") + + if result.Associated_data == nil { + t.Logf("WARNING: Result %d is missing associated data", i+1) + } else { + t.Logf("Result %d has associated data present", i+1) + } + assert.NotNil(t, result.Associated_data, "Associated data should not be nil") + + if result.Status == avroSchema.RedeemResultStatusOk { + t.Logf("SUCCESS: Result %d redemption succeeded (status: OK)", i+1) + } else if result.Status == avroSchema.RedeemResultStatusDuplicate_redemption { + t.Logf("NOTE: Result %d is a duplicate redemption - this is expected when tokens are reused", i+1) + } else if result.Status == avroSchema.RedeemResultStatusIdempotent_redemption { + t.Logf("NOTE: Result %d is an idempotent redemption - this is expected for repeated requests", i+1) + } else { + t.Logf("WARNING: Result %d redemption status: %s (non-fatal, may be expected)", i+1, result.Status) + } + } +} +func issueTokensViaKafka( + t *testing.T, + requestID string, + testMetadata []byte, + testID string, +) ( + []*crypto.Token, + []*crypto.BlindedToken, + avroSchema.SigningResultV2Set, +) { + t.Logf("Issuing tokens via Kafka: RequestID=%s, TestID=%s", requestID, testID) + + var tokens []*crypto.Token + var blindedTokens []*crypto.BlindedToken + var marshaledBlindedTokens []string + + t.Log("Generating random tokens...") + // Number of tokens must be divisible by the sum of Buffer and Overlap of the Issuer + for i := range 2 { + t.Logf("Generating token %d/2...", i+1) + + token, err := crypto.RandomToken() + require.NoError(t, err, "Failed to generate random token %d", i+1) + tokens = append(tokens, token) + + t.Logf("Blinding token %d/2...", i+1) + blindedToken := token.Blind() + blindedTokens = append(blindedTokens, blindedToken) + + textToken, err := blindedToken.MarshalText() + require.NoError(t, err, "Failed to marshal blinded token %d to text", i+1) + marshaledBlindedTokens = append(marshaledBlindedTokens, string(textToken)) + } + + t.Log("Creating signing request...") + signingRequest1 := avroSchema.SigningRequest{ + Associated_data: testMetadata, + Blinded_tokens: marshaledBlindedTokens, + Issuer_type: "TestIssuer", + Issuer_cohort: 1, + } + + signingRequestSet := &avroSchema.SigningRequestSet{ + Request_id: requestID, + Data: []avroSchema.SigningRequest{signingRequest1}, + } + + var signingRequestBuffer bytes.Buffer + err := signingRequestSet.Serialize(&signingRequestBuffer) + require.NoError(t, err, "Failed to serialize signing request to binary") + + t.Log("Setting up Kafka writer and reader...") + signingWriter := kafka.NewWriter(kafka.WriterConfig{ + Brokers: []string{kafkaHost}, + Topic: requestIssuanceTopicName, + }) + defer signingWriter.Close() + + signingReader := kafka.NewReader(kafka.ReaderConfig{ + Brokers: []string{kafkaHost}, + Topic: responseIssuanceTopicName, + GroupID: fmt.Sprintf("test-signing-%s", testID), + StartOffset: kafka.LastOffset, + MinBytes: 1, + MaxBytes: 10e6, + MaxWait: 100 * time.Millisecond, + }) + defer signingReader.Close() + + t.Log("Sending signing request to Kafka...") + err = signingWriter.WriteMessages(context.Background(), + kafka.Message{ + Key: []byte(requestID), + Value: signingRequestBuffer.Bytes(), + }, + ) + require.NoError(t, err, "Failed to write signing request to Kafka") + t.Log("Successfully sent token issuance request to Kafka") + + t.Logf("Waiting for signing response (timeout: %s)...", responseWaitDuration) + ctx, cancel := context.WithTimeout(context.Background(), responseWaitDuration) + defer cancel() + + message, err := signingReader.ReadMessage(ctx) + require.NoError(t, err, "Failed to read signing response from Kafka") + t.Log("Successfully received signing response from Kafka") + + t.Log("Deserializing signing response...") + signingResultSet, err := avroSchema.DeserializeSigningResultV2Set( + bytes.NewReader(message.Value), + ) + require.NoError(t, err, "Failed to deserialize signing response") + + t.Logf("Verifying signing response: Expected RequestID=%s, Got RequestID=%s", + requestID, signingResultSet.Request_id) + + require.Equal(t, requestID, signingResultSet.Request_id, + "Request ID in signing response should match the request") + + t.Logf("Received %d signing results", len(signingResultSet.Data)) + require.Len(t, signingResultSet.Data, 2, "Expected 2 signing results") + + return tokens, blindedTokens, signingResultSet +} +func doRedemptionHTTPTest(t *testing.T, issuerName string, which RedeemEndpoint, tokenInfos []TokenInfo) { + t.Logf("TESTING HTTP REDEMPTION VIA %s ENDPOINT", which) + t.Logf("Issuer: %s, Token count: %d", issuerName, len(tokenInfos)) + + client := &http.Client{Timeout: 30 * time.Second} + + // Get the current active key for logs + t.Log("Retrieving current active key from issuer...") + issuerReq, err := http.NewRequest("GET", fmt.Sprintf("http://cbp:2416/v3/issuer/%s", issuerName), nil) + if err != nil { + t.Logf("WARNING: Failed to create issuer request: %v", err) + } + + issuerResp, err := client.Do(issuerReq) + if err != nil { + t.Logf("WARNING: Failed to retrieve issuer info: %v", err) + } else { + defer issuerResp.Body.Close() + + var issuerInfo struct { + PublicKey string `json:"public_key"` + } + + err = json.NewDecoder(issuerResp.Body).Decode(&issuerInfo) + if err != nil { + t.Logf("WARNING: Failed to decode issuer response: %v", err) + } else { + t.Logf("Current active key: %s", issuerInfo.PublicKey) + } + } + + t.Log("Attempting to redeem each token...") + succeeded := 0 + for i, ti := range tokenInfos { + t.Logf("Processing token %d/%d (signed with key: %s)...", + i+1, len(tokenInfos), ti.SignedKey) + + payload := "test" + signature, err := ti.UnblindedToken.DeriveVerificationKey().Sign(payload) + if err != nil { + t.Logf("WARNING: Failed to sign payload for token %d: %v", i+1, err) + continue + } + + redeemRequest := blindedTokenRedeemRequest{ + Payload: payload, + TokenPreimage: ti.UnblindedToken.Preimage(), + Signature: signature, + } + + jsonData, err := json.Marshal(redeemRequest) + if err != nil { + t.Logf("WARNING: Failed to marshal redemption request for token %d: %v", i+1, err) + continue + } + + url := fmt.Sprintf("http://cbp:2416/%s/blindedToken/%s/redemption/", which, issuerName) + t.Logf("Sending redemption request to: %s", url) + + req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData)) + if err != nil { + t.Logf("WARNING: Failed to create HTTP request for token %d: %v", i+1, err) + continue + } + + req.Header.Set("Content-Type", "application/json") + resp, err := client.Do(req) + if err != nil { + t.Logf("WARNING: Failed to make HTTP request for token %d: %v", i+1, err) + continue + } + + body, _ := io.ReadAll(resp.Body) + resp.Body.Close() + + if resp.StatusCode == http.StatusOK { + t.Logf("SUCCESS: Token #%d successfully redeemed (status: %s)", i+1, resp.Status) + succeeded++ + + // Test duplicate detection + t.Logf("Testing duplicate detection for token #%d...", i+1) + + req2, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData)) + if err != nil { + t.Logf("WARNING: Failed to create duplicate HTTP request: %v", err) + continue + } + + req2.Header.Set("Content-Type", "application/json") + resp2, err := client.Do(req2) + if err != nil { + t.Logf("WARNING: Failed to make duplicate HTTP request: %v", err) + continue + } + + defer resp2.Body.Close() + + if resp2.StatusCode == http.StatusConflict { + t.Logf("SUCCESS: Duplicate correctly detected (status: %s)", resp2.Status) + } else { + t.Logf("ERROR: Duplicate NOT correctly detected (expected 409 Conflict, got %d: %s)", + resp2.StatusCode, resp2.Status) + } + + require.Equal(t, http.StatusConflict, resp2.StatusCode, + "Duplicate redemption should be blocked with 409 Conflict") + } else { + // Some failures are expected, especially for tokens with future keys + bodyExcerpt := string(body) + if len(bodyExcerpt) > 100 { + bodyExcerpt = bodyExcerpt[:100] + "..." + } + t.Logf("NOTE: Token #%d failed to redeem (status: %s): %s", + i+1, resp.Status, bodyExcerpt) + t.Logf("This may be expected if the token's key is not currently active") + } + } + + t.Logf("Successfully redeemed %d/%d tokens", succeeded, len(tokenInfos)) + require.GreaterOrEqualf(t, succeeded, 1, + "At least one token must be redeemable! Zero redemptions indicates a critical problem.") + + t.Logf("HTTP REDEMPTION VIA %s ENDPOINT TEST COMPLETED", which) +} diff --git a/kafka/main.go b/kafka/main.go index 76eb116c..d0c36bf9 100644 --- a/kafka/main.go +++ b/kafka/main.go @@ -96,14 +96,21 @@ func StartConsumers(ctx context.Context, providedServer *server.Server, logger * adsResultSignV1Topic := os.Getenv("SIGN_PRODUCER_TOPIC") adsConsumerGroupV1 := os.Getenv("CONSUMER_GROUP") - prometheus.MustRegister(tokenIssuanceRequestTotal) - prometheus.MustRegister(tokenIssuanceFailureTotal) - prometheus.MustRegister(tokenRedeemRequestTotal) - prometheus.MustRegister(tokenRedeemFailureTotal) - prometheus.MustRegister(duplicateRedemptionTotal) - prometheus.MustRegister(idempotentRedemptionTotal) - prometheus.MustRegister(rebootFromPanicTotal) - prometheus.MustRegister(kafkaErrorTotal) + var prometheusRegistry prometheus.Registerer + if os.Getenv("ENV") == "local" || os.Getenv("ENV") == "test" { + prometheusRegistry = prometheus.NewRegistry() + } else { + prometheusRegistry = prometheus.DefaultRegisterer + } + + prometheusRegistry.MustRegister(tokenIssuanceRequestTotal) + prometheusRegistry.MustRegister(tokenIssuanceFailureTotal) + prometheusRegistry.MustRegister(tokenRedeemRequestTotal) + prometheusRegistry.MustRegister(tokenRedeemFailureTotal) + prometheusRegistry.MustRegister(duplicateRedemptionTotal) + prometheusRegistry.MustRegister(idempotentRedemptionTotal) + prometheusRegistry.MustRegister(rebootFromPanicTotal) + prometheusRegistry.MustRegister(kafkaErrorTotal) if len(brokers) < 1 { brokers = strings.Split(os.Getenv("VPC_KAFKA_BROKERS"), ",") @@ -356,7 +363,7 @@ func Emit( func getDialer(ctx context.Context, logger *slog.Logger) (*kafkaGo.Dialer, error) { var dialer *kafkaGo.Dialer env := os.Getenv("ENV") - if env != "local" { + if env != "local" && env != "test" { logger.Debug("generating TLSDialer") var cfg aws.Config var err error @@ -375,15 +382,14 @@ func getDialer(ctx context.Context, logger *slog.Logger) (*kafkaGo.Dialer, error return nil, fmt.Errorf("failed to setup aws config: %w", err) } - mechanism := aws_msk_iam_v2.NewMechanism(cfg) tlsDialer, _, err := batgo_kafka.TLSDialer() - dialer = tlsDialer - dialer.SASLMechanism = mechanism - if err != nil { kafkaErrorTotal.Inc() return nil, fmt.Errorf("failed to initialize TLS dialer: %w", err) } + mechanism := aws_msk_iam_v2.NewMechanism(cfg) + dialer = tlsDialer + dialer.SASLMechanism = mechanism } else { logger.Debug("generating Dialer") dialer = &kafkaGo.Dialer{ diff --git a/kafka/signed_blinded_token_issuer_handler.go b/kafka/signed_blinded_token_issuer_handler.go index 2e9f2837..1bad1129 100644 --- a/kafka/signed_blinded_token_issuer_handler.go +++ b/kafka/signed_blinded_token_issuer_handler.go @@ -161,7 +161,17 @@ OUTER: // if this is a time aware issuer, make sure the request contains the appropriate number of blinded tokens if issuer.Version == 3 && issuer.Buffer > 0 { if len(request.Blinded_tokens)%(issuer.Buffer+issuer.Overlap) != 0 { - reqLogger.Error("error request contains invalid number of blinded tokens") + reqLogger.Error( + "error request contains invalid number of blinded tokens", + "blinded_token_count", + len(request.Blinded_tokens), + "issuer_buffer", + issuer.Buffer, + "issuer_overlap", + issuer.Overlap, + "non_zero_trigger", + len(request.Blinded_tokens)%(issuer.Buffer+issuer.Overlap), + ) kafkaErrorTotal.Inc() blindedTokenResults = append(blindedTokenResults, avroSchema.SigningResultV2{ Signed_tokens: nil, diff --git a/kafka/signed_token_redeem_handler.go b/kafka/signed_token_redeem_handler.go index ee46ee11..58db52bf 100644 --- a/kafka/signed_token_redeem_handler.go +++ b/kafka/signed_token_redeem_handler.go @@ -99,12 +99,14 @@ func SignedTokenRedeemHandler( reqLogger, ) } + reqLogger.Debug("fetched issuers", "issuers", issuers) // Create a lookup for issuers & signing keys based on public key. signedTokens := make(map[string]SignedIssuerToken) now := time.Now() for _, issuer := range issuers { + reqLogger.Debug("checking issuer for match to request", "issuer", issuer) if issuer.HasExpired(now) { continue } @@ -120,7 +122,10 @@ func SignedTokenRedeemHandler( marshaledPublicKey, mErr := issuerPublicKey.MarshalText() // Unmarshalling failure is a data issue and is probably permanent. if mErr != nil { - message := fmt.Sprintf("request %s: could not unmarshal issuer public key into text", tokenRedeemRequestSet.Request_id) + message := fmt.Sprintf( + "request %s: could not unmarshal issuer public key into text", + tokenRedeemRequestSet.Request_id, + ) kafkaErrorTotal.Inc() return handlePermanentRedemptionError( ctx, @@ -225,7 +230,15 @@ func SignedTokenRedeemHandler( verified = true verifiedIssuer = &issuer verifiedCohort = int32(issuer.IssuerCohort) + } else { + reqLogger.Error("failed to verify", slog.Any("error", err)) } + } else { + reqLogger.Error( + "no signed token for the requested public key", + slog.Any("signedTokens", signedTokens), + "publicKey", request.Public_key, + ) } if !verified { @@ -418,7 +431,7 @@ func avroRedeemErrorResultFromError( Associated_data: []byte(message), } resultSet := avroSchema.RedeemResultSet{ - Request_id: "", + Request_id: requestID, Data: []avroSchema.RedeemResult{redeemResult}, } var resultSetBuffer bytes.Buffer @@ -443,7 +456,11 @@ func handlePermanentRedemptionError( redeemResultStatus int32, logger *slog.Logger, ) error { - logger.Error("encountered permanent redemption failure", slog.Any("error", message)) + logger.Error( + "encountered permanent redemption failure", + slog.Any("message", message), + slog.Any("error", cause), + ) kafkaErrorTotal.Inc() toEmit := avroRedeemErrorResultFromError( message, diff --git a/server/issuers.go b/server/issuers.go index 78384ff1..7a20d4d9 100644 --- a/server/issuers.go +++ b/server/issuers.go @@ -62,7 +62,11 @@ func (c *Server) GetLatestIssuer(issuerType string, issuerCohort int16) (*model. ) if err != nil { if errors.Is(err, errIssuerCohortNotFound) { - c.Logger.Error("Issuer with given type and cohort not found") + c.Logger.Error( + "Issuer with given type and cohort not found", + "issuerType", issuerType, + "issuerCohort", issuerCohort, + ) return nil, &handlers.AppError{ Message: "Issuer with given type and cohort not found", Code: 404, diff --git a/server/server.go b/server/server.go index 0d8697ce..6949dad1 100644 --- a/server/server.go +++ b/server/server.go @@ -189,10 +189,14 @@ func SetupLogger( ) (context.Context, *slog.Logger) { // Simplify logs during local development env := os.Getenv("ENV") + logLevel := slog.LevelWarn + if env == "local" || env == "test" { + logLevel = slog.LevelDebug + } logFormat := httplog.SchemaECS.Concise(env == "local") logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{ ReplaceAttr: logFormat.ReplaceAttr, - Level: slog.LevelWarn, + Level: logLevel, })).With( slog.String("app", "challenge-bypass"), slog.String("version", version), diff --git a/server/tokens.go b/server/tokens.go index 04f5ac16..423d4d7c 100644 --- a/server/tokens.go +++ b/server/tokens.go @@ -201,6 +201,7 @@ func (c *Server) blindedTokenRedeemHandlerV3(w http.ResponseWriter, r *http.Requ } issuer, err := c.fetchIssuerByType(ctx, issuerType) + c.Logger.Debug("ISSUER", slog.Any("issuer", issuer)) if err != nil { switch { case errors.Is(err, sql.ErrNoRows): @@ -281,6 +282,7 @@ func (c *Server) blindedTokenRedeemHandlerV3(w http.ResponseWriter, r *http.Requ } if err := btd.VerifyTokenRedemption(request.TokenPreimage, request.Signature, request.Payload, skeys); err != nil { + c.Logger.Error("failed to verify token", slog.Any("error", err)) return &handlers.AppError{ Message: "Could not verify that token redemption is valid", Code: http.StatusBadRequest, diff --git a/utils/test/dynamodb.go b/utils/test/dynamodb.go index 60a0be43..aa430599 100644 --- a/utils/test/dynamodb.go +++ b/utils/test/dynamodb.go @@ -64,7 +64,7 @@ func SetupDynamodbTables(db *dynamodb.DynamoDB) error { return fmt.Errorf("error creating dynamodb table") } - err = tableIsActive(db, *input.TableName, time.Second, 10*time.Millisecond) + err = tableIsActive(db, *input.TableName, time.Second, 100*time.Millisecond) if err != nil { return fmt.Errorf("error table is not active %w", err) } From d041c99295df415b9bc0fc8e51b2fc2ce3d572f4 Mon Sep 17 00:00:00 2001 From: Jackson Date: Mon, 11 Aug 2025 22:07:09 -0400 Subject: [PATCH 2/9] Tidy --- go.mod | 9 ---- go.sum | 138 --------------------------------------------------------- 2 files changed, 147 deletions(-) diff --git a/go.mod b/go.mod index f1744200..4d4911a6 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,6 @@ require ( github.com/go-chi/httplog/v3 v3.2.2 github.com/golang-migrate/migrate/v4 v4.18.3 github.com/google/uuid v1.6.0 - github.com/jackc/pgx/v4 v4.18.2 github.com/jmoiron/sqlx v1.3.5 github.com/lib/pq v1.10.9 github.com/patrickmn/go-cache v2.1.0+incompatible @@ -35,7 +34,6 @@ require ( github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.23 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 // indirect @@ -54,13 +52,6 @@ require ( github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/golang-lru v0.5.4 // indirect - github.com/jackc/chunkreader/v2 v2.0.1 // indirect - github.com/jackc/pgconn v1.14.3 // indirect - github.com/jackc/pgio v1.0.0 // indirect - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgproto3/v2 v2.3.3 // indirect - github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect - github.com/jackc/pgtype v1.14.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/klauspost/compress v1.15.15 // indirect github.com/linkedin/goavro v2.1.0+incompatible // indirect diff --git a/go.sum b/go.sum index aff1385b..6b745a43 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/actgardner/gogen-avro/v10 v10.2.1 h1:z3pOGblRjAJCYpkIJ8CmbMJdksi4rAhaygw0dyXZ930= @@ -43,7 +41,6 @@ github.com/aws/aws-sdk-go-v2/service/dynamodb v1.18.4/go.mod h1:njGV8YOTBFbXQGuo github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.11/go.mod h1:iV4q2hsqtNECrfmlXyord9u4zyuFEJX9eLgLpSPzWA8= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE= github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.23 h1:5AwQnYQT3ZX/N7hPTAx4ClWyucaiqr2esQRMNbJIby0= github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.23/go.mod h1:s8OUYECPoPpevQHmRmMBemFIx6Oc91iapsw56KiXIMY= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.13/go.mod h1:V390DK4MQxLpDdXxFqizyz8KUxuWImkW/xzgXMz0yyk= github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM= @@ -79,12 +76,7 @@ github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 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/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= -github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= @@ -116,8 +108,6 @@ github.com/go-chi/httplog/v3 v3.2.2 h1:G0oYv3YYcikNjijArHFUlqfR78cQNh9fGT43i6Stq github.com/go-chi/httplog/v3 v3.2.2/go.mod h1:N/J1l5l1fozUrqIVuT8Z/HzNeSy8TF2EFyokPLe6y2w= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -126,10 +116,7 @@ github.com/go-redis/redis v6.15.8+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8w github.com/go-redis/redis/v8 v8.4.2/go.mod h1:A1tbYoHSa1fXwN+//ljcCYYJeLmVrwL9hbQN45Jdy0M= github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= -github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-migrate/migrate/v4 v4.18.3 h1:EYGkoOsvgHHfm5U/naS1RP/6PL/Xv3S4B/swMiAmDLs= @@ -158,7 +145,6 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -169,90 +155,31 @@ github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9 github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= -github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= -github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= -github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= -github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= -github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= -github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= -github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= -github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= -github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= -github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= -github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= -github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= -github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= -github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= -github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= -github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= -github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= -github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= -github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= -github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= -github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= -github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= -github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= -github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.18.2 h1:xVpYkNR5pk5bMCZGfClbO962UIqVABcAGt7ha1s/FeU= -github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= -github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.15.7/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/linkedin/goavro v2.1.0+incompatible h1:DV2aUlj2xZiuxQyvag8Dy7zjY69ENjS66bWkSfdpddY= github.com/linkedin/goavro v2.1.0+incompatible/go.mod h1:bBCwI2eGYpUI/4820s67MElg9tdeLbINjLjiM2xZFYM= -github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= @@ -287,7 +214,6 @@ github.com/pierrec/lz4/v4 v4.1.17/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFu github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -305,14 +231,10 @@ github.com/redis/go-redis/v9 v9.8.0 h1:q3nRvjrlge/6UD7eTu/DSg2uYiU2mCL0G/uzBWqhi github.com/redis/go-redis/v9 v9.8.0/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= -github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w= github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= @@ -324,20 +246,10 @@ github.com/segmentio/kafka-go/sasl/aws_msk_iam_v2 v0.1.0 h1:Fjet4CFbGyWMbvwWb42P github.com/segmentio/kafka-go/sasl/aws_msk_iam_v2 v0.1.0/go.mod h1:zk5DCsbNtQ0BhooxFaVpLBns0tArkR/xE+4oq2MvCq0= github.com/shengdoushi/base58 v1.0.0 h1:tGe4o6TmdXFJWoI31VoSWvuaKxf0Px3gqa3sUWhAxBs= github.com/shengdoushi/base58 v1.0.0/go.mod h1:m5uIILfzcKMw6238iWAhP4l3s5+uXyF3+bJKUNhAL9I= -github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= -github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -353,7 +265,6 @@ github.com/xdg/stringprep v1.0.3/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9 h1:k/gmLsJDWwWqbLCur2yWnJzwQEKRcAHXo6seXGuSwWw= github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA= -github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= go.opentelemetry.io/otel v0.14.0/go.mod h1:vH5xEuwy7Rts0GNtsCW3HYQoZDY+OmBJ6t1bFGGlxgw= @@ -363,41 +274,18 @@ go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2 go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ= go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -410,20 +298,12 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -437,34 +317,21 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -474,17 +341,13 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/linkedin/goavro.v1 v1.0.5 h1:BJa69CDh0awSsLUmZ9+BowBdokpduDZSM9Zk8oKHfN4= gopkg.in/linkedin/goavro.v1 v1.0.5/go.mod h1:Aw5GdAbizjOEl0kAMHV9iHmA8reZzW/OKuJAl4Hb9F0= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -493,4 +356,3 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= From 5faf2d9e9170dcc7a988542bc65048fd233af16e Mon Sep 17 00:00:00 2001 From: Jackson Date: Mon, 11 Aug 2025 22:30:07 -0400 Subject: [PATCH 3/9] Correct test name --- integration-tests/intergration_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests/intergration_test.go b/integration-tests/intergration_test.go index 1d1f9a3d..1d5147aa 100644 --- a/integration-tests/intergration_test.go +++ b/integration-tests/intergration_test.go @@ -415,7 +415,7 @@ func TestHTTPIssuerGetEndpoint(t *testing.T) { t.Log("HTTP ISSUER GET ENDPOINT TEST PASSED") } -func TestHTTPTokenIssuanceViaKafkaAndRedeemFlow(t *testing.T) { +func TestTokenIssuanceViaKafkaAndRedeemViaHTTPFlow(t *testing.T) { t.Log("TESTING TOKEN ISSUANCE VIA KAFKA AND REDEMPTION VIA HTTP") issuerName := "TestIssuer-" + testIssuerSuffix.String() requestID := fmt.Sprintf("test-request-%d", time.Now().UnixNano()) From 181e4876d0cd6dfda04c364a0711fbd746a85208 Mon Sep 17 00:00:00 2001 From: Jackson Date: Mon, 11 Aug 2025 22:52:15 -0400 Subject: [PATCH 4/9] Correct README --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 085f9d73..ce1195c9 100644 --- a/README.md +++ b/README.md @@ -91,8 +91,6 @@ The integration tests use a separate `docker-compose.integration.yml` file which - Runs LocalStack for DynamoDB emulation - Configures all services with test-specific settings -The test runner writes results to the `./test-results` directory for inspection after test runs. - ### Have an M1 / M2 (ARM) Mac? 1.) In Docker Desktop, go to: `Settings -> Docker Engine`
From 7c7d9f529c1102aa7cda4e478105a6924d3cb31a Mon Sep 17 00:00:00 2001 From: Jackson Date: Wed, 13 Aug 2025 15:19:03 -0400 Subject: [PATCH 5/9] Rename integration test file --- integration-tests/{intergration_test.go => integration_test.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename integration-tests/{intergration_test.go => integration_test.go} (100%) diff --git a/integration-tests/intergration_test.go b/integration-tests/integration_test.go similarity index 100% rename from integration-tests/intergration_test.go rename to integration-tests/integration_test.go From 88f35b4b04737dfdd86edc92a178a15c0260e8e8 Mon Sep 17 00:00:00 2001 From: Jackson Date: Tue, 19 Aug 2025 16:12:29 -0400 Subject: [PATCH 6/9] Address simple PR feedback --- integration-tests/integration_test.go | 77 +++++++++------------------ server/issuers.go | 5 -- 2 files changed, 24 insertions(+), 58 deletions(-) diff --git a/integration-tests/integration_test.go b/integration-tests/integration_test.go index 1d5147aa..801110bb 100644 --- a/integration-tests/integration_test.go +++ b/integration-tests/integration_test.go @@ -20,12 +20,13 @@ import ( "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/dynamodb" crypto "github.com/brave-intl/challenge-bypass-ristretto-ffi" - avroSchema "github.com/brave-intl/challenge-bypass-server/avro/generated" - "github.com/brave-intl/challenge-bypass-server/utils/test" "github.com/google/uuid" kafka "github.com/segmentio/kafka-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + avroSchema "github.com/brave-intl/challenge-bypass-server/avro/generated" + "github.com/brave-intl/challenge-bypass-server/utils/test" ) // RedeemEndpoint identifies which redemption API to use @@ -50,8 +51,8 @@ var ( testIssuerSuffix = uuid.New() ) -// TokenInfo represents an unblinded token and its signing key -type TokenInfo struct { +// tokenInfo represents an unblinded token and its signing key +type tokenInfo struct { UnblindedToken *crypto.UnblindedToken SignedKey string } @@ -64,6 +65,7 @@ type issuerResponse struct { ExpiresAt string `json:"expires_at,omitempty"` Cohort int16 `json:"cohort"` } + type issuerV3CreateRequest struct { Name string `json:"name"` Cohort int16 `json:"cohort"` @@ -74,18 +76,13 @@ type issuerV3CreateRequest struct { Overlap int `json:"overlap"` Buffer int `json:"buffer"` } + type blindedTokenRedeemRequest struct { Payload string `json:"payload"` TokenPreimage *crypto.TokenPreimage `json:"t"` Signature *crypto.VerificationSignature `json:"signature"` } -type blindedTokenRedeemResponse struct { - Cohort int16 `json:"cohort"` -} -type BlindedTokenIssueRequestV2 struct { - BlindedTokens []*crypto.BlindedToken `json:"blinded_tokens"` - IssuerCohort int16 `json:"cohort"` -} + type blindedTokenIssueResponse struct { BatchProof *crypto.BatchDLEQProof `json:"batch_proof"` SignedTokens []*crypto.SignedToken `json:"signed_tokens"` @@ -98,6 +95,7 @@ func TestMain(m *testing.M) { result := m.Run() os.Exit(result) } + func setup() { logger := log.New(os.Stdout, "[TEST SETUP] ", log.Ldate|log.Ltime) logger.Println("Starting test environment setup...") @@ -109,42 +107,7 @@ func setup() { createTestIssuer(logger) logger.Println("Test environment setup completed successfully") } -func TestBasicKafkaConnection(t *testing.T) { - t.Log("TESTING BASIC KAFKA CONNECTION") - testMessage := "Hello Kafka " + uuid.New().String() - - t.Log("Connecting to Kafka for writing...") - conn, err := kafka.DialLeader(context.Background(), "tcp", kafkaHost, connectionTestTopicName, 0) - require.NoError(t, err, "Failed to connect to Kafka for writing") - defer conn.Close() - - t.Log("Writing test message to Kafka...") - conn.SetWriteDeadline(time.Now().Add(30 * time.Second)) - _, err = conn.WriteMessages( - kafka.Message{Value: []byte(testMessage)}, - ) - require.NoError(t, err, "Failed to write test message") - t.Logf("Successfully wrote message: %s", testMessage) - - t.Log("Creating reader to verify message...") - conn.SetReadDeadline(time.Now().Add(30 * time.Second)) - reader := kafka.NewReader(kafka.ReaderConfig{ - Brokers: []string{kafkaHost}, - Topic: connectionTestTopicName, - }) - defer reader.Close() - t.Log("Attempting to read back the message...") - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - message, err := reader.ReadMessage(ctx) - if err != nil { - t.Fatalf("Failed to read message: %v", err) - } - t.Logf("Successfully read message: %s", string(message.Value)) - assert.Equal(t, testMessage, string(message.Value), "Message content doesn't match") - t.Log("BASIC KAFKA CONNECTION TEST PASSED") -} func TestKafkaTokenIssuanceAndRedeemFlow(t *testing.T) { t.Log("TESTING KAFKA TOKEN ISSUANCE AND REDEMPTION FLOW") requestID := fmt.Sprintf("test-request-%d", time.Now().UnixNano()) @@ -327,9 +290,7 @@ func TestKafkaTokenIssuanceAndRedeemFlow(t *testing.T) { var resultSet avroSchema.RedeemResultSet message, err := reader.ReadMessage(ctx) - if err != nil { - t.Fatalf("Failed to read redemption response from Kafka: %v", err) - } + require.NoError(t, err, "Failed to read redemption response from Kafka") t.Log("Successfully received redemption message from Kafka") t.Log("Deserializing redemption response...") @@ -356,7 +317,8 @@ func TestKafkaTokenIssuanceAndRedeemFlow(t *testing.T) { t.Log("KAFKA TOKEN ISSUANCE AND REDEMPTION FLOW TEST COMPLETED") } -func TestHTTPIssuerGetEndpoint(t *testing.T) { + +func TestGetIssuerV1(t *testing.T) { t.Log("TESTING HTTP ISSUER GET ENDPOINT") issuerName := "TestIssuer-" + testIssuerSuffix.String() @@ -415,6 +377,7 @@ func TestHTTPIssuerGetEndpoint(t *testing.T) { t.Log("HTTP ISSUER GET ENDPOINT TEST PASSED") } + func TestTokenIssuanceViaKafkaAndRedeemViaHTTPFlow(t *testing.T) { t.Log("TESTING TOKEN ISSUANCE VIA KAFKA AND REDEMPTION VIA HTTP") issuerName := "TestIssuer-" + testIssuerSuffix.String() @@ -437,7 +400,7 @@ func TestTokenIssuanceViaKafkaAndRedeemViaHTTPFlow(t *testing.T) { t.Log("Step 2: Mapping original blinded tokens to unblinded tokens and signing keys...") // Map each original blinded token to its unblinded token and signing key - var allTokenInfos []TokenInfo + var allTokenInfos []tokenInfo for i, originalBlindedToken := range blindedTokens { t.Logf("Processing token %d/%d...", i+1, len(blindedTokens)) @@ -482,7 +445,7 @@ func TestTokenIssuanceViaKafkaAndRedeemViaHTTPFlow(t *testing.T) { require.Len(t, resultUnblindedTokens, 1, "Should get exactly one unblinded token") - allTokenInfos = append(allTokenInfos, TokenInfo{ + allTokenInfos = append(allTokenInfos, tokenInfo{ UnblindedToken: resultUnblindedTokens[0], SignedKey: result.Issuer_public_key, }) @@ -512,6 +475,7 @@ func TestTokenIssuanceViaKafkaAndRedeemViaHTTPFlow(t *testing.T) { t.Log("TOKEN ISSUANCE VIA KAFKA AND REDEMPTION VIA HTTP TEST COMPLETED") } + func waitForKafka(logger *log.Logger) { var conn *kafka.Conn var err error @@ -533,6 +497,7 @@ func waitForKafka(logger *log.Logger) { logger.Fatalf("FATAL: Failed to connect to Kafka after %d attempts: %v", maxRetries, err) } + func ensureTopicsExist(logger *log.Logger) { logger.Printf("Connecting to Kafka broker at %s to create topics...", kafkaHost) @@ -581,6 +546,7 @@ func ensureTopicsExist(logger *log.Logger) { } } } + func inspectKafkaSetup(logger *log.Logger) { logger.Printf("Connecting to Kafka at %s to inspect configuration...", kafkaHost) @@ -629,6 +595,7 @@ func inspectKafkaSetup(logger *log.Logger) { } } } + func checkNetworkConnectivity(logger *log.Logger) { hosts := []string{ kafkaHost, @@ -651,6 +618,7 @@ func checkNetworkConnectivity(logger *log.Logger) { } } } + func initializeLocalStack(logger *log.Logger) { logger.Print("Creating AWS session for LocalStack...") @@ -680,6 +648,7 @@ func initializeLocalStack(logger *log.Logger) { logger.Print("SUCCESS: LocalStack DynamoDB setup completed successfully") } + func createTestIssuer(logger *log.Logger) { issuerName := "TestIssuer-" + testIssuerSuffix.String() logger.Printf("Creating test issuer '%s'...", issuerName) @@ -791,6 +760,7 @@ func validateResponse(t *testing.T, response avroSchema.RedeemResultSet) { } } } + func issueTokensViaKafka( t *testing.T, requestID string, @@ -895,7 +865,8 @@ func issueTokensViaKafka( return tokens, blindedTokens, signingResultSet } -func doRedemptionHTTPTest(t *testing.T, issuerName string, which RedeemEndpoint, tokenInfos []TokenInfo) { + +func doRedemptionHTTPTest(t *testing.T, issuerName string, which RedeemEndpoint, tokenInfos []tokenInfo) { t.Logf("TESTING HTTP REDEMPTION VIA %s ENDPOINT", which) t.Logf("Issuer: %s, Token count: %d", issuerName, len(tokenInfos)) diff --git a/server/issuers.go b/server/issuers.go index 7a20d4d9..c965d8a0 100644 --- a/server/issuers.go +++ b/server/issuers.go @@ -62,11 +62,6 @@ func (c *Server) GetLatestIssuer(issuerType string, issuerCohort int16) (*model. ) if err != nil { if errors.Is(err, errIssuerCohortNotFound) { - c.Logger.Error( - "Issuer with given type and cohort not found", - "issuerType", issuerType, - "issuerCohort", issuerCohort, - ) return nil, &handlers.AppError{ Message: "Issuer with given type and cohort not found", Code: 404, From b9c03f61928a58322e70bb50929f56f0d47c61db Mon Sep 17 00:00:00 2001 From: Jackson Date: Tue, 19 Aug 2025 16:27:53 -0400 Subject: [PATCH 7/9] Remove unneeded type --- integration-tests/integration_test.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/integration-tests/integration_test.go b/integration-tests/integration_test.go index 801110bb..4d16791e 100644 --- a/integration-tests/integration_test.go +++ b/integration-tests/integration_test.go @@ -83,12 +83,6 @@ type blindedTokenRedeemRequest struct { Signature *crypto.VerificationSignature `json:"signature"` } -type blindedTokenIssueResponse struct { - BatchProof *crypto.BatchDLEQProof `json:"batch_proof"` - SignedTokens []*crypto.SignedToken `json:"signed_tokens"` - PublicKey *crypto.PublicKey `json:"public_key"` -} - // TestMain runs setup before all tests func TestMain(m *testing.M) { setup() From 710af1577e7d0f0388833d308ed6fbbe3c4c24b1 Mon Sep 17 00:00:00 2001 From: Jackson Date: Fri, 22 Aug 2025 23:35:15 -0400 Subject: [PATCH 8/9] Integration testing pr feedback (#827) Address PR comments regarding testing structure. --- Makefile | 2 +- integration-tests/integration_test.go | 1232 ++++++++++++------------- 2 files changed, 617 insertions(+), 617 deletions(-) diff --git a/Makefile b/Makefile index 9532ad47..ca1b9c09 100644 --- a/Makefile +++ b/Makefile @@ -49,7 +49,7 @@ integration-test: integration-test-clean @$(INTEGRATION_COMPOSE) up -d @echo "โณ Waiting for services to be ready..." - @for i in $$(seq 1 30); do \ + @for i in $$(seq 1 10); do \ echo -n "$$i... "; \ sleep 1; \ done; \ diff --git a/integration-tests/integration_test.go b/integration-tests/integration_test.go index 4d16791e..3c45acb1 100644 --- a/integration-tests/integration_test.go +++ b/integration-tests/integration_test.go @@ -29,17 +29,15 @@ import ( "github.com/brave-intl/challenge-bypass-server/utils/test" ) -// RedeemEndpoint identifies which redemption API to use type RedeemEndpoint string const ( - RedeemV1 RedeemEndpoint = "v1" - RedeemV3 RedeemEndpoint = "v3" - // Kafka settings - kafkaHost = "kafka:9092" - maxRetries = 10 - retryInterval = 2 * time.Second - responseWaitDuration = 30 * time.Second + RedeemV1 RedeemEndpoint = "v1" + RedeemV3 RedeemEndpoint = "v3" + kafkaHost = "kafka:9092" + maxRetries = 10 + retryInterval = 2 * time.Second + responseWaitDuration = 30 * time.Second ) var ( @@ -48,27 +46,24 @@ var ( responseRedeemTopicName = os.Getenv("TEST_SHOULD_READ_REDEEM_REQUESTS_HERE") requestIssuanceTopicName = os.Getenv("TEST_SHOULD_WRITE_SIGNING_REQUESTS_HERE") responseIssuanceTopicName = os.Getenv("TEST_SHOULD_READ_SIGNING_REQUESTS_HERE") - testIssuerSuffix = uuid.New() ) -// tokenInfo represents an unblinded token and its signing key type tokenInfo struct { UnblindedToken *crypto.UnblindedToken SignedKey string } -// API request/response types type issuerResponse struct { ID string `json:"id"` Name string `json:"name"` PublicKey *crypto.PublicKey `json:"public_key"` ExpiresAt string `json:"expires_at,omitempty"` - Cohort int16 `json:"cohort"` + Cohort int32 `json:"cohort"` } type issuerV3CreateRequest struct { Name string `json:"name"` - Cohort int16 `json:"cohort"` + Cohort int32 `json:"cohort"` MaxTokens int `json:"max_tokens"` ExpiresAt *time.Time `json:"expires_at"` ValidFrom *time.Time `json:"valid_from"` @@ -83,7 +78,6 @@ type blindedTokenRedeemRequest struct { Signature *crypto.VerificationSignature `json:"signature"` } -// TestMain runs setup before all tests func TestMain(m *testing.M) { setup() result := m.Run() @@ -95,286 +89,425 @@ func setup() { logger.Println("Starting test environment setup...") waitForKafka(logger) ensureTopicsExist(logger) - inspectKafkaSetup(logger) - checkNetworkConnectivity(logger) initializeLocalStack(logger) - createTestIssuer(logger) logger.Println("Test environment setup completed successfully") } func TestKafkaTokenIssuanceAndRedeemFlow(t *testing.T) { t.Log("TESTING KAFKA TOKEN ISSUANCE AND REDEMPTION FLOW") + issuerName := "TestIssuer-" + uuid.New().String() + requestID := fmt.Sprintf("test-request-%d", time.Now().UnixNano()) testMetadata := []byte(`{"user_id": "test-user", "timestamp": "2025-07-30T12:00:00Z"}`) testID := uuid.New().String() t.Logf("Test parameters: RequestID=%s, TestID=%s", requestID, testID) - t.Log("Step 1: Issuing tokens via Kafka...") - tokens, blindedTokens, signingResultSet := issueTokensViaKafka( - t, - requestID, - testMetadata, - testID, - ) - t.Logf("Successfully received signing results with %d tokens", len(tokens)) - - // Unblind tokens and attempt to redeem each one - t.Log("Step 2: Processing signing results and attempting redemption...") - for i, result := range signingResultSet.Data { - t.Logf("Processing signing result %d/%d", i+1, len(signingResultSet.Data)) - - require.Equal(t, avroSchema.SigningResultV2StatusOk, result.Status, "Signing failed for result %d", i) - require.NotEmpty(t, result.Signed_tokens, "No signed tokens in result %d", i) - require.NotEmpty(t, result.Issuer_public_key, "No public key in result %d", i) - require.NotEmpty(t, result.Proof, "No proof in result %d", i) - t.Logf("Successfully received %d signed tokens for request %d", len(result.Signed_tokens), i+1) - - validFrom, _ := time.Parse(time.RFC3339, result.Valid_from.String) - // Skip tokens signed with future keys - if validFrom.After(time.Now()) { - t.Logf("SKIPPING: Token signed with future key %s (valid from %s) - this is expected", - result.Issuer_public_key, validFrom.Format(time.RFC3339)) - continue - } - t.Log("Processing signed tokens...") - var signedTokens []*crypto.SignedToken - for _, signedTokenString := range result.Signed_tokens { - var signedToken crypto.SignedToken - err := signedToken.UnmarshalText([]byte(signedTokenString)) - require.NoError(t, err, "failed to unmarshal signed token") - signedTokens = append(signedTokens, &signedToken) - } + var tokens []*crypto.Token + var blindedTokens []*crypto.BlindedToken + var signingResultSet avroSchema.SigningResultV2Set + t.Logf("Creating test issuer '%s'...", issuerName) - t.Log("Processing blinded tokens...") - var responseBlindedTokens []*crypto.BlindedToken - for _, blindedTokenString := range result.Blinded_tokens { - var blindedToken crypto.BlindedToken - err := blindedToken.UnmarshalText([]byte(blindedTokenString)) - require.NoError(t, err, "failed to unmarshal blinded token") - responseBlindedTokens = append(responseBlindedTokens, &blindedToken) - } + now := time.Now() + expires := now.Add(1 * time.Hour) - t.Log("Unmarshaling cryptographic parameters...") - var ( - batchDLEQProof crypto.BatchDLEQProof - issuerPublicKey crypto.PublicKey - ) - err := issuerPublicKey.UnmarshalText([]byte(result.Issuer_public_key)) - require.NoError(t, err, "failed to unmarshal issuer public key") - err = batchDLEQProof.UnmarshalText([]byte(result.Proof)) - require.NoError(t, err, "failed to unmarshal batch DLEQ proof") - - // Verify signed tokens - t.Log("Verifying signed tokens with DLEQ proof...") - verifyResult, err := batchDLEQProof.Verify( - responseBlindedTokens, - signedTokens, - &issuerPublicKey, + issuerRequest := issuerV3CreateRequest{ + Name: issuerName, + Cohort: 1, + MaxTokens: 500, + ExpiresAt: &expires, + ValidFrom: &now, + Duration: "PT1H30M", + Overlap: 1, + Buffer: 1, + } + + t.Logf( + "Issuer configuration: MaxTokens=%d, ValidFrom=%s, ExpiresAt=%s, Duration=%s, Overlap=%d, Buffer=%d", + issuerRequest.MaxTokens, + now.Format(time.RFC3339), + expires.Format(time.RFC3339), + issuerRequest.Duration, + issuerRequest.Overlap, + issuerRequest.Buffer, + ) + createTestIssuer(t, issuerRequest) + + t.Run("issue_tokens", func(t *testing.T) { + tokens, blindedTokens, signingResultSet = issueTokensViaKafka( + t, + testMetadata, + testID, + requestID, + issuerRequest, ) - require.NoError(t, err, "failed to verify signed tokens") - require.Equal(t, verifyResult, true, "DLEQ proof should be valid") - t.Log("Successfully verified tokens with DLEQ proof") - - // REDEMPTION PHASE - Use the specific token that was signed with this key - t.Log("Preparing for token redemption...") - tokenIndex := 0 - if i > 0 { - tokenIndex = 1 // For the second result, use the second token - t.Log("Using second token for this redemption (i > 0)") - } else { - t.Log("Using first token for this redemption (i = 0)") - } + t.Logf("Successfully received signing results with %d tokens", len(tokens)) + require.NotEmpty(t, tokens, "Should have tokens after issuance") + require.NotEmpty(t, blindedTokens, "Should have blinded tokens after issuance") + require.NotEmpty(t, signingResultSet.Data, "Should have signing results after issuance") + }) - // Get the specific token that was signed with this issuer's key - specificToken := tokens[tokenIndex] - specificBlindedTokenText, err := blindedTokens[tokenIndex].MarshalText() - require.NoError(t, err, "failed to marshal specific blinded tokens") - - // Find the signed token that corresponds to our blinded token - t.Log("Finding the corresponding signed token...") - var signedUnblindedToken *crypto.UnblindedToken - var matchFound bool - - for j, respBlindedToken := range responseBlindedTokens { - respBlindedTokenText, err := respBlindedToken.MarshalText() - require.NoError(t, err, "failed to marshal response blinded tokens") - - if bytes.Equal(respBlindedTokenText, specificBlindedTokenText) { - t.Logf("Found matching token at index %d", j) - matchFound = true - - // This is our token - unblind it with the corresponding signed token - t.Log("Unblinding token...") - unblindedTokens, err := batchDLEQProof.VerifyAndUnblind( - []*crypto.Token{specificToken}, - []*crypto.BlindedToken{respBlindedToken}, - []*crypto.SignedToken{signedTokens[j]}, - &issuerPublicKey, + t.Run("process_signing_results", func(t *testing.T) { + for i, result := range signingResultSet.Data { + t.Logf( + "Processing signing result %d/%d", + i+1, + len(signingResultSet.Data), + ) + + require.Equal( + t, + avroSchema.SigningResultV2StatusOk, + result.Status, + "Signing should succeed for result %d", + i, + ) + + require.NotEmpty( + t, + result.Signed_tokens, + "Should have signed tokens in result %d", + i, + ) + + require.NotEmpty( + t, + result.Issuer_public_key, + "Should have public key in result %d", + i, + ) + + require.NotEmpty( + t, + result.Proof, + "Should have proof in result %d", + i, + ) + + validFrom, _ := time.Parse(time.RFC3339, result.Valid_from.String) + if validFrom.After(time.Now()) { + t.Logf( + "Skipping token with future key (valid from %s)", + validFrom.Format(time.RFC3339), ) - require.NoError(t, err, "failed to verify and unblind specific token") - require.Len(t, unblindedTokens, 1, "expected exactly one unblinded token") - - signedUnblindedToken = unblindedTokens[0] - break + continue } - } - - require.True(t, matchFound, "Should find a matching token") - require.NotNil(t, signedUnblindedToken, "failed to find and unblind the specific token") - t.Log("Preparing redemption request...") - tokenPreimage, err := signedUnblindedToken.Preimage().MarshalText() - require.NoError(t, err, "failed to marshal preimage") + // Verify the rest of the test. This only needs to run once + if i == 0 { + t.Run("verify_and_redeem_token", func(t *testing.T) { + var signedToken crypto.SignedToken + err := signedToken.UnmarshalText([]byte(result.Signed_tokens[0])) + require.NoError(t, err, "Should unmarshal signed token") - signature, err := signedUnblindedToken.DeriveVerificationKey().Sign("test") - require.NoError(t, err, "failed to create signature") + var blindedToken crypto.BlindedToken + err = blindedToken.UnmarshalText([]byte(result.Blinded_tokens[0])) + require.NoError(t, err, "Should unmarshal blinded token") - stringSignature, err := signature.MarshalText() - require.NoError(t, err, "failed to marshal signature") + var issuerPublicKey crypto.PublicKey + err = issuerPublicKey.UnmarshalText([]byte(result.Issuer_public_key)) + require.NoError(t, err, "Should unmarshal issuer public key") - redeemRequest := avroSchema.RedeemRequest{ - Associated_data: testMetadata, - Public_key: result.Issuer_public_key, - Token_preimage: string(tokenPreimage), - Binding: "test", - Signature: string(stringSignature), - } + var batchDLEQProof crypto.BatchDLEQProof + err = batchDLEQProof.UnmarshalText([]byte(result.Proof)) + require.NoError(t, err, "Should unmarshal batch DLEQ proof") - requestSet := &avroSchema.RedeemRequestSet{ - Request_id: requestID, - Data: []avroSchema.RedeemRequest{redeemRequest}, - } + verifyResult, err := batchDLEQProof.Verify( + []*crypto.BlindedToken{&blindedToken}, + []*crypto.SignedToken{&signedToken}, + &issuerPublicKey, + ) + require.NoError(t, err, "Should verify signed token") + require.True(t, verifyResult, "DLEQ proof should be valid") + + var specificToken *crypto.Token + for j, origBlinded := range blindedTokens { + origBlindedText, _ := origBlinded.MarshalText() + respBlindedText, _ := blindedToken.MarshalText() + + if bytes.Equal(origBlindedText, respBlindedText) { + specificToken = tokens[j] + break + } + } + require.NotNil(t, specificToken, "Should find the original token") - var requestSetBuffer bytes.Buffer - err = requestSet.Serialize(&requestSetBuffer) - require.NoError(t, err, "Failed to serialize redeem request to binary") + unblindedTokens, err := batchDLEQProof.VerifyAndUnblind( + []*crypto.Token{specificToken}, + []*crypto.BlindedToken{&blindedToken}, + []*crypto.SignedToken{&signedToken}, + &issuerPublicKey, + ) + require.NoError(t, err, "Should verify and unblind token") + require.Len(t, unblindedTokens, 1, "Should get exactly one unblinded token") + + signedUnblindedToken := unblindedTokens[0] + tokenPreimage, _ := signedUnblindedToken.Preimage().MarshalText() + signature, _ := signedUnblindedToken.DeriveVerificationKey().Sign("test") + stringSignature, _ := signature.MarshalText() + + redeemRequest := avroSchema.RedeemRequest{ + Associated_data: testMetadata, + Public_key: result.Issuer_public_key, + Token_preimage: string(tokenPreimage), + Binding: "test", + Signature: string(stringSignature), + } - t.Log("Setting up Kafka writer and reader for redemption...") - writer := kafka.NewWriter(kafka.WriterConfig{ - Brokers: []string{kafkaHost}, - Topic: requestRedeemTopicName, - }) - defer writer.Close() - - reader := kafka.NewReader(kafka.ReaderConfig{ - Brokers: []string{kafkaHost}, - Topic: responseRedeemTopicName, - GroupID: fmt.Sprintf("test-signing-%s-%d", testID, i), - StartOffset: kafka.LastOffset, - MinBytes: 1, - MaxBytes: 10e6, - MaxWait: 100 * time.Millisecond, - }) - defer reader.Close() - - t.Log("Sending redemption request to Kafka...") - err = writer.WriteMessages(context.Background(), - kafka.Message{ - Key: []byte(requestID), - Value: requestSetBuffer.Bytes(), - }, - ) - require.NoError(t, err, "Failed to write redeem request to Kafka") - t.Logf("Successfully sent token redemption request to Kafka (RequestID: %s)", requestID) + duplicateRequestID := requestID + "-duplicate" + requestSet := &avroSchema.RedeemRequestSet{ + Request_id: duplicateRequestID, + Data: []avroSchema.RedeemRequest{redeemRequest}, + } - t.Log("Waiting for redemption response from Kafka...") - ctx, cancel := context.WithTimeout(context.Background(), responseWaitDuration) - defer cancel() + var requestSetBuffer bytes.Buffer + err = requestSet.Serialize(&requestSetBuffer) + require.NoError(t, err, "Should serialize duplicate request") - var resultSet avroSchema.RedeemResultSet - message, err := reader.ReadMessage(ctx) - require.NoError(t, err, "Failed to read redemption response from Kafka") - t.Log("Successfully received redemption message from Kafka") + writer := kafka.NewWriter(kafka.WriterConfig{ + Brokers: []string{kafkaHost}, + Topic: requestRedeemTopicName, + }) + defer writer.Close() + + reader := kafka.NewReader(kafka.ReaderConfig{ + Brokers: []string{kafkaHost}, + Topic: responseRedeemTopicName, + GroupID: fmt.Sprintf("test-duplicate-%s", testID), + StartOffset: kafka.LastOffset, + MinBytes: 1, + MaxBytes: 10e6, + MaxWait: 100 * time.Millisecond, + }) + defer reader.Close() + + t.Run("redeem_via_kafka", func(t *testing.T) { + err = writer.WriteMessages(context.Background(), + kafka.Message{ + Key: []byte(requestID), + Value: requestSetBuffer.Bytes(), + }, + ) + require.NoError(t, err, "Should write redeem request to Kafka") + + ctx, cancel := context.WithTimeout( + context.Background(), + responseWaitDuration, + ) + defer cancel() + + message, err := reader.ReadMessage(ctx) + require.NoError(t, err, "Should read redemption response") + + resultSet, err := avroSchema.DeserializeRedeemResultSet( + bytes.NewReader(message.Value), + ) + require.NoError(t, err, "Should deserialize redemption response") + + require.Equal(t, requestID, resultSet.Request_id, + "Response request ID should match") + + require.NotEmpty(t, resultSet.Data, "Should have redemption results") + + result := resultSet.Data[0] + assert.NotEmpty(t, result.Issuer_name, "Should have issuer name") + assert.Greater(t, result.Issuer_cohort, int32(-1), "Should have valid cohort") + assert.Contains(t, + []avroSchema.RedeemResultStatus{ + avroSchema.RedeemResultStatusOk, + avroSchema.RedeemResultStatusIdempotent_redemption, + }, + result.Status, + "Redemption should succeed") + }) - t.Log("Deserializing redemption response...") - resultSet, err = avroSchema.DeserializeRedeemResultSet( - bytes.NewReader(message.Value), - ) + t.Run("redeem_duplicate", func(t *testing.T) { + err = writer.WriteMessages(context.Background(), + kafka.Message{ + Key: []byte(duplicateRequestID), + Value: requestSetBuffer.Bytes(), + }, + ) + require.NoError(t, err, "Should write duplicate request") + + ctx, cancel := context.WithTimeout( + context.Background(), + responseWaitDuration, + ) + defer cancel() + + message, err := reader.ReadMessage(ctx) + require.NoError(t, err, "Should read duplicate response") + + resultSet, err := avroSchema.DeserializeRedeemResultSet( + bytes.NewReader(message.Value), + ) + require.NoError(t, err, "Should deserialize duplicate response") + + require.Equal(t, duplicateRequestID, resultSet.Request_id, + "Duplicate response ID should match") + + require.NotEmpty(t, resultSet.Data, "Should have duplicate results") + + result := resultSet.Data[0] + assert.Equal(t, + avroSchema.RedeemResultStatusDuplicate_redemption, + result.Status, + "Duplicate redemption should be detected") + }) + }) - if err != nil { - t.Logf("WARNING: Cannot deserialize redemption message: %v", err) - t.Logf("Raw message content: %s", string(message.Value)) + // Only need to run the detailed tests once + break + } } + }) +} - if resultSet.Request_id == requestID { - t.Logf("Found matching redemption response for request ID: %s", requestID) - t.Log("Validating response...") - validateResponse(t, resultSet) - } else { - t.Logf("WARNING: Received redemption message for different request ID: %s (expected: %s)", - resultSet.Request_id, requestID) - } +func TestIssuerV1(t *testing.T) { + client := &http.Client{Timeout: 30 * time.Second} - require.NotNil(t, resultSet, "Redemption response set should not be nil") + exp := time.Now().Add(time.Hour).UTC() + + issuerRequest := issuerV3CreateRequest{ + Name: "TestIssuer-" + uuid.New().String(), + Cohort: 1, + MaxTokens: 1, + ExpiresAt: &exp, } - t.Log("KAFKA TOKEN ISSUANCE AND REDEMPTION FLOW TEST COMPLETED") -} + t.Run("create_issuer", func(t *testing.T) { + body, err := json.Marshal(issuerRequest) + require.NoError(t, err) -func TestGetIssuerV1(t *testing.T) { - t.Log("TESTING HTTP ISSUER GET ENDPOINT") - issuerName := "TestIssuer-" + testIssuerSuffix.String() + req, err := http.NewRequest( + http.MethodPost, + "http://cbp:2416/v1/issuer", + bytes.NewBuffer(body), + ) + require.NoError(t, err) - t.Logf("Retrieving issuer information for '%s'...", issuerName) - req, err := http.NewRequest("GET", fmt.Sprintf("http://cbp:2416/v1/issuer/%s", issuerName), nil) - require.NoError(t, err, "failed to create GET issuer HTTP request") + resp, err := client.Do(req) + require.NoError(t, err) - client := &http.Client{Timeout: 30 * time.Second} - resp, err := client.Do(req) - require.NoError(t, err, "failed to make GET issuer HTTP request") - defer resp.Body.Close() + assert.Equal(t, http.StatusOK, resp.StatusCode) + }) - t.Logf("Issuer GET response status: %s", resp.Status) - require.Equal(t, http.StatusOK, resp.StatusCode, "GET issuer request should succeed") + t.Run("get_issuer", func(t *testing.T) { + t.Log("TESTING HTTP ISSUER GET ENDPOINT") - var issuerResp issuerResponse - err = json.NewDecoder(resp.Body).Decode(&issuerResp) - require.NoError(t, err, "failed to decode issuer response") + t.Logf("Retrieving issuer information for '%s'...", issuerRequest.Name) + req, err := http.NewRequest( + "GET", + fmt.Sprintf("http://cbp:2416/v1/issuer/%s", issuerRequest.Name), + nil, + ) + require.NoError(t, err, "failed to create GET issuer HTTP request") - t.Logf("Issuer details: ID=%s, Name=%s, Cohort=%d", - issuerResp.ID, issuerResp.Name, issuerResp.Cohort) + client := &http.Client{Timeout: 30 * time.Second} + resp, err := client.Do(req) + require.NoError(t, err, "failed to make GET issuer HTTP request") + defer resp.Body.Close() + + t.Logf("Issuer GET response status: %s", resp.Status) + require.Equal( + t, + http.StatusOK, + resp.StatusCode, + "GET issuer request should succeed", + ) + + var issuerResp issuerResponse + err = json.NewDecoder(resp.Body).Decode(&issuerResp) + require.NoError(t, err, "failed to decode issuer response") - assert.NotEmpty(t, issuerResp.ID, "issuer ID should not be empty") - assert.Equal(t, issuerName, issuerResp.Name, "issuer name should match") - assert.NotNil(t, issuerResp.PublicKey, "public key should not be nil") - assert.Equal(t, int16(1), issuerResp.Cohort, "cohort should be 1") + t.Logf("Issuer details: ID=%s, Name=%s, Cohort=%d", + issuerResp.ID, issuerResp.Name, issuerResp.Cohort) - if issuerResp.ExpiresAt != "" { - expiresAt, err := time.Parse(time.RFC3339, issuerResp.ExpiresAt) - require.NoError(t, err, "failed to parse expires_at timestamp") + assert.NotEmpty(t, issuerResp.ID, "issuer ID should not be empty") + assert.Equal(t, issuerRequest.Name, issuerResp.Name, "issuer name should match") + assert.NotNil(t, issuerResp.PublicKey, "public key should not be nil") + assert.Equal(t, int32(1), issuerResp.Cohort, "cohort should be 1") - if expiresAt.After(time.Now()) { - t.Logf("Expiration is correctly set in the future: %s", expiresAt.Format(time.RFC3339)) + if issuerResp.ExpiresAt != "" { + expiresAt, err := time.Parse(time.RFC3339, issuerResp.ExpiresAt) + require.NoError(t, err, "failed to parse expires_at timestamp") + + if expiresAt.After(time.Now()) { + t.Logf( + "Expiration is correctly set in the future: %s", + expiresAt.Format(time.RFC3339), + ) + } else { + t.Logf( + "WARNING: Expiration is in the past: %s", + expiresAt.Format(time.RFC3339), + ) + } + + assert.True( + t, + expiresAt.After(time.Now()), + "expiration should be in the future", + ) } else { - t.Logf("WARNING: Expiration is in the past: %s", expiresAt.Format(time.RFC3339)) + t.Log("Note: Issuer doesn't have an expiration time set") } - assert.True(t, expiresAt.After(time.Now()), "expiration should be in the future") - } else { - t.Log("Note: Issuer doesn't have an expiration time set") - } + t.Log("Successfully validated issuer response") - t.Log("Successfully validated issuer response") + t.Log("Testing retrieval of non-existent issuer...") + req, err = http.NewRequest( + "GET", + "http://cbp:2416/v1/issuer/NonExistentIssuer", + nil, + ) + require.NoError( + t, + err, + "failed to create GET issuer HTTP request for non-existent issuer", + ) - t.Log("Testing retrieval of non-existent issuer...") - req, err = http.NewRequest("GET", "http://cbp:2416/v1/issuer/NonExistentIssuer", nil) - require.NoError(t, err, "failed to create GET issuer HTTP request for non-existent issuer") + resp, err = client.Do(req) + require.NoError( + t, + err, + "failed to make GET issuer HTTP request for non-existent issuer", + ) + defer resp.Body.Close() + + t.Logf("Non-existent issuer GET response status: %s", resp.Status) + assert.Equal( + t, + http.StatusNotFound, + resp.StatusCode, + "non-existent issuer should return 404", + ) + t.Log("Non-existent issuer correctly returned 404 Not Found") - resp, err = client.Do(req) - require.NoError(t, err, "failed to make GET issuer HTTP request for non-existent issuer") - defer resp.Body.Close() + t.Log("HTTP ISSUER GET ENDPOINT TEST PASSED") + }) - t.Logf("Non-existent issuer GET response status: %s", resp.Status) - assert.Equal(t, http.StatusNotFound, resp.StatusCode, "non-existent issuer should return 404") - t.Log("Non-existent issuer correctly returned 404 Not Found") + t.Run("issuer_not_found", func(t *testing.T) { + req, err := http.NewRequest( + http.MethodGet, + "http://cbp:2416/v1/issuer/NonExistentIssuer", + nil, + ) + require.NoError(t, err) + + resp, err := client.Do(req) + require.NoError(t, err) + defer resp.Body.Close() - t.Log("HTTP ISSUER GET ENDPOINT TEST PASSED") + assert.Equal(t, http.StatusNotFound, resp.StatusCode) + }) } func TestTokenIssuanceViaKafkaAndRedeemViaHTTPFlow(t *testing.T) { t.Log("TESTING TOKEN ISSUANCE VIA KAFKA AND REDEMPTION VIA HTTP") - issuerName := "TestIssuer-" + testIssuerSuffix.String() + + issuerName := "TestIssuer-" + uuid.New().String() requestID := fmt.Sprintf("test-request-%d", time.Now().UnixNano()) testMetadata := []byte(`{"user_id": "test-user", "timestamp": "2025-07-30T12:00:00Z"}`) testID := uuid.New().String() @@ -382,114 +515,246 @@ func TestTokenIssuanceViaKafkaAndRedeemViaHTTPFlow(t *testing.T) { t.Logf("Test parameters: IssuerName=%s, RequestID=%s, TestID=%s", issuerName, requestID, testID) - t.Log("Step 1: Issuing tokens via Kafka...") - tokens, blindedTokens, signingResultSet := issueTokensViaKafka( - t, - requestID, - testMetadata, - testID, - ) - t.Logf("Successfully received signing results with %d tokens and %d result entries", - len(tokens), len(signingResultSet.Data)) - - t.Log("Step 2: Mapping original blinded tokens to unblinded tokens and signing keys...") - // Map each original blinded token to its unblinded token and signing key + var tokens []*crypto.Token + var blindedTokens []*crypto.BlindedToken + var signingResultSet avroSchema.SigningResultV2Set var allTokenInfos []tokenInfo - for i, originalBlindedToken := range blindedTokens { - t.Logf("Processing token %d/%d...", i+1, len(blindedTokens)) - - originalBlindedTokenBytes, _ := originalBlindedToken.MarshalText() - originalBlindedTokenStr := string(originalBlindedTokenBytes) - - var foundMatch bool - for j, result := range signingResultSet.Data { - t.Logf("Checking against result %d/%d...", j+1, len(signingResultSet.Data)) - for k, resultBlindedTokenStr := range result.Blinded_tokens { - if resultBlindedTokenStr == originalBlindedTokenStr { - t.Logf("Found matching blinded token in result %d at position %d", j+1, k+1) - foundMatch = true - - // Correct batch-proof for this token - t.Log("Unmarshaling cryptographic parameters...") - var batchDLEQProof crypto.BatchDLEQProof - _ = batchDLEQProof.UnmarshalText([]byte(result.Proof)) - - var issuerPublicKey crypto.PublicKey - _ = issuerPublicKey.UnmarshalText([]byte(result.Issuer_public_key)) + t.Logf("Creating test issuer '%s'...", issuerName) - var signedToken crypto.SignedToken - _ = signedToken.UnmarshalText([]byte(result.Signed_tokens[k])) + now := time.Now() + expires := now.Add(1 * time.Hour) - var blindedToken crypto.BlindedToken - _ = blindedToken.UnmarshalText([]byte(resultBlindedTokenStr)) + issuerRequest := issuerV3CreateRequest{ + Name: issuerName, + Cohort: 1, + MaxTokens: 500, + ExpiresAt: &expires, + ValidFrom: &now, + Duration: "PT1H30M", + Overlap: 1, + Buffer: 1, + } - t.Log("Verifying and unblinding token...") - resultUnblindedTokens, err := batchDLEQProof.VerifyAndUnblind( - []*crypto.Token{tokens[i]}, - []*crypto.BlindedToken{&blindedToken}, - []*crypto.SignedToken{&signedToken}, - &issuerPublicKey) + t.Logf( + "Issuer configuration: MaxTokens=%d, ValidFrom=%s, ExpiresAt=%s, Duration=%s, Overlap=%d, Buffer=%d", + issuerRequest.MaxTokens, + now.Format(time.RFC3339), + expires.Format(time.RFC3339), + issuerRequest.Duration, + issuerRequest.Overlap, + issuerRequest.Buffer, + ) + createTestIssuer(t, issuerRequest) + + t.Run("issue_tokens_via_kafka", func(t *testing.T) { + tokens, blindedTokens, signingResultSet = issueTokensViaKafka( + t, + testMetadata, + testID, + requestID, + issuerRequest, + ) + t.Logf( + "Successfully received signing results with %d tokens and %d result entries", + len(tokens), + len(signingResultSet.Data), + ) + require.NotEmpty(t, tokens, "Should have tokens after issuance") + require.NotEmpty(t, blindedTokens, "Should have blinded tokens after issuance") + require.NotEmpty(t, signingResultSet.Data, "Should have signing results after issuance") + }) - if err != nil { - t.Logf("WARNING: Error verifying and unblinding token: %v", err) - } else { - t.Log("Successfully verified and unblinded token") + t.Run("map_tokens_to_signing_keys", func(t *testing.T) { + for i, originalBlindedToken := range blindedTokens { + t.Logf("Processing token %d/%d...", i+1, len(blindedTokens)) + originalBlindedTokenBytes, _ := originalBlindedToken.MarshalText() + originalBlindedTokenStr := string(originalBlindedTokenBytes) + var foundMatch bool + for j, result := range signingResultSet.Data { + for k, resultBlindedTokenStr := range result.Blinded_tokens { + if resultBlindedTokenStr == originalBlindedTokenStr { + t.Logf( + "Found matching blinded token in result %d at position %d", + j, + k, + ) + foundMatch = true + + var batchDLEQProof crypto.BatchDLEQProof + _ = batchDLEQProof.UnmarshalText([]byte(result.Proof)) + var issuerPublicKey crypto.PublicKey + _ = issuerPublicKey.UnmarshalText([]byte(result.Issuer_public_key)) + var signedToken crypto.SignedToken + _ = signedToken.UnmarshalText([]byte(result.Signed_tokens[k])) + var blindedToken crypto.BlindedToken + _ = blindedToken.UnmarshalText([]byte(resultBlindedTokenStr)) + + resultUnblindedTokens, err := batchDLEQProof.VerifyAndUnblind( + []*crypto.Token{tokens[i]}, + []*crypto.BlindedToken{&blindedToken}, + []*crypto.SignedToken{&signedToken}, + &issuerPublicKey) + + require.NoError( + t, + err, + "Should successfully verify and unblind", + ) + require.Len( + t, + resultUnblindedTokens, + 1, + "Should get exactly one unblinded token", + ) + + allTokenInfos = append(allTokenInfos, tokenInfo{ + UnblindedToken: resultUnblindedTokens[0], + SignedKey: result.Issuer_public_key, + }) + break } - - require.Len(t, resultUnblindedTokens, 1, "Should get exactly one unblinded token") - - allTokenInfos = append(allTokenInfos, tokenInfo{ - UnblindedToken: resultUnblindedTokens[0], - SignedKey: result.Issuer_public_key, - }) - + } + if foundMatch { break } } + require.True( + t, + foundMatch, + "token %d should have a matching result", + i+1, + ) + } + require.Len(t, allTokenInfos, len(tokens), + "Should have the same number of token infos as original tokens") + }) - if foundMatch { - break - } + for i, token := range allTokenInfos { + if i%2 == 0 { + t.Run("redeem_v1_endpoint", func(t *testing.T) { + testHTTPRedemption(t, issuerName, RedeemV1, token) + }) + } else { + t.Run("redeem_v3_endpoint", func(t *testing.T) { + testHTTPRedemption(t, issuerName, RedeemV3, token) + }) } + } +} - require.True(t, foundMatch, "token %d should have a matching result", i+1) +func testHTTPRedemption( + t *testing.T, + issuerName string, + endpoint RedeemEndpoint, + token tokenInfo, +) { + client := &http.Client{Timeout: 30 * time.Second} + payload := "test" + signature, err := token.UnblindedToken.DeriveVerificationKey().Sign(payload) + require.NoError(t, err, "Should be able to sign payload") + + redeemRequest := blindedTokenRedeemRequest{ + Payload: payload, + TokenPreimage: token.UnblindedToken.Preimage(), + Signature: signature, } - require.Len(t, allTokenInfos, len(tokens), - "Should have the same number of token infos as original tokens") + jsonData, err := json.Marshal(redeemRequest) + require.NoError(t, err, "Should marshal redemption request") - t.Logf("Successfully mapped %d tokens to their unblinded versions", len(allTokenInfos)) + url := fmt.Sprintf( + "http://cbp:2416/%s/blindedToken/%s/redemption/", + endpoint, + issuerName, + ) - t.Log("Step 3: Testing redemption via HTTP v1 endpoint...") - doRedemptionHTTPTest(t, issuerName, RedeemV1, allTokenInfos) + req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData)) + require.NoError(t, err, "Should create HTTP request") + req.Header.Set("Content-Type", "application/json") - t.Log("Step 4: Testing redemption via HTTP v3 endpoint...") - doRedemptionHTTPTest(t, issuerName, RedeemV3, allTokenInfos) + t.Run("successful_redemption", func(t *testing.T) { + resp, err := client.Do(req) + require.NoError(t, err, "Should make HTTP request") + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + require.NoError(t, err, "Should read response body") + + assert.Equal( + t, + http.StatusOK, + resp.StatusCode, + `Should successfully redeem token. + Instead received: %s + Request: %s`, + body, + jsonData, + ) + }) - t.Log("TOKEN ISSUANCE VIA KAFKA AND REDEMPTION VIA HTTP TEST COMPLETED") + t.Run("duplicate_redemption", func(t *testing.T) { + resp, err := client.Do(req) + require.NoError(t, err, "Should make HTTP request") + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + require.NoError(t, err, "Should read response body") + + assert.Equal( + t, + http.StatusConflict, + resp.StatusCode, + `Duplicate redemption should be blocked with 409 Conflict. + Instead received: %s + Request: %s`, + body, + jsonData, + ) + }) } func waitForKafka(logger *log.Logger) { var conn *kafka.Conn var err error - logger.Printf("Attempting to connect to Kafka at %s (max %d attempts)...", kafkaHost, maxRetries) + logger.Printf( + "Attempting to connect to Kafka at %s (max %d attempts)...", + kafkaHost, + maxRetries, + ) for i := range maxRetries { - conn, err = kafka.DialLeader(context.Background(), "tcp", kafkaHost, "dummy", 0) + conn, err = kafka.DialLeader( + context.Background(), + "tcp", + kafkaHost, + "dummy", + 0, + ) if err == nil { conn.Close() - logger.Printf("SUCCESS: Connected to Kafka after %d/%d attempts", i+1, maxRetries) + logger.Printf( + "SUCCESS: Connected to Kafka after %d/%d attempts", + i+1, + maxRetries, + ) return } - logger.Printf("WARNING: Attempt %d/%d: Failed to connect to Kafka: %v", i+1, maxRetries, err) + logger.Printf( + "WARNING: Attempt %d/%d: Failed to connect to Kafka: %v", + i+1, + maxRetries, + err, + ) logger.Printf("Retrying in %s...", retryInterval) time.Sleep(retryInterval) } - logger.Fatalf("FATAL: Failed to connect to Kafka after %d attempts: %v", maxRetries, err) + logger.Fatalf( + "FATAL: Failed to connect to Kafka after %d attempts: %v", + maxRetries, + err, + ) } func ensureTopicsExist(logger *log.Logger) { @@ -534,81 +799,12 @@ func ensureTopicsExist(logger *log.Logger) { }) if err != nil { - logger.Printf(" WARNING: Topic creation error (may already exist): %v", err) + logger.Printf( + "WARNING: Topic creation error (may already exist): %v", + err, + ) } else { - logger.Printf(" SUCCESS: Topic created successfully") - } - } -} - -func inspectKafkaSetup(logger *log.Logger) { - logger.Printf("Connecting to Kafka at %s to inspect configuration...", kafkaHost) - - conn, err := kafka.Dial("tcp", kafkaHost) - if err != nil { - logger.Printf("ERROR: Failed to connect to Kafka for inspection: %v", err) - return - } - defer conn.Close() - - logger.Print("Reading Kafka partitions...") - partitions, err := conn.ReadPartitions() - if err != nil { - logger.Printf("ERROR: Failed to read Kafka partitions: %v", err) - return - } - - logger.Print("=== Available Kafka topics ===") - topics := make(map[string]bool) - for _, p := range partitions { - topics[p.Topic] = true - } - - if len(topics) == 0 { - logger.Print("WARNING: No topics found in Kafka!") - } else { - for topic := range topics { - logger.Printf(" - %s", topic) - } - } - - requiredTopics := []string{ - connectionTestTopicName, - requestRedeemTopicName, - responseRedeemTopicName, - requestIssuanceTopicName, - responseIssuanceTopicName, - } - - logger.Print("=== Checking required topics ===") - for _, topic := range requiredTopics { - if topics[topic] { - logger.Printf(" SUCCESS: Topic %s - Found", topic) - } else { - logger.Printf(" ERROR: Topic %s - MISSING", topic) - } - } -} - -func checkNetworkConnectivity(logger *log.Logger) { - hosts := []string{ - kafkaHost, - "kafka:9092", - "localhost:9092", - "127.0.0.1:9092", - } - - logger.Print("=== Testing Kafka connectivity with different host configurations ===") - - for _, host := range hosts { - logger.Printf("Trying to connect to: %s", host) - conn, err := kafka.Dial("tcp", host) - - if err != nil { - logger.Printf(" FAILED: %s - Error: %v", host, err) - } else { - logger.Printf(" SUCCESS: %s - Connected successfully!", host) - conn.Close() + logger.Printf("SUCCESS: Topic created successfully") } } } @@ -637,129 +833,52 @@ func initializeLocalStack(logger *log.Logger) { logger.Print("Setting up DynamoDB tables...") err = test.SetupDynamodbTables(svc) if err != nil { - logger.Fatalf("FATAL: Failed to initialize LocalStack DynamoDB tables: %v", err) + logger.Fatalf( + "FATAL: Failed to initialize LocalStack DynamoDB tables: %v", + err, + ) } logger.Print("SUCCESS: LocalStack DynamoDB setup completed successfully") } -func createTestIssuer(logger *log.Logger) { - issuerName := "TestIssuer-" + testIssuerSuffix.String() - logger.Printf("Creating test issuer '%s'...", issuerName) - - now := time.Now() - expires := now.Add(1 * time.Hour) - - request := issuerV3CreateRequest{ - Name: issuerName, - Cohort: 1, - MaxTokens: 500, - ExpiresAt: &expires, - ValidFrom: &now, - Duration: "PT1H30M", - Overlap: 1, - Buffer: 1, - } - - logger.Printf("Issuer configuration: MaxTokens=%d, ValidFrom=%s, ExpiresAt=%s, Duration=%s, Overlap=%d, Buffer=%d", - request.MaxTokens, now.Format(time.RFC3339), expires.Format(time.RFC3339), request.Duration, request.Overlap, request.Buffer) - +func createTestIssuer(t *testing.T, request issuerV3CreateRequest) { jsonData, err := json.Marshal(request) - if err != nil { - logger.Fatalf("FATAL: Failed to marshal issuer creation request: %v", err) - return - } + require.NoError(t, err, "FATAL: Failed to marshal issuer creation request") - logger.Print("Sending POST request to create issuer...") - req, err := http.NewRequest("POST", "http://cbp:2416/v3/issuer/", bytes.NewBuffer(jsonData)) - if err != nil { - logger.Fatalf("FATAL: Failed to create HTTP request: %v", err) - return - } + t.Log("Sending POST request to create issuer...") + req, err := http.NewRequest( + "POST", + "http://cbp:2416/v3/issuer/", + bytes.NewBuffer(jsonData), + ) + require.NoError(t, err, "FATAL: Failed to create HTTP request") req.Header.Set("Content-Type", "application/json") client := &http.Client{} resp, err := client.Do(req) - if err != nil { - logger.Fatalf("FATAL: Failed to make HTTP request: %v", err) - return - } + require.NoError(t, err, "FATAL: Failed to make HTTP request") defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) - if err != nil { - logger.Fatalf("FATAL: Failed to read response body: %v", err) - return - } + _, err = io.ReadAll(resp.Body) + require.NoError(t, err, "FATAL: Failed to read response body") if resp.StatusCode >= 200 && resp.StatusCode < 300 { - logger.Printf("SUCCESS: Issuer creation succeeded with status: %s", resp.Status) + t.Logf( + "SUCCESS: Issuer creation succeeded with status: %s", + resp.Status, + ) } else { - logger.Printf("WARNING: Issuer creation returned status: %s", resp.Status) - } - - if string(body) != "" { - logger.Printf("Response body: %s", string(body)) - } -} - -// Helper functions -func validateResponse(t *testing.T, response avroSchema.RedeemResultSet) { - t.Logf("Validating redemption result set with %d results...", len(response.Data)) - - for i, result := range response.Data { - t.Logf("Validating result %d/%d...", i+1, len(response.Data)) - - if result.Issuer_name == "" { - t.Logf("WARNING: Result %d is missing issuer_name", i+1) - } else { - t.Logf("Result %d has issuer_name: %s", i+1, result.Issuer_name) - } - assert.NotEmpty(t, result.Issuer_name, "Result should have an issuer_name") - - if result.Issuer_cohort < 0 { - t.Logf("WARNING: Result %d has invalid issuer_cohort: %d", i+1, result.Issuer_cohort) - } else { - t.Logf("Result %d has issuer_cohort: %d", i+1, result.Issuer_cohort) - } - assert.Greater(t, result.Issuer_cohort, int32(-1), "Issuer cohort should be a valid value") - - validStatuses := []avroSchema.RedeemResultStatus{ - avroSchema.RedeemResultStatusOk, - avroSchema.RedeemResultStatusDuplicate_redemption, - avroSchema.RedeemResultStatusUnverified, - avroSchema.RedeemResultStatusError, - avroSchema.RedeemResultStatusIdempotent_redemption, - } - - t.Logf("Result %d status: %s", i+1, result.Status) - - assert.Contains(t, validStatuses, result.Status, "Status should be a valid redemption status") - - if result.Associated_data == nil { - t.Logf("WARNING: Result %d is missing associated data", i+1) - } else { - t.Logf("Result %d has associated data present", i+1) - } - assert.NotNil(t, result.Associated_data, "Associated data should not be nil") - - if result.Status == avroSchema.RedeemResultStatusOk { - t.Logf("SUCCESS: Result %d redemption succeeded (status: OK)", i+1) - } else if result.Status == avroSchema.RedeemResultStatusDuplicate_redemption { - t.Logf("NOTE: Result %d is a duplicate redemption - this is expected when tokens are reused", i+1) - } else if result.Status == avroSchema.RedeemResultStatusIdempotent_redemption { - t.Logf("NOTE: Result %d is an idempotent redemption - this is expected for repeated requests", i+1) - } else { - t.Logf("WARNING: Result %d redemption status: %s (non-fatal, may be expected)", i+1, result.Status) - } + t.Logf("WARNING: Issuer creation returned status: %s", resp.Status) } } func issueTokensViaKafka( t *testing.T, - requestID string, testMetadata []byte, + requestID string, testID string, + issuer issuerV3CreateRequest, ) ( []*crypto.Token, []*crypto.BlindedToken, @@ -773,14 +892,15 @@ func issueTokensViaKafka( t.Log("Generating random tokens...") // Number of tokens must be divisible by the sum of Buffer and Overlap of the Issuer - for i := range 2 { - t.Logf("Generating token %d/2...", i+1) + tokenCount := issuer.Buffer + issuer.Overlap + for i := range tokenCount { + t.Logf("Generating token %d/%d...", i+1, tokenCount) token, err := crypto.RandomToken() require.NoError(t, err, "Failed to generate random token %d", i+1) tokens = append(tokens, token) - t.Logf("Blinding token %d/2...", i+1) + t.Logf("Blinding token %d/%d...", i+1, tokenCount) blindedToken := token.Blind() blindedTokens = append(blindedTokens, blindedToken) @@ -793,8 +913,8 @@ func issueTokensViaKafka( signingRequest1 := avroSchema.SigningRequest{ Associated_data: testMetadata, Blinded_tokens: marshaledBlindedTokens, - Issuer_type: "TestIssuer", - Issuer_cohort: 1, + Issuer_type: issuer.Name, + Issuer_cohort: issuer.Cohort, } signingRequestSet := &avroSchema.SigningRequestSet{ @@ -855,132 +975,12 @@ func issueTokensViaKafka( "Request ID in signing response should match the request") t.Logf("Received %d signing results", len(signingResultSet.Data)) - require.Len(t, signingResultSet.Data, 2, "Expected 2 signing results") + require.Len( + t, + signingResultSet.Data, + tokenCount, + "Expected signing result count to match requested token count", + ) return tokens, blindedTokens, signingResultSet } - -func doRedemptionHTTPTest(t *testing.T, issuerName string, which RedeemEndpoint, tokenInfos []tokenInfo) { - t.Logf("TESTING HTTP REDEMPTION VIA %s ENDPOINT", which) - t.Logf("Issuer: %s, Token count: %d", issuerName, len(tokenInfos)) - - client := &http.Client{Timeout: 30 * time.Second} - - // Get the current active key for logs - t.Log("Retrieving current active key from issuer...") - issuerReq, err := http.NewRequest("GET", fmt.Sprintf("http://cbp:2416/v3/issuer/%s", issuerName), nil) - if err != nil { - t.Logf("WARNING: Failed to create issuer request: %v", err) - } - - issuerResp, err := client.Do(issuerReq) - if err != nil { - t.Logf("WARNING: Failed to retrieve issuer info: %v", err) - } else { - defer issuerResp.Body.Close() - - var issuerInfo struct { - PublicKey string `json:"public_key"` - } - - err = json.NewDecoder(issuerResp.Body).Decode(&issuerInfo) - if err != nil { - t.Logf("WARNING: Failed to decode issuer response: %v", err) - } else { - t.Logf("Current active key: %s", issuerInfo.PublicKey) - } - } - - t.Log("Attempting to redeem each token...") - succeeded := 0 - for i, ti := range tokenInfos { - t.Logf("Processing token %d/%d (signed with key: %s)...", - i+1, len(tokenInfos), ti.SignedKey) - - payload := "test" - signature, err := ti.UnblindedToken.DeriveVerificationKey().Sign(payload) - if err != nil { - t.Logf("WARNING: Failed to sign payload for token %d: %v", i+1, err) - continue - } - - redeemRequest := blindedTokenRedeemRequest{ - Payload: payload, - TokenPreimage: ti.UnblindedToken.Preimage(), - Signature: signature, - } - - jsonData, err := json.Marshal(redeemRequest) - if err != nil { - t.Logf("WARNING: Failed to marshal redemption request for token %d: %v", i+1, err) - continue - } - - url := fmt.Sprintf("http://cbp:2416/%s/blindedToken/%s/redemption/", which, issuerName) - t.Logf("Sending redemption request to: %s", url) - - req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData)) - if err != nil { - t.Logf("WARNING: Failed to create HTTP request for token %d: %v", i+1, err) - continue - } - - req.Header.Set("Content-Type", "application/json") - resp, err := client.Do(req) - if err != nil { - t.Logf("WARNING: Failed to make HTTP request for token %d: %v", i+1, err) - continue - } - - body, _ := io.ReadAll(resp.Body) - resp.Body.Close() - - if resp.StatusCode == http.StatusOK { - t.Logf("SUCCESS: Token #%d successfully redeemed (status: %s)", i+1, resp.Status) - succeeded++ - - // Test duplicate detection - t.Logf("Testing duplicate detection for token #%d...", i+1) - - req2, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData)) - if err != nil { - t.Logf("WARNING: Failed to create duplicate HTTP request: %v", err) - continue - } - - req2.Header.Set("Content-Type", "application/json") - resp2, err := client.Do(req2) - if err != nil { - t.Logf("WARNING: Failed to make duplicate HTTP request: %v", err) - continue - } - - defer resp2.Body.Close() - - if resp2.StatusCode == http.StatusConflict { - t.Logf("SUCCESS: Duplicate correctly detected (status: %s)", resp2.Status) - } else { - t.Logf("ERROR: Duplicate NOT correctly detected (expected 409 Conflict, got %d: %s)", - resp2.StatusCode, resp2.Status) - } - - require.Equal(t, http.StatusConflict, resp2.StatusCode, - "Duplicate redemption should be blocked with 409 Conflict") - } else { - // Some failures are expected, especially for tokens with future keys - bodyExcerpt := string(body) - if len(bodyExcerpt) > 100 { - bodyExcerpt = bodyExcerpt[:100] + "..." - } - t.Logf("NOTE: Token #%d failed to redeem (status: %s): %s", - i+1, resp.Status, bodyExcerpt) - t.Logf("This may be expected if the token's key is not currently active") - } - } - - t.Logf("Successfully redeemed %d/%d tokens", succeeded, len(tokenInfos)) - require.GreaterOrEqualf(t, succeeded, 1, - "At least one token must be redeemable! Zero redemptions indicates a critical problem.") - - t.Logf("HTTP REDEMPTION VIA %s ENDPOINT TEST COMPLETED", which) -} From 15ec90f3131ab8b22cd44d9022afa0f0b5f1ac86 Mon Sep 17 00:00:00 2001 From: Jackson Date: Fri, 22 Aug 2025 23:45:04 -0400 Subject: [PATCH 9/9] Enable specifying an integration test --- Makefile | 9 ++++++++- README.md | 5 +++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index ca1b9c09..3c28007e 100644 --- a/Makefile +++ b/Makefile @@ -59,7 +59,14 @@ integration-test: integration-test-clean @$(INTEGRATION_COMPOSE) --profile test build test-runner @echo "๐Ÿงช Running integration tests..." - @$(INTEGRATION_COMPOSE) --profile test run --rm test-runner || (echo "โŒ Tests failed!"; $(MAKE) integration-test-clean; exit 1) + @TEST_NAME="$${TEST_NAME:-}" && \ + if [ -n "$$TEST_NAME" ]; then \ + echo "Running specific test: $$TEST_NAME"; \ + $(INTEGRATION_COMPOSE) --profile test run --rm test-runner go test -v -tags=integration ./... -run=$$TEST_NAME || (echo "โŒ Tests failed!"; $(MAKE) integration-test-clean; exit 1); \ + else \ + echo "Running all tests"; \ + $(INTEGRATION_COMPOSE) --profile test run --rm test-runner || (echo "โŒ Tests failed!"; $(MAKE) integration-test-clean; exit 1); \ + fi @echo "๐Ÿงน Cleaning up..." @$(MAKE) integration-test-clean diff --git a/README.md b/README.md index ce1195c9..6abcd952 100644 --- a/README.md +++ b/README.md @@ -50,9 +50,10 @@ The integration tests: To run the integration tests, simply use: ```bash +# run all integration tests make integration-test -# or -make docker-integration-test +# or run a specific integration test +make integration-test TEST_NAME=TestTokenIssuanceViaKafkaAndRedeemViaHTTPFlow ``` This command will: