Ctrl K
ComunidadGuías…

internal/users/storetest/storetest.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// Package storetest contains the acceptance contract shared by user stores.
package storetest

import (
	"context"
	"errors"
	"fmt"
	"sync"
	"testing"
	"time"

	"github.com/readeem/hostebin/internal/users"
)

// Run exercises the observable users.Store contract. Backend packages should
// call it from a TestStoreConformance test with a fresh store factory.
func Run(t *testing.T, newStore func(*testing.T) users.Store) {
	t.Helper()
	ctx := context.Background()

	t.Run("sentinel errors and composite operations", func(t *testing.T) {
		store := newStore(t)
		defer store.Close()
		if _, err := store.GetUser(ctx, "missing"); !errors.Is(err, users.ErrUserNotFound) {
			t.Fatalf("GetUser missing = %v", err)
		}
		if _, err := store.GetTokenForUser(ctx, "missing"); !errors.Is(err, users.ErrUserNotFound) {
			t.Fatalf("GetTokenForUser missing = %v", err)
		}
		now := time.Now().UTC()
		user := users.User{ID: "u_one", Name: "one", Role: users.RoleAdmin, CreatedAt: now}
		token := users.Token{ID: "t_one", UserID: user.ID, Label: "initial", Digest: users.HashToken("secret"), CreatedAt: now}
		if err := store.CreateUserWithToken(ctx, user, token); err != nil {
			t.Fatal(err)
		}
		clash := users.User{ID: "u_two", Name: "ONE", Role: users.RoleUser, CreatedAt: now}
		clashToken := users.Token{ID: "t_clash", UserID: clash.ID, Label: "clash", Digest: users.HashToken("clash"), CreatedAt: now}
		if err := store.CreateUserWithToken(ctx, clash, clashToken); !errors.Is(err, users.ErrDuplicateName) {
			t.Fatalf("duplicate name = %v", err)
		}
		second := users.User{ID: "u_two", Name: "two", Role: users.RoleUser, CreatedAt: now}
		duplicate := users.Token{ID: "t_two", UserID: second.ID, Label: "duplicate", Digest: token.Digest, CreatedAt: now}
		if err := store.CreateUserWithToken(ctx, second, duplicate); !errors.Is(err, users.ErrDuplicateToken) {
			t.Fatalf("composite injected failure = %v", err)
		}
		if _, err := store.GetUser(ctx, second.ID); !errors.Is(err, users.ErrUserNotFound) {
			t.Fatalf("partially-created user after composite failure = %v", err)
		}
		if _, _, err := store.LookupToken(ctx, token.Digest); err != nil {
			t.Fatal(err)
		}
		another := users.Token{ID: "t_another", UserID: user.ID, Label: "another", Digest: users.HashToken("another"), CreatedAt: now}
		if err := store.SetToken(ctx, another); err != nil {
			t.Fatalf("set replacement token = %v", err)
		}
		if _, _, err := store.LookupToken(ctx, token.Digest); !errors.Is(err, users.ErrTokenNotFound) {
			t.Fatalf("replaced token still resolves = %v", err)
		}
		token = another
		if err := store.DeleteTokenForUser(ctx, user.ID); err != nil {
			t.Fatal(err)
		}
		if _, _, err := store.LookupToken(ctx, token.Digest); !errors.Is(err, users.ErrTokenNotFound) {
			t.Fatalf("lookup immediately after revoke = %v", err)
		}
		if err := store.DeleteUserWithToken(ctx, user.ID); err != nil {
			t.Fatal(err)
		}
		if _, err := store.GetUser(ctx, user.ID); !errors.Is(err, users.ErrUserNotFound) {
			t.Fatalf("user after composite delete = %v", err)
		}
	})

	t.Run("concurrent access", func(t *testing.T) {
		store := newStore(t)
		defer store.Close()
		now := time.Now().UTC()
		user := users.User{ID: "u_concurrent", Name: "concurrent", Role: users.RoleUser, CreatedAt: now}
		seed := users.Token{ID: "t_seed", UserID: user.ID, Label: "seed", Digest: users.HashToken("seed"), CreatedAt: now}
		if err := store.CreateUserWithToken(ctx, user, seed); err != nil {
			t.Fatal(err)
		}
		var wg sync.WaitGroup
		for i := range 16 {
			wg.Go(func() {
				id := fmt.Sprintf("t_%d", i)
				digest := users.HashToken(id)
				_ = store.SetToken(ctx, users.Token{ID: id, UserID: user.ID, Label: id, Digest: digest, CreatedAt: now})
				_, _, _ = store.LookupToken(ctx, digest)
				_, _ = store.GetTokenForUser(ctx, user.ID)
			})
		}
		wg.Wait()
		token, err := store.GetTokenForUser(ctx, user.ID)
		if err != nil || token.UserID != user.ID {
			t.Fatalf("GetTokenForUser = %#v, %v", token, err)
		}
	})
}