package authentication import ( "context" "errors" "testing" "time" "ruben/inventory2/consts" "ruben/inventory2/internal/testdb" ) func TestDevLogin(t *testing.T) { pool := testdb.Pool(t) auth := &Authenticator{db: pool} ctx := context.Background() userID := testdb.NewUserID(t) t.Cleanup(func() { pool.Exec(context.Background(), "DELETE FROM oauth_tokens WHERE id_token_subject = $1", userID) pool.Exec(context.Background(), "DELETE FROM oauth_users WHERE user_id = $1", userID) }) accessToken, expiration, err := auth.DevLogin(ctx, userID, "Test User") if err != nil { t.Fatalf("DevLogin() error = %v", err) } if accessToken == "" { t.Fatal("DevLogin() returned an empty access token") } if !expiration.After(time.Now().Add(30 * 24 * time.Hour)) { t.Errorf("DevLogin() expiration = %v, want something far enough out to avoid the near-expiry refresh path", expiration) } claims, err := auth.GetAccessTokenClaimsAndExpiration(ctx, accessToken) if err != nil { t.Fatalf("GetAccessTokenClaimsAndExpiration() error = %v", err) } if claims.Name != "Test User" { t.Errorf("claims.Name = %q, want %q", claims.Name, "Test User") } // Postgres timestamptz has microsecond precision, so the round-tripped // value loses the sub-microsecond portion of Go's nanosecond clock. if diff := claims.Expiration.Sub(expiration); diff > time.Millisecond || diff < -time.Millisecond { t.Errorf("claims.Expiration = %v, want ~%v (diff %v)", claims.Expiration, expiration, diff) } _, tokenType, err := auth.getRefreshTokenForAccessToken(ctx, accessToken) if err != nil { t.Fatalf("getRefreshTokenForAccessToken() error = %v", err) } if tokenType != "dev" { t.Errorf("tokenType = %q, want %q", tokenType, "dev") } } // DevLogin should be safe to call more than once for the same user_id - // e.g. testing multiple times as the same dev identity - since oauth_users // is keyed on user_id but each call mints its own oauth_tokens row. func TestDevLogin_SameUserIDTwice(t *testing.T) { pool := testdb.Pool(t) auth := &Authenticator{db: pool} ctx := context.Background() userID := testdb.NewUserID(t) t.Cleanup(func() { pool.Exec(context.Background(), "DELETE FROM oauth_tokens WHERE id_token_subject = $1", userID) pool.Exec(context.Background(), "DELETE FROM oauth_users WHERE user_id = $1", userID) }) token1, _, err := auth.DevLogin(ctx, userID, "Test User") if err != nil { t.Fatalf("first DevLogin() error = %v", err) } token2, _, err := auth.DevLogin(ctx, userID, "Test User") if err != nil { t.Fatalf("second DevLogin() error = %v", err) } if token1 == token2 { t.Fatalf("DevLogin() returned the same access token twice: %q", token1) } } func TestGetAccessTokenClaimsAndExpiration_UnknownToken(t *testing.T) { pool := testdb.Pool(t) auth := &Authenticator{db: pool} ctx := context.Background() _, err := auth.GetAccessTokenClaimsAndExpiration(ctx, "no-such-token-"+testdb.NewUserID(t)) if !errors.Is(err, consts.ErrNotFound) { t.Fatalf("GetAccessTokenClaimsAndExpiration() error = %v, want %v", err, consts.ErrNotFound) } }