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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/ci-database-test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Database Integration Test
on:
pull_request:
push:
branches:
- 'main'

jobs:
tests:
name: "Database Integration testing"
runs-on: ubuntu-latest
permissions:
# Give the default GITHUB_TOKEN write permission to commit and push the
# added or changed files to the repository.
contents: write
steps:
- uses: actions/checkout@v3
- name: "Set up database environment"
run: docker compose -f docker/dev-all.yaml up -d
- name: "Test against cassandra"
run: cd server/ && make ci-cassandra-integ-test
- name: "Test against mongodb"
run: cd server/ && make ci-mongodb-integ-test
- name: "Test against mysql"
run: cd server/ && make ci-mysql-integ-test
- name: "Test against postgresql"
run: cd server/ && make ci-postgresql-integ-test
- name: "Test against dynamodb"
run: cd server/ && make ci-dynamodb-integ-test
- name: Dump docker logs
if: always()
uses: jwalton/gh-docker-logs@v2
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ api-code-gen-server: #generate/refresh go server code for api.yaml, do this afte
#api-code-gen-py: #generate/refresh python apis
# rm -Rf ./pyapi/* ; true
# java -jar openapi-generator-cli-7.14.0.jar generate -i api.yaml -g python -o ./pyapi -p packageVersion=0.0.3 -p packageName=xcherryapi --git-user-id xcherryio --git-repo-id apis

40 changes: 39 additions & 1 deletion server/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,42 @@ help:
@echo " run - Build and run the server"
@echo " dev - Run with live reload (requires air)"
@echo " docker - Build Docker image"
@echo " help - Show this help message"
@echo " help - Show this help message"


ci-cassandra-integ-test:
$Q go test -v ./databases/cassandra -cover -coverprofile coverage.cassandra.out -coverpkg ./databases/...

ci-mongodb-integ-test:
$Q go test -v ./databases/mongodb -cover -coverprofile coverage.mongodb.out -coverpkg ./databases/...

ci-mysql-integ-test:
$Q go test -v ./databases/mysql -cover -coverprofile coverage.mysql.out -coverpkg ./databases/...

ci-postgresql-integ-test:
$Q go test -v ./databases/postgresql -cover -coverprofile coverage.postgresql.out -coverpkg ./databases/...

ci-dynamodb-integ-test:
$Q go test -v ./databases/dynamodb -cover -coverprofile coverage.dynamodb.out -coverpkg ./databases/...

integ-cassandra-test:
$Q go test -v ./databases/cassandra

integ-mongodb-test:
$Q go test -v ./databases/mongodb

integ-mysql-test:
$Q go test -v ./databases/mysql

integ-postgresql-test:
$Q go test -v ./databases/postgresql

integ-dynamodb-test:
$Q go test -v ./databases/dynamodb

