237 lines
9.1 KiB
Go
237 lines
9.1 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/gochat/gochat/internal/model"
|
|
)
|
|
|
|
// hashToken returns the SHA-256 hex digest of a plaintext token string.
|
|
func hashToken(plain string) string {
|
|
h := sha256.Sum256([]byte(plain))
|
|
return fmt.Sprintf("%x", h)
|
|
}
|
|
|
|
// tokenPrefix extracts the first 8 characters of the plaintext token for prefix lookup.
|
|
func tokenPrefix(plain string) string {
|
|
if len(plain) < 8 {
|
|
return plain
|
|
}
|
|
return plain[:8]
|
|
}
|
|
|
|
// helper: build a minimal valid AccessToken for testing
|
|
func newTestAccessToken(ownerType string, ownerID uint, plainToken string, name string) *model.AccessToken {
|
|
return &model.AccessToken{
|
|
OwnerType: ownerType,
|
|
OwnerID: ownerID,
|
|
Token: hashToken(plainToken),
|
|
TokenPrefix: tokenPrefix(plainToken),
|
|
Name: name,
|
|
}
|
|
}
|
|
|
|
// ========== Create + GetByID roundtrip ==========
|
|
|
|
func TestAccessTokenRepo_CreateAndGetByID(t *testing.T) {
|
|
db := setupTestDB(t, &model.AccessToken{}, &model.PlatformApp{}, &model.User{})
|
|
repo := NewAccessTokenRepo(db)
|
|
|
|
// Create prerequisite PlatformApp for FK
|
|
account := &model.Account{Name: "TestAccount"}
|
|
require.NoError(t, db.Create(account).Error)
|
|
platformApp := &model.PlatformApp{Name: "TestApp", Type: "api", Status: "active"}
|
|
require.NoError(t, db.Create(platformApp).Error)
|
|
|
|
token := newTestAccessToken(model.AccessTokenOwnerTypePlatformApp, platformApp.ID, "secret_plain_token_1", "TestToken")
|
|
err := repo.Create(context.Background(), token)
|
|
require.NoError(t, err)
|
|
assert.NotZero(t, token.ID, "ID should be set after Create")
|
|
|
|
// GetByID roundtrip
|
|
found, err := repo.GetByID(context.Background(), token.ID)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, token.ID, found.ID)
|
|
assert.Equal(t, hashToken("secret_plain_token_1"), found.Token)
|
|
assert.Equal(t, tokenPrefix("secret_plain_token_1"), found.TokenPrefix)
|
|
assert.Equal(t, model.AccessTokenOwnerTypePlatformApp, found.OwnerType)
|
|
assert.Equal(t, platformApp.ID, found.OwnerID)
|
|
}
|
|
|
|
// ========== FindByTokenPrefix lookup ==========
|
|
|
|
func TestAccessTokenRepo_FindByTokenPrefix(t *testing.T) {
|
|
db := setupTestDB(t, &model.AccessToken{}, &model.PlatformApp{}, &model.User{})
|
|
repo := NewAccessTokenRepo(db)
|
|
|
|
account := &model.Account{Name: "TestAccount"}
|
|
require.NoError(t, db.Create(account).Error)
|
|
platformApp := &model.PlatformApp{Name: "TestApp", Type: "api", Status: "active"}
|
|
require.NoError(t, db.Create(platformApp).Error)
|
|
|
|
plain := "abcdef1234567890xyz"
|
|
token := newTestAccessToken(model.AccessTokenOwnerTypePlatformApp, platformApp.ID, plain, "PrefixLookup")
|
|
err := repo.Create(context.Background(), token)
|
|
require.NoError(t, err)
|
|
|
|
// Find by prefix + owner type
|
|
found, err := repo.FindByTokenPrefix(context.Background(), tokenPrefix(plain), model.AccessTokenOwnerTypePlatformApp)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, token.ID, found.ID)
|
|
assert.Equal(t, hashToken(plain), found.Token)
|
|
}
|
|
|
|
// ========== FindByOwner for PlatformApp owner ==========
|
|
|
|
func TestAccessTokenRepo_FindByOwner_PlatformApp(t *testing.T) {
|
|
db := setupTestDB(t, &model.AccessToken{}, &model.PlatformApp{}, &model.User{})
|
|
repo := NewAccessTokenRepo(db)
|
|
|
|
account := &model.Account{Name: "TestAccount"}
|
|
require.NoError(t, db.Create(account).Error)
|
|
platformApp := &model.PlatformApp{Name: "TestApp", Type: "api", Status: "active"}
|
|
require.NoError(t, db.Create(platformApp).Error)
|
|
|
|
t1 := newTestAccessToken(model.AccessTokenOwnerTypePlatformApp, platformApp.ID, "token_p1", "Token1")
|
|
t2 := newTestAccessToken(model.AccessTokenOwnerTypePlatformApp, platformApp.ID, "token_p2", "Token2")
|
|
require.NoError(t, repo.Create(context.Background(), t1))
|
|
require.NoError(t, repo.Create(context.Background(), t2))
|
|
|
|
tokens, err := repo.FindByOwner(context.Background(), model.AccessTokenOwnerTypePlatformApp, platformApp.ID)
|
|
require.NoError(t, err)
|
|
assert.Len(t, tokens, 2)
|
|
}
|
|
|
|
// ========== FindByOwner for User owner ==========
|
|
|
|
func TestAccessTokenRepo_FindByOwner_User(t *testing.T) {
|
|
db := setupTestDB(t, &model.AccessToken{}, &model.PlatformApp{}, &model.User{})
|
|
repo := NewAccessTokenRepo(db)
|
|
|
|
account := &model.Account{Name: "TestAccount"}
|
|
require.NoError(t, db.Create(account).Error)
|
|
user := &model.User{Name: "TestUser", Email: "user@test.com"}
|
|
require.NoError(t, db.Create(user).Error)
|
|
|
|
t1 := newTestAccessToken(model.AccessTokenOwnerTypeUser, user.ID, "token_u1", "UserToken1")
|
|
t2 := newTestAccessToken(model.AccessTokenOwnerTypeUser, user.ID, "token_u2", "UserToken2")
|
|
require.NoError(t, repo.Create(context.Background(), t1))
|
|
require.NoError(t, repo.Create(context.Background(), t2))
|
|
|
|
tokens, err := repo.FindByOwner(context.Background(), model.AccessTokenOwnerTypeUser, user.ID)
|
|
require.NoError(t, err)
|
|
assert.Len(t, tokens, 2)
|
|
|
|
// Different owner should not see these tokens
|
|
tokens2, err := repo.FindByOwner(context.Background(), model.AccessTokenOwnerTypePlatformApp, user.ID)
|
|
require.NoError(t, err)
|
|
assert.Len(t, tokens2, 0)
|
|
}
|
|
|
|
// ========== UpdateLastUsedAt ==========
|
|
|
|
func TestAccessTokenRepo_UpdateLastUsedAt(t *testing.T) {
|
|
db := setupTestDB(t, &model.AccessToken{}, &model.PlatformApp{}, &model.User{})
|
|
repo := NewAccessTokenRepo(db)
|
|
|
|
account := &model.Account{Name: "TestAccount"}
|
|
require.NoError(t, db.Create(account).Error)
|
|
platformApp := &model.PlatformApp{Name: "TestApp", Type: "api", Status: "active"}
|
|
require.NoError(t, db.Create(platformApp).Error)
|
|
|
|
token := newTestAccessToken(model.AccessTokenOwnerTypePlatformApp, platformApp.ID, "token_lu", "LastUsed")
|
|
require.NoError(t, repo.Create(context.Background(), token))
|
|
|
|
// Initially LastUsedAt should be nil
|
|
found, err := repo.GetByID(context.Background(), token.ID)
|
|
require.NoError(t, err)
|
|
assert.Nil(t, found.LastUsedAt)
|
|
|
|
// Update LastUsedAt
|
|
err = repo.UpdateLastUsedAt(context.Background(), token.ID)
|
|
require.NoError(t, err)
|
|
|
|
// Verify it was set
|
|
found2, err := repo.GetByID(context.Background(), token.ID)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, found2.LastUsedAt)
|
|
assert.True(t, found2.LastUsedAt.After(time.Now().Add(-5*time.Second)))
|
|
}
|
|
|
|
// ========== Soft delete + FindByTokenPrefix should not find deleted tokens ==========
|
|
|
|
func TestAccessTokenRepo_SoftDelete_NotFoundByPrefix(t *testing.T) {
|
|
db := setupTestDB(t, &model.AccessToken{}, &model.PlatformApp{}, &model.User{})
|
|
repo := NewAccessTokenRepo(db)
|
|
|
|
account := &model.Account{Name: "TestAccount"}
|
|
require.NoError(t, db.Create(account).Error)
|
|
platformApp := &model.PlatformApp{Name: "TestApp", Type: "api", Status: "active"}
|
|
require.NoError(t, db.Create(platformApp).Error)
|
|
|
|
plain := "deletable_token_abc"
|
|
token := newTestAccessToken(model.AccessTokenOwnerTypePlatformApp, platformApp.ID, plain, "DeleteMe")
|
|
require.NoError(t, repo.Create(context.Background(), token))
|
|
|
|
// Verify prefix lookup works before deletion
|
|
found, err := repo.FindByTokenPrefix(context.Background(), tokenPrefix(plain), model.AccessTokenOwnerTypePlatformApp)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, token.ID, found.ID)
|
|
|
|
// Soft delete
|
|
err = repo.Delete(context.Background(), token.ID)
|
|
require.NoError(t, err)
|
|
|
|
// FindByTokenPrefix should NOT find the deleted token
|
|
_, err = repo.FindByTokenPrefix(context.Background(), tokenPrefix(plain), model.AccessTokenOwnerTypePlatformApp)
|
|
assert.Error(t, err, "FindByTokenPrefix should not find soft-deleted tokens")
|
|
}
|
|
|
|
// ========== FindActiveByOwner ==========
|
|
|
|
func TestAccessTokenRepo_FindActiveByOwner(t *testing.T) {
|
|
db := setupTestDB(t, &model.AccessToken{}, &model.PlatformApp{}, &model.User{})
|
|
repo := NewAccessTokenRepo(db)
|
|
|
|
account := &model.Account{Name: "TestAccount"}
|
|
require.NoError(t, db.Create(account).Error)
|
|
platformApp := &model.PlatformApp{Name: "TestApp", Type: "api", Status: "active"}
|
|
require.NoError(t, db.Create(platformApp).Error)
|
|
|
|
// Token with no expiry (never expires)
|
|
t1 := newTestAccessToken(model.AccessTokenOwnerTypePlatformApp, platformApp.ID, "active_token_no_expiry", "NoExpiry")
|
|
require.NoError(t, repo.Create(context.Background(), t1))
|
|
|
|
// Token with future expiry (still active)
|
|
futureExpiry := time.Now().Add(24 * time.Hour)
|
|
t2 := newTestAccessToken(model.AccessTokenOwnerTypePlatformApp, platformApp.ID, "active_token_future_expiry", "FutureExpiry")
|
|
t2.ExpiresAt = &futureExpiry
|
|
require.NoError(t, repo.Create(context.Background(), t2))
|
|
|
|
// Token with past expiry (expired)
|
|
pastExpiry := time.Now().Add(-24 * time.Hour)
|
|
t3 := newTestAccessToken(model.AccessTokenOwnerTypePlatformApp, platformApp.ID, "expired_token", "Expired")
|
|
t3.ExpiresAt = &pastExpiry
|
|
require.NoError(t, repo.Create(context.Background(), t3))
|
|
|
|
// FindActiveByOwner should return only non-expired tokens
|
|
tokens, err := repo.FindActiveByOwner(context.Background(), model.AccessTokenOwnerTypePlatformApp, platformApp.ID)
|
|
require.NoError(t, err)
|
|
assert.Len(t, tokens, 2, "should only return non-expired tokens")
|
|
|
|
// Verify both active tokens are present (check IDs)
|
|
ids := make(map[uint]bool)
|
|
for _, tk := range tokens {
|
|
ids[tk.ID] = true
|
|
}
|
|
assert.True(t, ids[t1.ID], "no-expiry token should be active")
|
|
assert.True(t, ids[t2.ID], "future-expiry token should be active")
|
|
assert.False(t, ids[t3.ID], "past-expiry token should NOT be active")
|
|
} |