diff --git a/.github/docker-compose.yml b/.github/docker-compose.yml new file mode 100644 index 0000000..f06f00a --- /dev/null +++ b/.github/docker-compose.yml @@ -0,0 +1,8 @@ +version: '3' + +services: + dynamodb: + image: amazon/dynamodb-local:latest + ports: + - "8880:8000" + command: "-jar DynamoDBLocal.jar -sharedDb -inMemory" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a19c62d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,25 @@ +name: CI + +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: 'stable' + - name: Start DynamoDB Local + run: docker compose -f '.github/docker-compose.yml' up -d + - name: Test + run: go test -v -race -cover -coverpkg=./... ./... + env: + DYNAMO_TEST_ENDPOINT: 'http://localhost:8880' + DYNAMO_TEST_REGION: local + DYNAMO_TEST_TABLE: TestDB + AWS_ACCESS_KEY_ID: dummy + AWS_SECRET_ACCESS_KEY: dummy + AWS_REGION: local diff --git a/db_test.go b/db_test.go index deddd62..3082e02 100644 --- a/db_test.go +++ b/db_test.go @@ -1,11 +1,16 @@ package dynamo import ( + "errors" + "log" "os" + "strconv" + "strings" "testing" "time" "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" ) @@ -19,10 +24,19 @@ var dummyCreds = credentials.NewStaticCredentials("dummy", "dummy", "") const offlineSkipMsg = "DYNAMO_TEST_REGION not set" -func init() { - // os.Setenv("DYNAMO_TEST_REGION", "us-west-2") +// widget is the data structure used for integration tests +type widget struct { + UserID int `dynamo:",hash"` + Time time.Time `dynamo:",range" index:"Msg-Time-index,range"` + Msg string `index:"Msg-Time-index,hash"` + Count int + Meta map[string]string + StrPtr *string `dynamo:",allowempty"` +} + +func TestMain(m *testing.M) { + var endpoint *string if region := os.Getenv("DYNAMO_TEST_REGION"); region != "" { - var endpoint *string if dte := os.Getenv("DYNAMO_TEST_ENDPOINT"); dte != "" { endpoint = aws.String(dte) } @@ -33,18 +47,42 @@ func init() { }) } if table := os.Getenv("DYNAMO_TEST_TABLE"); table != "" { + // Test-% --> Test-1707708680863 + table = strings.ReplaceAll(table, "%", strconv.FormatInt(time.Now().UnixMilli(), 10)) testTable = table } + + var created []Table + if testDB != nil { + table := testDB.Table(testTable) + log.Println("Checking test table:", testTable) + _, err := table.Describe().Run() + switch { + case isTableNotExistsErr(err) && endpoint != nil: + log.Println("Creating test table:", testTable) + if err := testDB.CreateTable(testTable, widget{}).Run(); err != nil { + panic(err) + } + created = append(created, testDB.Table(testTable)) + case err != nil: + panic(err) + } + } + + code := m.Run() + defer os.Exit(code) + + for _, table := range created { + log.Println("Deleting test table:", table.Name()) + if err := table.DeleteTable().Run(); err != nil { + log.Println("Error deleting test table:", table.Name(), err) + } + } } -// widget is the data structure used for integration tests -type widget struct { - UserID int `dynamo:",hash"` - Time time.Time `dynamo:",range"` - Msg string - Count int - Meta map[string]string - StrPtr *string `dynamo:",allowempty"` +func isTableNotExistsErr(err error) bool { + var ae awserr.Error + return errors.As(err, &ae) && ae.Code() == "ResourceNotFoundException" } func TestListTables(t *testing.T) {