Infra ADR-0004 renamed the Gitea host. Bulk replace across go.mod and all .go import paths. Build and tests pass unchanged. Closes #20 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Dt6aHEDWRjkK14Voi6HnGh
112 lines
3.3 KiB
Go
112 lines
3.3 KiB
Go
package store_test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"git.d-ma.be/mathias/tapir/internal/adapters/store"
|
|
)
|
|
|
|
// seedUserRow inserts a bare users row (FK target for a connection) as the
|
|
// superuser pool, which bypasses RLS.
|
|
func seedUserRow(t *testing.T, userID string) {
|
|
t.Helper()
|
|
_, err := rawPool(t).Exec(context.Background(),
|
|
`INSERT INTO users (id) VALUES ($1) ON CONFLICT (id) DO NOTHING`, userID)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestUpsertConnectionInsertsRow(t *testing.T) {
|
|
ctx := context.Background()
|
|
s := newStore(t)
|
|
p := rawPool(t)
|
|
resetDB(t, p)
|
|
seedUserRow(t, userA)
|
|
|
|
require.NoError(t, s.UpsertConnection(ctx, userA, store.Connection{
|
|
Provider: "youtube",
|
|
ProviderAccount: "chan@example.com",
|
|
TokenRef: "youtube/" + userA + "/refresh_token",
|
|
Status: "active",
|
|
}))
|
|
|
|
conns, err := s.ConnectionsForUser(ctx, userA)
|
|
require.NoError(t, err)
|
|
require.Len(t, conns, 1)
|
|
require.Equal(t, "youtube", conns[0].Provider)
|
|
require.Equal(t, "chan@example.com", conns[0].ProviderAccount)
|
|
require.Equal(t, "youtube/"+userA+"/refresh_token", conns[0].TokenRef)
|
|
require.Equal(t, "active", conns[0].Status)
|
|
require.False(t, conns[0].ConnectedAt.IsZero(), "connected_at set by the DB default")
|
|
}
|
|
|
|
func TestUpsertConnectionIsIdempotentOnUserProvider(t *testing.T) {
|
|
ctx := context.Background()
|
|
s := newStore(t)
|
|
p := rawPool(t)
|
|
resetDB(t, p)
|
|
seedUserRow(t, userA)
|
|
|
|
require.NoError(t, s.UpsertConnection(ctx, userA, store.Connection{
|
|
Provider: "youtube", TokenRef: "ref-1", Status: "active",
|
|
}))
|
|
// Re-connect the same provider: must update in place, not duplicate.
|
|
require.NoError(t, s.UpsertConnection(ctx, userA, store.Connection{
|
|
Provider: "youtube", TokenRef: "ref-2", Status: "revoked",
|
|
}))
|
|
|
|
var count int
|
|
require.NoError(t, p.QueryRow(ctx,
|
|
`SELECT count(*) FROM video_connections WHERE user_id = $1 AND provider = 'youtube'`,
|
|
userA).Scan(&count))
|
|
require.Equal(t, 1, count, "second connect must update, not duplicate")
|
|
|
|
conns, err := s.ConnectionsForUser(ctx, userA)
|
|
require.NoError(t, err)
|
|
require.Len(t, conns, 1)
|
|
require.Equal(t, "ref-2", conns[0].TokenRef, "token_ref overwritten")
|
|
require.Equal(t, "revoked", conns[0].Status, "status overwritten")
|
|
}
|
|
|
|
func TestDeleteConnection(t *testing.T) {
|
|
ctx := context.Background()
|
|
s := newStore(t)
|
|
resetDB(t, rawPool(t))
|
|
seedUserRow(t, userA)
|
|
|
|
require.NoError(t, s.UpsertConnection(ctx, userA, store.Connection{
|
|
Provider: "youtube", TokenRef: "ref", Status: "active",
|
|
}))
|
|
require.NoError(t, s.DeleteConnection(ctx, userA, "youtube"))
|
|
|
|
conns, err := s.ConnectionsForUser(ctx, userA)
|
|
require.NoError(t, err)
|
|
require.Empty(t, conns)
|
|
|
|
// Deleting an absent connection is a no-op, not an error.
|
|
require.NoError(t, s.DeleteConnection(ctx, userA, "youtube"))
|
|
}
|
|
|
|
func TestConnectionsForUserIsScoped(t *testing.T) {
|
|
ctx := context.Background()
|
|
s := newStore(t)
|
|
resetDB(t, rawPool(t))
|
|
seedUserRow(t, userA)
|
|
seedUserRow(t, userB)
|
|
|
|
require.NoError(t, s.UpsertConnection(ctx, userA, store.Connection{
|
|
Provider: "youtube", TokenRef: "a-ref", Status: "active",
|
|
}))
|
|
|
|
// User B must not see user A's connection.
|
|
connsB, err := s.ConnectionsForUser(ctx, userB)
|
|
require.NoError(t, err)
|
|
require.Empty(t, connsB, "user B must not see user A's connections")
|
|
|
|
connsA, err := s.ConnectionsForUser(ctx, userA)
|
|
require.NoError(t, err)
|
|
require.Len(t, connsA, 1)
|
|
}
|