- TestRequestUser: bearer, token and auth_token cookies in guard order; expired, foreign, blacklisted, revoked by tokens_valid_after and deactivated tokens are anonymous; only infrastructure failures error - TestEndRequestSession (no database): jti blacklisted until the token expires, forget cookie with the configured Domain and Path, best effort - TestAvatarURL: thumbnail, broken-image and storage-failure fallbacks, gravatar with the size, the user payload's avatar_url unchanged - TestDeactivatingEvent: one event per newly deactivated user inside the bulk action's transaction, a listener error rolls the action back
111 lines
3.5 KiB
Go
111 lines
3.5 KiB
Go
package user
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net/http"
|
|
"sync"
|
|
"testing"
|
|
|
|
"git.golem15.com/golem15/sm-user-plugin/classes"
|
|
"git.golem15.com/golem15/sm-user-plugin/models"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// TestDeactivatingEvent pins user.Deactivating (D-05): the admin deactivate
|
|
// bulk action fires one event per newly deactivated user inside the
|
|
// action's transaction, already deactivated users fire none, a listener
|
|
// error rolls the whole action back, and the event name is the constant.
|
|
func TestDeactivatingEvent(t *testing.T) {
|
|
if classes.DeactivatingEventName != "user.Deactivating" {
|
|
t.Fatalf("DeactivatingEventName = %q", classes.DeactivatingEventName)
|
|
}
|
|
env := newAdminEnv(t)
|
|
token := env.admin(t, permAccessUsers)
|
|
|
|
var mu sync.Mutex
|
|
var fired []uint
|
|
var failFor uint
|
|
var txSawDeactivation = map[uint]bool{}
|
|
env.app.Events.Listen[*classes.DeactivatingEvent]("test.deactivating", func(ctx context.Context, e *classes.DeactivatingEvent) error {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
if e.User == nil || e.Tx == nil {
|
|
return errors.New("event without user or transaction")
|
|
}
|
|
fired = append(fired, e.User.ID)
|
|
// Inside the action's transaction the row is already deactivated.
|
|
var deleted int64
|
|
if err := e.Tx.Session(&gorm.Session{NewDB: true}).Unscoped().Model(&models.User{}).
|
|
Where("id = ? AND deleted_at IS NOT NULL", e.User.ID).Count(&deleted).Error; err != nil {
|
|
return err
|
|
}
|
|
txSawDeactivation[e.User.ID] = deleted == 1
|
|
if e.User.ID == failFor {
|
|
return errors.New("listener refuses")
|
|
}
|
|
return nil
|
|
})
|
|
reset := func() {
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
fired, txSawDeactivation = nil, map[uint]bool{}
|
|
}
|
|
|
|
a, b := env.seedUser(t, "deact-a", nil), env.seedUser(t, "deact-b", nil)
|
|
if rec := env.bulk(t, token, "deactivate", a, b); rec.Code != http.StatusOK {
|
|
t.Fatalf("deactivate: %d %s", rec.Code, rec.Body.String())
|
|
}
|
|
mu.Lock()
|
|
if len(fired) != 2 || !txSawDeactivation[a.ID] || !txSawDeactivation[b.ID] {
|
|
t.Fatalf("events %v, seen in the transaction %v", fired, txSawDeactivation)
|
|
}
|
|
mu.Unlock()
|
|
for _, u := range []*models.User{a, b} {
|
|
if row := env.reload(t, u.ID); row == nil || !row.DeletedAt.Valid {
|
|
t.Fatalf("user %d not deactivated", u.ID)
|
|
}
|
|
}
|
|
|
|
// Already deactivated users fire nothing; a mixed selection fires only
|
|
// for the newly deactivated one.
|
|
reset()
|
|
c := env.seedUser(t, "deact-c", nil)
|
|
if rec := env.bulk(t, token, "deactivate", a, c); rec.Code != http.StatusOK {
|
|
t.Fatalf("mixed deactivate: %d %s", rec.Code, rec.Body.String())
|
|
}
|
|
mu.Lock()
|
|
if len(fired) != 1 || fired[0] != c.ID {
|
|
t.Fatalf("mixed selection fired %v, want only %d", fired, c.ID)
|
|
}
|
|
mu.Unlock()
|
|
|
|
// A listener error rolls the whole action back.
|
|
reset()
|
|
d, e := env.seedUser(t, "deact-d", nil), env.seedUser(t, "deact-e", nil)
|
|
failFor = e.ID
|
|
if rec := env.bulk(t, token, "deactivate", d, e); rec.Code < 400 {
|
|
t.Fatalf("deactivate with a refusing listener: %d %s", rec.Code, rec.Body.String())
|
|
}
|
|
failFor = 0
|
|
for _, u := range []*models.User{d, e} {
|
|
if row := env.reload(t, u.ID); row == nil || row.DeletedAt.Valid {
|
|
t.Fatalf("user %d deactivated although a listener refused", u.ID)
|
|
}
|
|
}
|
|
|
|
// Other bulk actions fire nothing.
|
|
reset()
|
|
if rec := env.bulk(t, token, "restore", a); rec.Code != http.StatusOK {
|
|
t.Fatalf("restore: %d %s", rec.Code, rec.Body.String())
|
|
}
|
|
if rec := env.bulk(t, token, "activate", d); rec.Code != http.StatusOK {
|
|
t.Fatalf("activate: %d %s", rec.Code, rec.Body.String())
|
|
}
|
|
mu.Lock()
|
|
if len(fired) != 0 {
|
|
t.Fatalf("restore and activate fired %v", fired)
|
|
}
|
|
mu.Unlock()
|
|
}
|