@@ -14733,3 +14733,189 @@ func TestSoftDeleteWorkspaceAgentsByWorkspaceID(t *testing.T) {
1473314733 err = db.SoftDeleteWorkspaceAgentsByWorkspaceID(ctx, wsEmpty)
1473414734 require.NoError(t, err)
1473514735}
14736+
14737+ func TestAIGatewayKeysTableConstraints(t *testing.T) {
14738+ t.Parallel()
14739+
14740+ db, _ := dbtestutil.NewDB(t)
14741+ ctx := testutil.Context(t, testutil.WaitMedium)
14742+
14743+ preExsiting := database.InsertAIGatewayKeyParams{
14744+ ID: uuid.New(),
14745+ Name: "name",
14746+ SecretPrefix: "cgw_test__1",
14747+ HashedSecret: []byte("first-secret"),
14748+ }
14749+ _, err := db.InsertAIGatewayKey(ctx, preExsiting)
14750+ require.NoError(t, err)
14751+
14752+ tests := []struct {
14753+ name string
14754+ params database.InsertAIGatewayKeyParams
14755+ expectUniqueErr database.UniqueConstraint
14756+ expectCheckErr database.CheckConstraint
14757+ }{
14758+ {
14759+ name: "duplicate name",
14760+ params: aiGatewayKeyParams(preExsiting.Name, "cgw_test002"),
14761+ expectUniqueErr: database.UniqueAiGatewayKeysNameIndex,
14762+ },
14763+ {
14764+ name: "duplicate secret prefix",
14765+ params: aiGatewayKeyParams("different-key", preExsiting.SecretPrefix),
14766+ expectUniqueErr: database.UniqueAiGatewayKeysSecretPrefixIndex,
14767+ },
14768+ {
14769+ name: "duplicate hashed secret",
14770+ params: database.InsertAIGatewayKeyParams{ID: uuid.New(), Name: "other-name", SecretPrefix: "cgw_1234567", HashedSecret: preExsiting.HashedSecret},
14771+ expectUniqueErr: database.UniqueAiGatewayKeysHashedSecretIndex,
14772+ },
14773+ {
14774+ name: "empty name",
14775+ params: aiGatewayKeyParams("", "cgw_1234567"),
14776+ expectCheckErr: database.CheckAiGatewayKeysNameCheck,
14777+ },
14778+ {
14779+ name: "name with trailing dash",
14780+ params: aiGatewayKeyParams("other-name-", "cgw_1234567"),
14781+ expectCheckErr: database.CheckAiGatewayKeysNameCheck,
14782+ },
14783+ {
14784+ name: "name with consecutive dashes",
14785+ params: aiGatewayKeyParams("other--name", "cgw_1234567"),
14786+ expectCheckErr: database.CheckAiGatewayKeysNameCheck,
14787+ },
14788+ {
14789+ name: "name with underscore",
14790+ params: aiGatewayKeyParams("other_name", "cgw_1234567"),
14791+ expectCheckErr: database.CheckAiGatewayKeysNameCheck,
14792+ },
14793+ {
14794+ name: "name with space",
14795+ params: aiGatewayKeyParams("other name", "cgw_1234567"),
14796+ expectCheckErr: database.CheckAiGatewayKeysNameCheck,
14797+ },
14798+ {
14799+ name: "name with leading dash",
14800+ params: aiGatewayKeyParams("-other-name", "cgw_1234567"),
14801+ expectCheckErr: database.CheckAiGatewayKeysNameCheck,
14802+ },
14803+ {
14804+ name: "name longer than 64 characters",
14805+ params: aiGatewayKeyParams(strings.Repeat("a", 65), "cgw_1234567"),
14806+ expectCheckErr: database.CheckAiGatewayKeysNameCheck,
14807+ },
14808+ {
14809+ name: "empty secret prefix",
14810+ params: aiGatewayKeyParams("other-name", ""),
14811+ expectCheckErr: database.CheckAiGatewayKeysSecretPrefixCheck,
14812+ },
14813+ {
14814+ name: "invalid secret prefix length",
14815+ params: aiGatewayKeyParams("other-name", "cgw_short"),
14816+ expectCheckErr: database.CheckAiGatewayKeysSecretPrefixCheck,
14817+ },
14818+ {
14819+ name: "empty hashed secret",
14820+ params: database.InsertAIGatewayKeyParams{ID: uuid.New(), Name: "other-name", SecretPrefix: "cgw_1234567"},
14821+ expectCheckErr: database.CheckAiGatewayKeysHashedSecretCheck,
14822+ },
14823+ }
14824+
14825+ for _, tc := range tests {
14826+ t.Run(tc.name, func(t *testing.T) {
14827+ t.Parallel()
14828+
14829+ ctx := testutil.Context(t, testutil.WaitShort)
14830+
14831+ _, err = db.InsertAIGatewayKey(ctx, tc.params)
14832+ require.Error(t, err)
14833+ requireAIGatewayKeysViolation(t, err, tc.expectUniqueErr, tc.expectCheckErr)
14834+ })
14835+ }
14836+ }
14837+
14838+ func TestAIGatewayKeysQueries(t *testing.T) {
14839+ t.Parallel()
14840+
14841+ db, _ := dbtestutil.NewDB(t)
14842+ ctx := testutil.Context(t, testutil.WaitLong)
14843+
14844+ first := aiGatewayKeyParams("first-key", "cgw_first__")
14845+ second := aiGatewayKeyParams("second-key", "cgw_second_")
14846+ second.HashedSecret = []byte("second-secret")
14847+
14848+ firstRow, err := db.InsertAIGatewayKey(ctx, first)
14849+ require.NoError(t, err)
14850+ require.Equal(t, first.ID, firstRow.ID)
14851+
14852+ require.Equal(t, "first-key", firstRow.Name)
14853+ require.Equal(t, first.SecretPrefix, firstRow.SecretPrefix)
14854+
14855+ secondRow, err := db.InsertAIGatewayKey(ctx, second)
14856+ require.NoError(t, err)
14857+ require.Equal(t, second.ID, secondRow.ID)
14858+
14859+ require.Equal(t, "second-key", secondRow.Name)
14860+ require.Equal(t, second.SecretPrefix, secondRow.SecretPrefix)
14861+
14862+ keys, err := db.ListAIGatewayKeys(ctx)
14863+ require.NoError(t, err)
14864+ require.Len(t, keys, 2)
14865+
14866+ requireAIGatewayKeysRow(t, keys[0], first, firstRow.CreatedAt)
14867+ require.False(t, keys[0].LastUsedAt.Valid)
14868+ requireAIGatewayKeysRow(t, keys[1], second, secondRow.CreatedAt)
14869+ require.False(t, keys[1].LastUsedAt.Valid)
14870+
14871+ deleted, err := db.DeleteAIGatewayKey(ctx, first.ID)
14872+ require.NoError(t, err)
14873+ require.Equal(t, first.ID, deleted.ID)
14874+ require.Equal(t, first.Name, deleted.Name)
14875+ require.Equal(t, first.SecretPrefix, deleted.SecretPrefix)
14876+ require.Equal(t, firstRow.CreatedAt, deleted.CreatedAt)
14877+
14878+ _, err = db.DeleteAIGatewayKey(ctx, first.ID)
14879+ require.ErrorIs(t, err, sql.ErrNoRows)
14880+
14881+ keys, err = db.ListAIGatewayKeys(ctx)
14882+ require.NoError(t, err)
14883+ require.Len(t, keys, 1)
14884+ requireAIGatewayKeysRow(t, keys[0], second, secondRow.CreatedAt)
14885+ }
14886+
14887+ func aiGatewayKeyParams(name string, secretPrefix string) database.InsertAIGatewayKeyParams {
14888+ return database.InsertAIGatewayKeyParams{
14889+ ID: uuid.New(),
14890+ Name: name,
14891+ SecretPrefix: secretPrefix,
14892+ HashedSecret: []byte("secret"),
14893+ }
14894+ }
14895+
14896+ func requireAIGatewayKeysRow(t *testing.T, listRow database.ListAIGatewayKeysRow, insertParams database.InsertAIGatewayKeyParams, insertCreatedAt time.Time) {
14897+ t.Helper()
14898+
14899+ require.Equal(t, insertParams.ID, listRow.ID)
14900+ require.Equal(t, insertParams.Name, listRow.Name)
14901+ require.Equal(t, insertParams.SecretPrefix, listRow.SecretPrefix)
14902+ require.Equal(t, insertCreatedAt, listRow.CreatedAt)
14903+ }
14904+
14905+ func requireAIGatewayKeysViolation(
14906+ t *testing.T,
14907+ err error,
14908+ uniqueConstraint database.UniqueConstraint,
14909+ checkConstraint database.CheckConstraint,
14910+ ) {
14911+ t.Helper()
14912+
14913+ switch {
14914+ case uniqueConstraint != "":
14915+ require.True(t, database.IsUniqueViolation(err, uniqueConstraint), "expected %q unique violation, got %v", uniqueConstraint, err)
14916+ case checkConstraint != "":
14917+ require.True(t, database.IsCheckViolation(err, checkConstraint), "expected %q check violation, got %v", checkConstraint, err)
14918+ default:
14919+ require.FailNow(t, "test case must expect a constraint error")
14920+ }
14921+ }
0 commit comments