|
| 1 | +package cache |
| 2 | + |
| 3 | +import ( |
| 4 | + "context" |
| 5 | + "crypto/sha256" |
| 6 | + "encoding/hex" |
| 7 | + "testing" |
| 8 | + "time" |
| 9 | + |
| 10 | + "github.com/ory/dockertest/v3" |
| 11 | + "github.com/stretchr/testify/require" |
| 12 | + |
| 13 | + "github.com/rudderlabs/rudder-go-kit/jsonrs" |
| 14 | + "github.com/rudderlabs/rudder-go-kit/testhelper/docker/resource/postgres" |
| 15 | +) |
| 16 | + |
| 17 | +// TestCacheStoreSet exercises the upsert: it asserts that writing the |
| 18 | +// same config twice does not rewrite the row (updated_at stays put), while |
| 19 | +// writing a different config does. |
| 20 | +func TestCacheStoreSet(t *testing.T) { |
| 21 | + pool, err := dockertest.NewPool("") |
| 22 | + require.NoError(t, err) |
| 23 | + resourcePostgres, err := postgres.Setup(pool, t) |
| 24 | + require.NoError(t, err) |
| 25 | + |
| 26 | + db := resourcePostgres.DB |
| 27 | + require.NoError(t, migrate(db)) |
| 28 | + |
| 29 | + store := &cacheStore{ |
| 30 | + DB: db, |
| 31 | + secret: sha256.Sum256([]byte("secret")), |
| 32 | + key: "test-workspace", |
| 33 | + } |
| 34 | + ctx := context.Background() |
| 35 | + |
| 36 | + // expectedHash mirrors the production hashing so the test verifies the |
| 37 | + // stored value independently. |
| 38 | + expectedHash := func(config any) string { |
| 39 | + b, err := jsonrs.Marshal(config) |
| 40 | + require.NoError(t, err) |
| 41 | + sum := sha256.Sum256(b) |
| 42 | + return hex.EncodeToString(sum[:]) |
| 43 | + } |
| 44 | + |
| 45 | + readRow := func() (updatedAt time.Time, hash string, config []byte) { |
| 46 | + t.Helper() |
| 47 | + require.NoError(t, db.QueryRowContext(ctx, |
| 48 | + `SELECT updated_at, config_hash, config FROM config_cache WHERE key = $1`, |
| 49 | + store.key, |
| 50 | + ).Scan(&updatedAt, &hash, &config)) |
| 51 | + return updatedAt, hash, config |
| 52 | + } |
| 53 | + |
| 54 | + configA := map[string]any{"workspace": "A", "sources": []string{"s1", "s2"}} |
| 55 | + configB := map[string]any{"workspace": "B", "sources": []string{"s3"}} |
| 56 | + |
| 57 | + // first write inserts the row |
| 58 | + require.NoError(t, store.set(ctx, configA)) |
| 59 | + firstUpdatedAt, firstHash, firstConfig := readRow() |
| 60 | + require.Equal(t, expectedHash(configA), firstHash) |
| 61 | + |
| 62 | + // writing an identical config must not rewrite the row |
| 63 | + require.NoError(t, store.set(ctx, configA)) |
| 64 | + sameUpdatedAt, sameHash, sameConfig := readRow() |
| 65 | + require.Equal(t, firstUpdatedAt, sameUpdatedAt, "updated_at must not change for identical config") |
| 66 | + require.Equal(t, firstHash, sameHash) |
| 67 | + require.Equal(t, firstConfig, sameConfig, "config blob must not be rewritten for identical config") |
| 68 | + |
| 69 | + // writing a different config must rewrite the row and bump updated_at |
| 70 | + time.Sleep(time.Millisecond) // ensure NOW() is observably later |
| 71 | + require.NoError(t, store.set(ctx, configB)) |
| 72 | + changedUpdatedAt, changedHash, changedConfig := readRow() |
| 73 | + require.True(t, changedUpdatedAt.After(firstUpdatedAt), "updated_at must advance when config changes") |
| 74 | + require.Equal(t, expectedHash(configB), changedHash) |
| 75 | + require.NotEqual(t, firstConfig, changedConfig, "config blob must be rewritten when config changes") |
| 76 | +} |
0 commit comments