diff --git a/.github/workflows/ci_release.yml b/.github/workflows/ci_release.yml index 404ac6ce..0be14072 100644 --- a/.github/workflows/ci_release.yml +++ b/.github/workflows/ci_release.yml @@ -1,10 +1,9 @@ name: Release on: push: - # Nightly schedule pending full e2e testing with other repos - #schedule: - # Run daily at 1:15am - #- cron: "15 1 * * *" + branches: + - main + pull_request: workflow_dispatch: # Inputs the workflow accepts. inputs: @@ -36,32 +35,23 @@ jobs: run: go install gitlab.com/NebulousLabs/analyze@latest - name: Install golangci-lint run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.45.0 + - name: Update Dependecies and Clean Up + run: make deps - name: Lint run: make lint - name: Run unit tests run: make test - - # Check if there were any changes since the last tag if this is not a push - # event - changes: - needs: [hadolint, test] - runs-on: ubuntu-latest - outputs: - updates: ${{steps.changes.outputs.any == 'true'}} - if: ${{ github.event_name != 'push' }} - steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 # Required due to the way Git works, without it this action won't be able to find any or the correct tags - - uses: SkynetLabs/.github/.github/actions/changes-since-last-tag@master + - name: Start Mongo Container + run: make start-mongo + - name: Run Long tests + run: make test-long-ci # Make a release if - # - there were changes and this is a scheduled job # - This is a manually trigger job, i.e. workflow_dispatch release: - needs: changes + needs: [hadolint, test] runs-on: ubuntu-latest - if: ${{ (needs.changes.outputs.updates == 'true' && github.event_name == 'schedule') || github.event_name == 'workflow_dispatch' }} + if: ${{ github.event_name == 'workflow_dispatch' }} outputs: new_version: ${{ steps.version.outputs.new-version }} steps: diff --git a/Makefile b/Makefile index 2d30bd68..13e332d4 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,10 @@ racevars= history_size=3 halt_on_error=1 atexit_sleep_ms=2000 # all will build and install release binaries all: release +deps: + go mod download + go mod tidy + # clean removes all directories that get automatically created during # development. # Also ensures that any docker containers are gone in the event of an error on a @@ -25,14 +29,26 @@ else - DEL /F /Q cover output endif +run = . + # count says how many times to run the tests. count = 1 # pkgs changes which packages the makefile calls operate on. run changes which # tests are run during testing. -pkgs = ./ ./api ./database ./email ./hash ./jwt ./lib ./metafetcher ./skynet - -# integration-pkgs defines the packages which contain integration tests -integration-pkgs = ./test ./test/api ./test/database ./test/email +pkgs = \ + ./ \ + ./api \ + ./database \ + ./email \ + ./hash \ + ./jwt \ + ./lib \ + ./metafetcher \ + ./skynet \ + ./test \ + ./test/api \ + ./test/database \ + ./test/email # fmt calls go fmt on all packages. fmt: @@ -68,16 +84,8 @@ ifneq ("$(OS)","Windows_NT") go mod tidy endif -# Credentials and port we are going to use for our test MongoDB instance. -MONGO_USER=admin -MONGO_PASSWORD=aO4tV5tC1oU3oQ7u -MONGO_PORT=17017 - -# call_mongo is a helper function that executes a query in an `eval` call to the -# test mongo instance. -define call_mongo - docker exec skynet-accounts-mongo-test-db mongo -u $(MONGO_USER) -p $(MONGO_PASSWORD) --port $(MONGO_PORT) --eval $(1) -endef +# Define docker container name our test MongoDB instance. +MONGO_TEST_CONTAINER_NAME=blocker-mongo-test-db # start-mongo starts a local mongoDB container with no persistence. # We first prepare for the start of the container by making sure the test @@ -86,29 +94,10 @@ endef # single node replica set. All the output is discarded because it's noisy and # if it causes a failure we'll immediately know where it is even without it. start-mongo: - -docker stop skynet-accounts-mongo-test-db 1>/dev/null 2>&1 - -docker rm skynet-accounts-mongo-test-db 1>/dev/null 2>&1 - chmod 400 $(shell pwd)/test/fixtures/mongo_keyfile - docker run \ - --rm \ - --detach \ - --name skynet-accounts-mongo-test-db \ - -p $(MONGO_PORT):$(MONGO_PORT) \ - -e MONGO_INITDB_ROOT_USERNAME=$(MONGO_USER) \ - -e MONGO_INITDB_ROOT_PASSWORD=$(MONGO_PASSWORD) \ - -v $(shell pwd)/test/fixtures/mongo_keyfile:/data/mgkey \ - mongo:4.4.1 mongod --port=$(MONGO_PORT) --replSet=skynet --keyFile=/data/mgkey 1>/dev/null 2>&1 - # wait for mongo to start before we try to configure it - status=1 ; while [[ $$status -gt 0 ]]; do \ - sleep 1 ; \ - $(call call_mongo,"") 1>/dev/null 2>&1 ; \ - status=$$? ; \ - done - # Initialise a single node replica set. - $(call call_mongo,"rs.initiate({_id: \"skynet\", members: [{ _id: 0, host: \"localhost:$(MONGO_PORT)\" }]})") 1>/dev/null 2>&1 + ./test/setup.sh $(MONGO_TEST_CONTAINER_NAME) stop-mongo: - -docker stop skynet-accounts-mongo-test-db + -docker stop $(MONGO_TEST_CONTAINER_NAME) # debug builds and installs debug binaries. This will also install the utils. debug: @@ -141,19 +130,16 @@ bench: fmt test: go test -short -tags='debug testing netgo' -timeout=5s $(pkgs) -run=. -count=$(count) -test-long: lint lint-ci start-mongo +test-long: lint lint-ci start-mongo test-long-ci stop-mongo + +test-long-ci: @mkdir -p cover - GORACE='$(racevars)' go test -race --coverprofile='./cover/cover.out' -v -failfast -tags='testing debug netgo' -timeout=60s $(pkgs) -run=$(run) -count=$(count) - GORACE='$(racevars)' go test -race -v -tags='testing debug netgo' -timeout=600s $(integration-pkgs) -run=$(run) -count=$(count) - -make stop-mongo - -# test-single allows us to run a single integration test. -# Make sure to start MongoDB yourself! -# Example: make test-single RUN=TestHandlers -test-single: export COOKIE_HASH_KEY="7eb32cfab5014d14394648dae1cf4e606727eee2267f6a50213cd842e61c5bce" -test-single: export COOKIE_ENC_KEY="65d31d12b80fc57df16d84c02a9bb62e2bc3b633388b05e49ef8abfdf0d35cf3" -test-single: - GORACE='$(racevars)' go test -race -v -tags='testing debug netgo' -timeout=300s $(integration-pkgs) -run=$(run) -count=$(count) + GORACE='$(racevars)' go test -race --coverprofile='./cover/cover.out' -v -failfast -tags='testing debug netgo' -timeout=600s $(pkgs) -run=$(run) -count=$(count) + +# Cookie vars +# TODO: Are these used? +COOKIE_HASH_KEY="7eb32cfab5014d14394648dae1cf4e606727eee2267f6a50213cd842e61c5bce" +COOKIE_ENC_KEY="65d31d12b80fc57df16d84c02a9bb62e2bc3b633388b05e49ef8abfdf0d35cf3" # docker-generate is a docker command for env var generation # @@ -167,4 +153,4 @@ docker-generate: clean sleep 3 @docker stop genenv || true && docker rm --force genenv -.PHONY: all fmt install release clean check test test-long test-single start-mongo stop-mongo docker-generate +.PHONY: all deps fmt install release clean check test test-long test-long-ci start-mongo stop-mongo docker-generate diff --git a/api/handlers_test.go b/api/handlers_test.go index 5dfe8070..f12e28e2 100644 --- a/api/handlers_test.go +++ b/api/handlers_test.go @@ -132,7 +132,7 @@ func TestUserLimitsGetFromTier(t *testing.T) { } }() // The call that we expect to log a critical. - _ = userLimitsGetFromTier("", math.MaxInt, false, true) + _ = userLimitsGetFromTier("", math.MaxInt64, false, true) return }() if err != nil { diff --git a/test/api/api_test.go b/test/api/api_test.go index 3e359a25..414bfdff 100644 --- a/test/api/api_test.go +++ b/test/api/api_test.go @@ -23,6 +23,9 @@ import ( // TestWithDBSession ensures that database transactions are started, committed, // and aborted properly. func TestWithDBSession(t *testing.T) { + if testing.Short() { + t.SkipNow() + } ctx := context.Background() dbName := test.DBNameForTest(t.Name()) db, err := test.NewDatabase(ctx, dbName) @@ -137,6 +140,9 @@ func TestWithDBSession(t *testing.T) { // TestUserTierCache ensures out tier cache works as expected. func TestUserTierCache(t *testing.T) { + if testing.Short() { + t.SkipNow() + } dbName := test.DBNameForTest(t.Name()) at, err := test.NewAccountsTester(dbName) if err != nil { diff --git a/test/api/handlers_test.go b/test/api/handlers_test.go index 9a7a4f6e..1d38469c 100644 --- a/test/api/handlers_test.go +++ b/test/api/handlers_test.go @@ -41,6 +41,9 @@ type subtest struct { // TestHandlers is a meta test that sets up a test instance of accounts and runs // a suite of tests that ensure all handlers behave as expected. func TestHandlers(t *testing.T) { + if testing.Short() { + t.SkipNow() + } dbName := test.DBNameForTest(t.Name()) at, err := test.NewAccountsTester(dbName) if err != nil { diff --git a/test/api/stripe_test.go b/test/api/stripe_test.go index 9ec52465..9530baef 100644 --- a/test/api/stripe_test.go +++ b/test/api/stripe_test.go @@ -27,7 +27,10 @@ func TestStripe(t *testing.T) { // We only run tests against Stripe's test infrastructure. For that we need // a test API key. key, ok := os.LookupEnv("STRIPE_API_KEY") - if !ok || !strings.HasPrefix(key, "sk_test_") { + if !ok { + t.Skipf("Skipping %s. If you want to run this test, update STRIPE_API_KEY to hold a test API key.\n", t.Name()) + } + if !strings.HasPrefix(key, "sk_test_") { t.Skipf("Skipping %s. If you want to run this test, update STRIPE_API_KEY to hold a test API key.\n"+ "Expected STRIPE_API_KEY that starts with '%s', got '%s'", t.Name(), "sk_test_", key[:8]) } diff --git a/test/database/apikeys_test.go b/test/database/apikeys_test.go index 3060d361..d6117223 100644 --- a/test/database/apikeys_test.go +++ b/test/database/apikeys_test.go @@ -10,6 +10,9 @@ import ( // TestAPIKeys ensures the DB operations with API keys work as expected. func TestAPIKeys(t *testing.T) { + if testing.Short() { + t.SkipNow() + } ctx := context.Background() dbName := test.DBNameForTest(t.Name()) db, err := test.NewDatabase(ctx, dbName) diff --git a/test/database/challenge_test.go b/test/database/challenge_test.go index 83557636..ec27888c 100644 --- a/test/database/challenge_test.go +++ b/test/database/challenge_test.go @@ -18,6 +18,9 @@ import ( // TestValidateChallengeResponse is a unit test using a database. func TestValidateChallengeResponse(t *testing.T) { + if testing.Short() { + t.SkipNow() + } ctx := context.Background() dbName := test.DBNameForTest(t.Name()) db, err := test.NewDatabase(ctx, dbName) @@ -126,6 +129,9 @@ func TestValidateChallengeResponse(t *testing.T) { // TestUnconfirmedUserUpdate ensures the entire flow for unconfirmed user // updates works as expected. func TestUnconfirmedUserUpdate(t *testing.T) { + if testing.Short() { + t.SkipNow() + } ctx := context.Background() dbName := test.DBNameForTest(t.Name()) db, err := test.NewDatabase(ctx, dbName) diff --git a/test/database/configuration_test.go b/test/database/configuration_test.go index 45de7439..1a8d8941 100644 --- a/test/database/configuration_test.go +++ b/test/database/configuration_test.go @@ -12,6 +12,9 @@ import ( // TestConfiguration ensures we can correctly read and write from/to the // configuration DB table. func TestConfiguration(t *testing.T) { + if testing.Short() { + t.SkipNow() + } ctx := context.Background() dbName := test.DBNameForTest(t.Name()) db, err := test.NewDatabase(ctx, dbName) diff --git a/test/database/upload_test.go b/test/database/upload_test.go index 76390458..75c8e274 100644 --- a/test/database/upload_test.go +++ b/test/database/upload_test.go @@ -13,6 +13,9 @@ import ( // TestUploadsByUser ensures UploadsByUser returns the correct uploads, // in the correct order, with the correct sized and so on. func TestUploadsByUser(t *testing.T) { + if testing.Short() { + t.SkipNow() + } ctx := context.Background() dbName := test.DBNameForTest(t.Name()) db, err := test.NewDatabase(ctx, dbName) @@ -145,6 +148,9 @@ func TestUploadsByUser(t *testing.T) { // TestUnpinUploads ensures UnpinUploads unpins all uploads of this // skylink by this user without affecting uploads by other users. func TestUnpinUploads(t *testing.T) { + if testing.Short() { + t.SkipNow() + } ctx := context.Background() dbName := test.DBNameForTest(t.Name()) db, err := test.NewDatabase(ctx, dbName) @@ -255,6 +261,9 @@ func TestUnpinUploads(t *testing.T) { // TestUploadCreateAnon ensures that UploadCreate can create anonymous uploads. func TestUploadCreateAnon(t *testing.T) { + if testing.Short() { + t.SkipNow() + } ctx := context.Background() dbName := test.DBNameForTest(t.Name()) db, err := database.NewCustomDB(ctx, dbName, test.DBTestCredentials(), nil) diff --git a/test/database/user_test.go b/test/database/user_test.go index 921607ba..3b06b7df 100644 --- a/test/database/user_test.go +++ b/test/database/user_test.go @@ -21,6 +21,9 @@ import ( // TestUserByEmail ensures UserByEmail works as expected. // This method also tests UserCreate. func TestUserByEmail(t *testing.T) { + if testing.Short() { + t.SkipNow() + } ctx := context.Background() dbName := test.DBNameForTest(t.Name()) db, err := test.NewDatabase(ctx, dbName) @@ -64,6 +67,9 @@ func TestUserByEmail(t *testing.T) { // TestUserByID ensures UserByID works as expected. func TestUserByID(t *testing.T) { + if testing.Short() { + t.SkipNow() + } ctx := context.Background() dbName := test.DBNameForTest(t.Name()) db, err := test.NewDatabase(ctx, dbName) @@ -106,6 +112,9 @@ func TestUserByID(t *testing.T) { // TestUserByPubKey makes sure UserByPubKey functions correctly, both with a // single and multiple pubkeys attached to a user. func TestUserByPubKey(t *testing.T) { + if testing.Short() { + t.SkipNow() + } ctx := context.Background() name := test.DBNameForTest(t.Name()) db, err := test.NewDatabase(ctx, name) @@ -157,6 +166,9 @@ func TestUserByPubKey(t *testing.T) { // TestUserByStripeID ensures UserByStripeID works as expected. // This method also tests UserCreate and UserSetStripeID. func TestUserByStripeID(t *testing.T) { + if testing.Short() { + t.SkipNow() + } ctx := context.Background() dbName := test.DBNameForTest(t.Name()) db, err := test.NewDatabase(ctx, dbName) @@ -201,6 +213,9 @@ func TestUserByStripeID(t *testing.T) { // TestUserBySub ensures UserBySub works as expected. // This method also tests UserCreate. func TestUserBySub(t *testing.T) { + if testing.Short() { + t.SkipNow() + } ctx := context.Background() dbName := test.DBNameForTest(t.Name()) db, err := test.NewDatabase(ctx, dbName) @@ -243,6 +258,9 @@ func TestUserBySub(t *testing.T) { // TestUserConfirmEmail ensures that email confirmation works as expected, // including resecting the expiration of tokens. func TestUserConfirmEmail(t *testing.T) { + if testing.Short() { + t.SkipNow() + } ctx := context.Background() dbName := test.DBNameForTest(t.Name()) db, err := test.NewDatabase(ctx, dbName) @@ -281,6 +299,9 @@ func TestUserConfirmEmail(t *testing.T) { // TestUserCreate ensures UserCreate works as expected. func TestUserCreate(t *testing.T) { + if testing.Short() { + t.SkipNow() + } ctx := context.Background() dbName := test.DBNameForTest(t.Name()) db, err := test.NewDatabase(ctx, dbName) @@ -333,6 +354,9 @@ func TestUserCreate(t *testing.T) { // TestUserCreateEmailConfirmation tests UserCreateEmailConfirmation. func TestUserCreateEmailConfirmation(t *testing.T) { + if testing.Short() { + t.SkipNow() + } ctx := context.Background() dbName := test.DBNameForTest(t.Name()) db, err := database.NewCustomDB(ctx, dbName, test.DBTestCredentials(), nil) @@ -364,6 +388,9 @@ func TestUserCreateEmailConfirmation(t *testing.T) { // TestUserDelete ensures UserDelete works as expected. func TestUserDelete(t *testing.T) { + if testing.Short() { + t.SkipNow() + } ctx := context.Background() dbName := test.DBNameForTest(t.Name()) db, err := test.NewDatabase(ctx, dbName) @@ -399,6 +426,9 @@ func TestUserDelete(t *testing.T) { // TestUserSave ensures that UserSave works as expected. func TestUserSave(t *testing.T) { + if testing.Short() { + t.SkipNow() + } ctx := context.Background() dbName := test.DBNameForTest(t.Name()) db, err := test.NewDatabase(ctx, dbName) @@ -445,6 +475,9 @@ func TestUserSave(t *testing.T) { // TestUserSetStripeID ensures that UserSetStripeID works as expected. func TestUserSetStripeID(t *testing.T) { + if testing.Short() { + t.SkipNow() + } ctx := context.Background() dbName := test.DBNameForTest(t.Name()) db, err := test.NewDatabase(ctx, dbName) @@ -479,6 +512,9 @@ func TestUserSetStripeID(t *testing.T) { // TestUserPubKey tests UserPubKeyAdd and UserPubKeyRemove. func TestUserPubKey(t *testing.T) { + if testing.Short() { + t.SkipNow() + } ctx := context.Background() dbName := test.DBNameForTest(t.Name()) db, err := database.NewCustomDB(ctx, dbName, test.DBTestCredentials(), nil) @@ -563,6 +599,9 @@ func TestUserPubKey(t *testing.T) { // TestUserSetTier ensures that UserSetTier works as expected. func TestUserSetTier(t *testing.T) { + if testing.Short() { + t.SkipNow() + } ctx := context.Background() dbName := test.DBNameForTest(t.Name()) db, err := test.NewDatabase(ctx, dbName) @@ -595,6 +634,9 @@ func TestUserSetTier(t *testing.T) { // TestUserStats ensures we report accurate statistics for users. func TestUserStats(t *testing.T) { + if testing.Short() { + t.SkipNow() + } ctx := context.Background() dbName := test.DBNameForTest(t.Name()) db, err := test.NewDatabase(ctx, dbName) diff --git a/test/email/sender_test.go b/test/email/sender_test.go index 8a501512..ca0b890a 100644 --- a/test/email/sender_test.go +++ b/test/email/sender_test.go @@ -21,6 +21,9 @@ import ( // TestSender goes through the standard Sender workflow and ensures that it // works correctly. func TestSender(t *testing.T) { + if testing.Short() { + t.SkipNow() + } ctx, cancel := context.WithCancel(context.Background()) defer cancel() dbName := test.DBNameForTest(t.Name()) @@ -88,6 +91,9 @@ func TestSender(t *testing.T) { // servers is sent exactly once. The test has several "servers" continuously // creating and "sending" emails. func TestContendingSenders(t *testing.T) { + if testing.Short() { + t.SkipNow() + } ctx := context.Background() dbName := test.DBNameForTest(t.Name()) db, err := test.NewDatabase(ctx, dbName) diff --git a/test/setup.sh b/test/setup.sh new file mode 100755 index 00000000..90b24dbe --- /dev/null +++ b/test/setup.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +MONGO_USER=admin +MONGO_PASSWORD=aO4tV5tC1oU3oQ7u +MONGO_PORT=37017 +MONGO_TEST_CONTAINER_NAME=$1 +MONGO_REPLSET=skynet + +# Stop and remove any existing docker container +printf '\n==STOPPING AND REMOVING DOCKER CONTAINERS==\n' +docker stop $MONGO_TEST_CONTAINER_NAME 1>/dev/null 2>&1 +docker rm $MONGO_TEST_CONTAINER_NAME 1>/dev/null 2>&1 + +# Start docker container +printf '\n==STARTING DOCKER CONTAINER==\n' +docker run \ + --rm \ + --detach \ + --name $MONGO_TEST_CONTAINER_NAME \ + -p $MONGO_PORT:$MONGO_PORT \ + -e MONGO_INITDB_ROOT_USERNAME=$MONGO_USER \ + -e MONGO_INITDB_ROOT_PASSWORD=$MONGO_PASSWORD \ + mongo:4.4.1 mongod --port=$MONGO_PORT --replSet=$MONGO_REPLSET 1>/dev/null 2>&1 + +# wait for mongo to start before we try to configure it +printf '\n==WAIT FOR MONGO TO BE ACCESSIBLE==\n' +status=1 +while [ $status -gt 0 ]; do + sleep 1 + # Execute command and save the stderr + err="$(docker exec $MONGO_TEST_CONTAINER_NAME mongo -u $MONGO_USER -p $MONGO_PASSWORD --port $MONGO_PORT 2>&1)" + # Grab the status code + status=$? + # Log for debugging + echo $err + echo $status +done + +# Initialise a single node replica set. +printf '\n==INITIALIZE REPLICASET==\n' +# Execute command and save the stderr +docker exec $MONGO_TEST_CONTAINER_NAME mongo -u $MONGO_USER -p $MONGO_PASSWORD --port $MONGO_PORT --eval "rs.initiate({_id: \"$MONGO_REPLSET\", members: [{ _id: 0, host: \"localhost:$MONGO_PORT\" }]})" diff --git a/test/utils.go b/test/utils.go index 9adea979..4f8946ad 100644 --- a/test/utils.go +++ b/test/utils.go @@ -74,7 +74,7 @@ func DBTestCredentials() database.DBCredentials { User: "admin", Password: "aO4tV5tC1oU3oQ7u", Host: "localhost", - Port: "17017", + Port: "37017", } } diff --git a/types/email_test.go b/types/email_test.go index 4d666e95..31933ff8 100644 --- a/types/email_test.go +++ b/types/email_test.go @@ -12,7 +12,7 @@ import ( func TestEmail_String(t *testing.T) { s := "mIxEdCaSeStRiNg" e := Email(s) - if e.String() != strings.ToLower(s) { + if !strings.EqualFold(e.String(), s) { t.Fatalf("Expected '%s', got '%s'", strings.ToLower(s), e) } } @@ -44,7 +44,7 @@ func TestEmail_UnmarshalJSON(t *testing.T) { t.Fatal(err) } // We expect the unmarshalled email to be lowercase only. - if string(e) != strings.ToLower(string(b[1:len(b)-1])) { + if !strings.EqualFold(string(e), string(b[1:len(b)-1])) { t.Fatalf("Expected to get a lowercase version of '%s', i.e. '%s' but got '%s'", e, strings.ToLower(string(e)), e) } }