integ-test-all:
$Q go test -v ./databases/cassandra
$Q go test -v ./databases/mongodb
$Q go test -v ./databases/mysql
$Q go test -v ./databases/postgresql
$Q go test -v ./databases/dynamodb
8 changes: 4 additions & 4 deletions server/databases/cassandra/cassandra_timer_store_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func (c *CassandraTimerStore) ClaimShardOwnership(
ctx context.Context, shardId int, ownerId string, metadata interface{},
) (shardVersion int64, retErr *databases.DbError) {
// Convert ZeroUUID to high/low format for shard records
zeroUuidHigh, zeroUuidLow, _ := databases.UuidToHighLow(databases.ZeroUUID)
zeroUuidHigh, zeroUuidLow := databases.UuidToHighLow(databases.ZeroUUID)

// Serialize metadata to JSON
var metadataJSON string
Expand Down Expand Up @@ -130,10 +130,10 @@ func (c *CassandraTimerStore) ClaimShardOwnership(

func (c *CassandraTimerStore) CreateTimer(ctx context.Context, shardId int, shardVersion int64, namespace string, timer *databases.DbTimer) (err *databases.DbError) {
// Convert the provided timer UUID to high/low format for predictable pagination
timerUuidHigh, timerUuidLow, _ := databases.UuidToHighLow(timer.TimerUuid)
timerUuidHigh, timerUuidLow := databases.UuidToHighLow(timer.TimerUuid)

// Convert ZeroUUID to high/low format for shard records
zeroUuidHigh, zeroUuidLow, _ := databases.UuidToHighLow(databases.ZeroUUID)
zeroUuidHigh, zeroUuidLow := databases.UuidToHighLow(databases.ZeroUUID)

// Serialize payload and retry policy to JSON
var payloadJSON, retryPolicyJSON string
Expand Down Expand Up @@ -210,7 +210,7 @@ func (c *CassandraTimerStore) CreateTimer(ctx context.Context, shardId int, shar

func (c *CassandraTimerStore) CreateTimerNoLock(ctx context.Context, shardId int, namespace string, timer *databases.DbTimer) (err *databases.DbError) {
// Convert the provided timer UUID to high/low format for predictable pagination
timerUuidHigh, timerUuidLow, _ := databases.UuidToHighLow(timer.TimerUuid)
timerUuidHigh, timerUuidLow := databases.UuidToHighLow(timer.TimerUuid)

// Serialize payload and retry policy to JSON
var payloadJSON, retryPolicyJSON string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,16 @@ package cassandra

import (
"context"
"crypto/md5"
"fmt"
"sync"
"testing"
"time"

"github.com/google/uuid"
"github.com/iworkflowio/durable-timer/databases"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// generateTimerUUID creates a stable UUID from timer namespace and ID for consistent upsert behavior
func generateTimerUUID(namespace, timerId string) uuid.UUID {
// Create a deterministic UUID based on namespace and timer ID
hash := md5.Sum([]byte(fmt.Sprintf("%s:%s", namespace, timerId)))
uuid, _ := uuid.FromBytes(hash[:])
return uuid
}

func TestCreateTimer_Basic(t *testing.T) {
store, cleanup := setupTestStore(t)
defer cleanup()
Expand All @@ -39,7 +29,7 @@ func TestCreateTimer_Basic(t *testing.T) {
// Create a timer
timer := &databases.DbTimer{
Id: "timer-1",
TimerUuid: generateTimerUUID(namespace, "timer-1"),
TimerUuid: databases.GenerateTimerUUID(namespace, "timer-1"),
Namespace: namespace,
ExecuteAt: time.Now().Add(5 * time.Minute),
CallbackUrl: "https://example.com/callback",
Expand Down Expand Up @@ -100,7 +90,7 @@ func TestCreateTimer_WithPayload(t *testing.T) {

timer := &databases.DbTimer{
Id: "timer-with-payload",
TimerUuid: generateTimerUUID(namespace, "timer-with-payload"),
TimerUuid: databases.GenerateTimerUUID(namespace, "timer-with-payload"),
Namespace: namespace,
ExecuteAt: time.Now().Add(10 * time.Minute),
CallbackUrl: "https://example.com/callback",
Expand Down Expand Up @@ -146,7 +136,7 @@ func TestCreateTimer_WithRetryPolicy(t *testing.T) {

timer := &databases.DbTimer{
Id: "timer-with-retry",
TimerUuid: generateTimerUUID(namespace, "timer-with-retry"),
TimerUuid: databases.GenerateTimerUUID(namespace, "timer-with-retry"),
Namespace: namespace,
ExecuteAt: time.Now().Add(15 * time.Minute),
CallbackUrl: "https://example.com/callback",
Expand Down Expand Up @@ -183,7 +173,7 @@ func TestCreateTimer_ShardVersionMismatch(t *testing.T) {

timer := &databases.DbTimer{
Id: "timer-version-mismatch",
TimerUuid: generateTimerUUID(namespace, "timer-version-mismatch"),
TimerUuid: databases.GenerateTimerUUID(namespace, "timer-version-mismatch"),
Namespace: namespace,
ExecuteAt: time.Now().Add(5 * time.Minute),
CallbackUrl: "https://example.com/callback",
Expand Down Expand Up @@ -232,7 +222,7 @@ func TestCreateTimer_ConcurrentCreation(t *testing.T) {
defer wg.Done()
timer := &databases.DbTimer{
Id: fmt.Sprintf("concurrent-timer-%d", idx),
TimerUuid: generateTimerUUID(namespace, fmt.Sprintf("concurrent-timer-%d", idx)),
TimerUuid: databases.GenerateTimerUUID(namespace, fmt.Sprintf("concurrent-timer-%d", idx)),
Namespace: namespace,
ExecuteAt: time.Now().Add(time.Duration(idx) * time.Minute),
CallbackUrl: fmt.Sprintf("https://example.com/callback/%d", idx),
Expand Down Expand Up @@ -277,7 +267,7 @@ func TestCreateTimerNoLock_Basic(t *testing.T) {
// Create a basic timer without needing shard ownership
timer := &databases.DbTimer{
Id: "timer-nolock-1",
TimerUuid: generateTimerUUID(namespace, "timer-nolock-1"),
TimerUuid: databases.GenerateTimerUUID(namespace, "timer-nolock-1"),
Namespace: namespace,
ExecuteAt: time.Now().Add(5 * time.Minute),
CallbackUrl: "https://example.com/callback",
Expand Down Expand Up @@ -334,7 +324,7 @@ func TestCreateTimerNoLock_WithPayload(t *testing.T) {

timer := &databases.DbTimer{
Id: "timer-nolock-payload",
TimerUuid: generateTimerUUID(namespace, "timer-nolock-payload"),
TimerUuid: databases.GenerateTimerUUID(namespace, "timer-nolock-payload"),
Namespace: namespace,
ExecuteAt: time.Now().Add(10 * time.Minute),
CallbackUrl: "https://example.com/nolock/callback",
Expand Down Expand Up @@ -376,7 +366,7 @@ func TestCreateTimerNoLock_WithRetryPolicy(t *testing.T) {

timer := &databases.DbTimer{
Id: "timer-nolock-retry",
TimerUuid: generateTimerUUID(namespace, "timer-nolock-retry"),
TimerUuid: databases.GenerateTimerUUID(namespace, "timer-nolock-retry"),
Namespace: namespace,
ExecuteAt: time.Now().Add(15 * time.Minute),
CallbackUrl: "https://example.com/nolock/retry",
Expand Down Expand Up @@ -410,7 +400,7 @@ func TestCreateTimerNoLock_NilPayloadAndRetryPolicy(t *testing.T) {
// Create timer with nil payload and retry policy
timer := &databases.DbTimer{
Id: "timer-nolock-nil-fields",
TimerUuid: generateTimerUUID(namespace, "timer-nolock-nil-fields"),
TimerUuid: databases.GenerateTimerUUID(namespace, "timer-nolock-nil-fields"),
Namespace: namespace,
ExecuteAt: time.Now().Add(5 * time.Minute),
CallbackUrl: "https://example.com/nolock/nil",
Expand Down Expand Up @@ -450,7 +440,7 @@ func TestCreateTimerNoLock_InvalidPayloadSerialization(t *testing.T) {
// Create timer with non-serializable payload (function type)
timer := &databases.DbTimer{
Id: "timer-nolock-invalid-payload",
TimerUuid: generateTimerUUID(namespace, "timer-nolock-invalid-payload"),
TimerUuid: databases.GenerateTimerUUID(namespace, "timer-nolock-invalid-payload"),
Namespace: namespace,
ExecuteAt: time.Now().Add(5 * time.Minute),
CallbackUrl: "https://example.com/nolock/invalid",
Expand Down Expand Up @@ -485,7 +475,7 @@ func TestCreateTimerNoLock_ConcurrentCreation(t *testing.T) {
defer wg.Done()
timer := &databases.DbTimer{
Id: fmt.Sprintf("concurrent-nolock-timer-%d", idx),
TimerUuid: generateTimerUUID(namespace, fmt.Sprintf("concurrent-nolock-timer-%d", idx)),
TimerUuid: databases.GenerateTimerUUID(namespace, fmt.Sprintf("concurrent-nolock-timer-%d", idx)),
Namespace: namespace,
ExecuteAt: time.Now().Add(time.Duration(idx) * time.Minute),
CallbackUrl: fmt.Sprintf("https://example.com/nolock/callback/%d", idx),
Expand Down Expand Up @@ -527,8 +517,8 @@ func TestCreateTimer_DuplicateTimerOverwrite(t *testing.T) {
shardId := 1
namespace := "test_namespace"
timerId := "duplicate-timer"
baseUuid := generateTimerUUID(namespace, timerId)
alternateUuid := generateTimerUUID(namespace, timerId+"_alt")
baseUuid := databases.GenerateTimerUUID(namespace, timerId)
alternateUuid := databases.GenerateTimerUUID(namespace, timerId+"_alt")

// Create shard record
ownerId := "owner-1"
Expand Down Expand Up @@ -649,7 +639,7 @@ func TestCreateTimerNoLock_DuplicateTimerOverwrite(t *testing.T) {
// Create initial timer
originalTimer := &databases.DbTimer{
Id: "duplicate-timer-nolock",
TimerUuid: generateTimerUUID(namespace, "duplicate-timer-nolock"),
TimerUuid: databases.GenerateTimerUUID(namespace, "duplicate-timer-nolock"),
Namespace: namespace,
ExecuteAt: time.Now().Add(5 * time.Minute),
CallbackUrl: "https://original-nolock.com/callback",
Expand All @@ -676,8 +666,8 @@ func TestCreateTimerNoLock_DuplicateTimerOverwrite(t *testing.T) {

// Create updated timer with same ID (should create new record due to different UUID)
updatedTimer := &databases.DbTimer{
Id: "duplicate-timer-nolock", // Same ID
TimerUuid: generateTimerUUID(namespace, "duplicate-timer-nolock-updated"), // Different UUID
Id: "duplicate-timer-nolock", // Same ID
TimerUuid: databases.GenerateTimerUUID(namespace, "duplicate-timer-nolock-updated"), // Different UUID
Namespace: namespace,
ExecuteAt: time.Now().Add(15 * time.Minute), // Different execution time
CallbackUrl: "https://updated-nolock.com/callback", // Different callback
Expand Down
Loading
Loading