163 lines
4.6 KiB
Go
163 lines
4.6 KiB
Go
package refreshtokens
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.sendico.io/tech/sxchnge/shared/database/record"
|
|
"git.sendico.io/tech/sxchnge/shared/database/repository"
|
|
"git.sendico.io/tech/sxchnge/shared/database/repository/builder"
|
|
"git.sendico.io/tech/sxchnge/shared/database/template"
|
|
"git.sendico.io/tech/sxchnge/shared/errors"
|
|
"github.com/stretchr/testify/require"
|
|
"go.mongodb.org/mongo-driver/v2/bson"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
type createRepository struct {
|
|
repository.Repository
|
|
filters []bson.D
|
|
patches []bson.D
|
|
patchMatches []bool
|
|
patchErr error
|
|
insertCalls int
|
|
insertErr error
|
|
storedID bson.ObjectID
|
|
}
|
|
|
|
func (r *createRepository) PatchOneByFilter(
|
|
_ context.Context,
|
|
filter builder.Query,
|
|
patch builder.Patch,
|
|
result storable.Storable,
|
|
) (bool, error) {
|
|
r.filters = append(r.filters, filter.BuildQuery())
|
|
r.patches = append(r.patches, patch.Build())
|
|
if r.patchErr != nil {
|
|
return false, r.patchErr
|
|
}
|
|
|
|
call := len(r.filters) - 1
|
|
matched := call < len(r.patchMatches) && r.patchMatches[call]
|
|
if matched {
|
|
result.SetID(r.storedID)
|
|
}
|
|
return matched, nil
|
|
}
|
|
|
|
func (r *createRepository) Insert(_ context.Context, object storable.Storable, _ builder.Query) error {
|
|
r.insertCalls++
|
|
if object.GetID() == nil || object.GetID().IsZero() {
|
|
object.SetID(bson.NewObjectID())
|
|
}
|
|
return r.insertErr
|
|
}
|
|
|
|
func newCreateDB(repo repository.Repository) *RefreshTokenDB {
|
|
return &RefreshTokenDB{
|
|
DBImp: template.DBImp[*sessionmodel.RefreshToken]{
|
|
Logger: zap.NewNop(),
|
|
Repository: repo,
|
|
},
|
|
}
|
|
}
|
|
|
|
func versionedToken(accountRef bson.ObjectID, version, value string) *sessionmodel.RefreshToken {
|
|
return &sessionmodel.RefreshToken{
|
|
ClientRefreshToken: sessionmodel.ClientRefreshToken{
|
|
SessionIdentifier: sessionmodel.SessionIdentifier{
|
|
ClientID: "web-app",
|
|
DeviceID: "browser",
|
|
SessionVersion: version,
|
|
},
|
|
RefreshToken: value,
|
|
},
|
|
AccountRef: &accountRef,
|
|
ExpiresAt: time.Now().Add(24 * time.Hour),
|
|
UserAgent: "test-agent",
|
|
IPAddress: "192.0.2.1",
|
|
}
|
|
}
|
|
|
|
func extendedJSON(t *testing.T, document bson.D) string {
|
|
t.Helper()
|
|
raw, err := bson.MarshalExtJSON(document, true, false)
|
|
require.NoError(t, err)
|
|
return string(raw)
|
|
}
|
|
|
|
func TestCreateReplacesSlotWithoutMatchingNewSessionVersion(t *testing.T) {
|
|
storedID := bson.NewObjectID()
|
|
repo := &createRepository{
|
|
patchMatches: []bool{true},
|
|
storedID: storedID,
|
|
}
|
|
db := newCreateDB(repo)
|
|
token := versionedToken(bson.NewObjectID(), "version-2", "replacement-token")
|
|
|
|
err := db.Create(context.Background(), token)
|
|
require.NoError(t, err)
|
|
require.Equal(t, storedID, *token.GetID())
|
|
require.Zero(t, repo.insertCalls)
|
|
require.Len(t, repo.filters, 1)
|
|
|
|
filterJSON := extendedJSON(t, repo.filters[0])
|
|
require.Contains(t, filterJSON, "accountRef")
|
|
require.Contains(t, filterJSON, "clientId")
|
|
require.Contains(t, filterJSON, "deviceId")
|
|
require.Contains(t, filterJSON, "isRevoked")
|
|
require.NotContains(t, filterJSON, "sessionVersion")
|
|
|
|
patchJSON := extendedJSON(t, repo.patches[0])
|
|
require.Contains(t, patchJSON, "sessionVersion")
|
|
require.Contains(t, patchJSON, "version-2")
|
|
}
|
|
|
|
func TestCreateReconcilesConcurrentSlotInsert(t *testing.T) {
|
|
storedID := bson.NewObjectID()
|
|
repo := &createRepository{
|
|
patchMatches: []bool{false, true},
|
|
insertErr: merrors.DataConflict("duplicate_key"),
|
|
storedID: storedID,
|
|
}
|
|
db := newCreateDB(repo)
|
|
token := versionedToken(bson.NewObjectID(), "version-2", "replacement-token")
|
|
|
|
err := db.Create(context.Background(), token)
|
|
require.NoError(t, err)
|
|
require.Equal(t, storedID, *token.GetID())
|
|
require.Equal(t, 1, repo.insertCalls)
|
|
require.Len(t, repo.filters, 2)
|
|
}
|
|
|
|
func TestCreatePreservesUnrelatedInsertConflict(t *testing.T) {
|
|
repo := &createRepository{
|
|
patchMatches: []bool{false, false},
|
|
insertErr: merrors.DataConflict("duplicate_key"),
|
|
storedID: bson.NewObjectID(),
|
|
}
|
|
db := newCreateDB(repo)
|
|
token := versionedToken(bson.NewObjectID(), "version-2", "replacement-token")
|
|
|
|
err := db.Create(context.Background(), token)
|
|
require.Error(t, err)
|
|
require.True(t, errors.Is(err, merrors.ErrDataConflict))
|
|
require.Equal(t, 1, repo.insertCalls)
|
|
require.Len(t, repo.filters, 2)
|
|
}
|
|
|
|
func TestCredentialFilterKeepsSessionVersion(t *testing.T) {
|
|
query := filterByAccount(bson.NewObjectID(), &sessionmodel.SessionIdentifier{
|
|
ClientID: "web-app",
|
|
DeviceID: "browser",
|
|
SessionVersion: "version-2",
|
|
})
|
|
|
|
filterJSON := extendedJSON(t, query.BuildQuery())
|
|
require.Contains(t, filterJSON, "sessionVersion")
|
|
require.Contains(t, filterJSON, "version-2")
|
|
}
|