test: an organisation delete removes the blobs, Postgres tests skip under -short
TestOrganisationLogo asserted only that the system_files rows go; it now also waits for the commit and asserts the logo's and avatar's originals and thumbnails are gone from the bucket. The Postgres tests of the gap closure call t.Skip under -short instead of returning, so -short runs report them as skipped, not passed.
This commit is contained in:
@@ -325,6 +325,6 @@ go test ./... # full run, starts Postgres with testcontainers (needs D
|
||||
|
||||
`TestBackendLinkSmoke` runs the account link end to end: My account offers `link`, the action answers the site URL with a 64-hex code, the show route answers the administrator's login and expiry, a session older than the request gets `stale_session`, a fresh one links (the published resolver answers the administrator), the code is one-shot, a second frontend user gets `already_linked`, an expired code is 404, unlink and deleting the frontend user remove the link, and the subject events fire. `TestBackendLinkAuthTimeSmoke` checks the `auth_time` proof: a session signed in before the request and refreshed after it gets `stale_session`, a sign-in after the request links, `POST /backend-link/show` answers for the code in the body, and My account's URL carries the code in the fragment. `TestBackendLinkAdminSmoke` covers the Administrators screen actions: offered only as they apply and only with `golem15.users.manage_backend_links`, the case-insensitive email match, `linked_by`, `BackendLinkedEvent`, the 422 refusals and the unlink. `TestUserMCPGuardSmoke` covers the `user` guard provider (requirements, the scope and group-permission checks, the dispatch credential, personal token resolution), `GET /token/me` and the revoke, group and user subject events. `TestFrontendPermissionCatalogSmoke` boots a test plugin that declares frontend permission codes and checks both editors' options. `TestMemberTokenScopesSmoke` checks the member token scope catalog (duplicate, built-in and bad names fail) and, with Docker, minting, the scope check, the list, `GET /tokens/scopes` and a second application refusing the scope. The others need Docker.
|
||||
|
||||
`TestMemberTokenScopesBoot` (no Docker) checks that the scope Boot step fails for a name two plugins declare (naming both), for a redeclared `read`, `write` or `ai`, for a bad name, an empty label and a missing phrase key. With Docker: `TestMemberTokenScopes` walks the member token scope acceptance list item by item (mint and `middleware.Scope`, the 403 message, the 422 for an undeclared scope, the list, discovery in en and pl, the per-application catalog and the legacy fallback); `TestBackendLinkAuthTime` checks the link proof against login, request, refresh and re-login orders, a token without `auth_time` and another user's session token; `TestBackendLinkShowPost` checks that `POST /backend-link/show` answers like the GET route with `Cache-Control: no-store, private`, the fragment URL and `backend_links.page_path`; `TestAccessRevocation` checks `AccessRevokedEvent` from the ban action, the password reset, the password change and an administrator setting a password (ids, `Before`, reason, the transaction, the subject event after commit, the rollback on a failing listener) and that a banned user loses the backend link until unbanned; `TestUserMCPGuardClientIP` checks `last_used_ip` on the MCP personal-token path; `TestAPITokenRevokeEvent` checks the token-specific revoked event of `DELETE /tokens/{id}`; `TestOrganisationLogo` checks the logo field, the upload, `OrganisationLogoFields` with and without a logo, and that deleting an organisation removes its avatar and logo attachments.
|
||||
`TestMemberTokenScopesBoot` (no Docker) checks that the scope Boot step fails for a name two plugins declare (naming both), for a redeclared `read`, `write` or `ai`, for a bad name, an empty label and a missing phrase key. With Docker: `TestMemberTokenScopes` walks the member token scope acceptance list item by item (mint and `middleware.Scope`, the 403 message, the 422 for an undeclared scope, the list, discovery in en and pl, the per-application catalog and the legacy fallback); `TestBackendLinkAuthTime` checks the link proof against login, request, refresh and re-login orders, a token without `auth_time` and another user's session token; `TestBackendLinkShowPost` checks that `POST /backend-link/show` answers like the GET route with `Cache-Control: no-store, private`, the fragment URL and `backend_links.page_path`; `TestAccessRevocation` checks `AccessRevokedEvent` from the ban action, the password reset, the password change and an administrator setting a password (ids, `Before`, reason, the transaction, the subject event after commit, the rollback on a failing listener) and that a banned user loses the backend link until unbanned; `TestUserMCPGuardClientIP` checks `last_used_ip` on the MCP personal-token path; `TestAPITokenRevokeEvent` checks the token-specific revoked event of `DELETE /tokens/{id}`; `TestOrganisationLogo` checks the logo field, the upload, `OrganisationLogoFields` with and without a logo, the URL fallback when the thumbnail fails, and that deleting an organisation removes its avatar and logo attachments, the rows and, once the delete commits, the blobs and thumbnails.
|
||||
|
||||
The admin tests (`TestAdmin*`) boot the plugin with the admin API mounted on a database of their own and sign in backend administrators with a chosen permission set; the helpers are in `admin_harness_test.go`.
|
||||
|
||||
@@ -110,10 +110,10 @@ func wholeSecondsUntruncated(t *testing.T, env *adminEnv, id uint, before time.T
|
||||
// revoked subject event per user after the commit; the ban fires only for
|
||||
// newly banned users; a failing listener rolls a password reset back; and
|
||||
// a banned linked user loses the backend link until unbanned. Postgres
|
||||
// only: under -short nothing runs.
|
||||
// only: skipped under -short.
|
||||
func TestAccessRevocation(t *testing.T) {
|
||||
if testing.Short() {
|
||||
return
|
||||
t.Skip("Postgres")
|
||||
}
|
||||
env := newAdminEnv(t)
|
||||
spy := env.spySubjects()
|
||||
|
||||
@@ -12,10 +12,10 @@ import (
|
||||
// TestAPITokenRevokeEvent pins plan 08's token-specific MCP revocation
|
||||
// (C-IN-02, user side): DELETE /tokens/{id} fires exactly one revoked
|
||||
// subject event for the user carrying that TokenID, so only the sessions of
|
||||
// that token end. Postgres only: under -short nothing runs.
|
||||
// that token end. Postgres only: skipped under -short.
|
||||
func TestAPITokenRevokeEvent(t *testing.T) {
|
||||
if testing.Short() {
|
||||
return
|
||||
t.Skip("Postgres")
|
||||
}
|
||||
env := newAdminEnv(t)
|
||||
spy := env.spySubjects()
|
||||
|
||||
@@ -20,10 +20,10 @@ import (
|
||||
// session signed in before the request stays stale through a refresh, a
|
||||
// sign-in after the request confirms (also after a refresh), a token
|
||||
// without auth_time is stale, and a session token of another user than the
|
||||
// authenticated one is 401. Postgres only: under -short nothing runs.
|
||||
// authenticated one is 401. Postgres only: skipped under -short.
|
||||
func TestBackendLinkAuthTime(t *testing.T) {
|
||||
if testing.Short() {
|
||||
return
|
||||
t.Skip("Postgres")
|
||||
}
|
||||
env := newAdminEnv(t)
|
||||
login := func(t *testing.T, user *models.User) string {
|
||||
|
||||
@@ -20,11 +20,11 @@ import (
|
||||
// show or confirm POST is ignored (RC-IN-01); My account answers the link as
|
||||
// <frontend_url>/link-admin#code=<64 hex> with no query string;
|
||||
// golem15.user.backend_links.page_path changes the path, and a value
|
||||
// without a leading slash gets one. Postgres only: under -short nothing
|
||||
// runs.
|
||||
// without a leading slash gets one. Postgres only: skipped under
|
||||
// -short.
|
||||
func TestBackendLinkShowPost(t *testing.T) {
|
||||
if testing.Short() {
|
||||
return
|
||||
t.Skip("Postgres")
|
||||
}
|
||||
env := newAdminEnv(t)
|
||||
show := func(t *testing.T, code string) (get, post *struct {
|
||||
|
||||
@@ -13,10 +13,10 @@ import (
|
||||
// personal-token path (C-IN-04): MCPResolvePersonalToken stamps
|
||||
// last_used_ip from towel.ClientIP, as the api_token guard does, and leaves
|
||||
// the column as it was when the context carries no address. Postgres only:
|
||||
// under -short nothing runs.
|
||||
// skipped under -short.
|
||||
func TestUserMCPGuardClientIP(t *testing.T) {
|
||||
if testing.Short() {
|
||||
return
|
||||
t.Skip("Postgres")
|
||||
}
|
||||
env := newAdminEnv(t)
|
||||
resolver, ok := env.app.Lookup[pact.MCPPersonalTokenResolver]()
|
||||
|
||||
@@ -59,10 +59,10 @@ type scopeList struct {
|
||||
// TestMemberTokenScopes pins E-2's acceptance list item by item (each
|
||||
// subtest quotes it) with a plugin declaring servers:read and servers:write,
|
||||
// plus the per-application catalog and the legacy MintAPIToken fallback.
|
||||
// Postgres only: under -short nothing runs.
|
||||
// Postgres only: skipped under -short.
|
||||
func TestMemberTokenScopes(t *testing.T) {
|
||||
if testing.Short() {
|
||||
return
|
||||
t.Skip("Postgres")
|
||||
}
|
||||
t.Cleanup(classes.ResetDeclaredAPITokenScopes)
|
||||
env := newAdminEnvWith(t, []string{"acme.inventory"})
|
||||
|
||||
@@ -4,16 +4,20 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.golem15.com/golem15/sm-user-plugin/classes"
|
||||
"git.golem15.com/golem15/sm-user-plugin/models"
|
||||
"git.golem15.com/golem15/summercms/modules/cabana"
|
||||
"git.golem15.com/golem15/summercms/modules/lagoon/attach"
|
||||
"gocloud.dev/blob"
|
||||
"gocloud.dev/blob/memblob"
|
||||
)
|
||||
|
||||
@@ -38,6 +42,23 @@ func (e *adminEnv) uploadOrganisationFile(t *testing.T, token string, id uint, f
|
||||
}
|
||||
}
|
||||
|
||||
// blobKeysUnder lists the bucket's keys under prefix.
|
||||
func (e *adminEnv) blobKeysUnder(t *testing.T, prefix string) []string {
|
||||
t.Helper()
|
||||
var keys []string
|
||||
it := e.bucket.List(&blob.ListOptions{Prefix: prefix})
|
||||
for {
|
||||
obj, err := it.Next(t.Context())
|
||||
if errors.Is(err, io.EOF) {
|
||||
return keys
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
keys = append(keys, obj.Key)
|
||||
}
|
||||
}
|
||||
|
||||
// organisationFiles counts an organisation's system_files rows of field.
|
||||
func (e *adminEnv) organisationFiles(t *testing.T, id uint, field string) int64 {
|
||||
t.Helper()
|
||||
@@ -55,11 +76,11 @@ func (e *adminEnv) organisationFiles(t *testing.T, id uint, field string) int64
|
||||
// answers has_logo, the disk name and a URL, or false and nils without a
|
||||
// logo, falling back to the original's public URL when the thumbnail fails;
|
||||
// the avatar keeps working beside it; and deleting the organisation
|
||||
// removes the logo attachment as it does the avatar. Postgres only: under
|
||||
// -short nothing runs.
|
||||
// removes the logo attachment as it does the avatar, rows and blobs.
|
||||
// Postgres only.
|
||||
func TestOrganisationLogo(t *testing.T) {
|
||||
if testing.Short() {
|
||||
return
|
||||
t.Skip("Postgres")
|
||||
}
|
||||
env := newAdminEnv(t)
|
||||
token := env.admin(t, permAccessUsers)
|
||||
@@ -161,9 +182,30 @@ func TestOrganisationLogo(t *testing.T) {
|
||||
t.Fatalf("failing logo thumbnail = %q %v %v, want %s", got, ok, err, attach.PublicURL(attach.BlobKey(logoDisk)))
|
||||
}
|
||||
|
||||
for _, disk := range []string{logoDisk, avatarDisk} {
|
||||
if keys := env.blobKeysUnder(t, attach.PartitionDirectory(disk)); len(keys) == 0 {
|
||||
t.Fatalf("no blob stored for %s before the delete", disk)
|
||||
}
|
||||
}
|
||||
|
||||
env.expect(t, http.StatusOK, http.MethodDelete, organisationPath(org.ID, ""), token, nil)
|
||||
if logos, avatars := env.organisationFiles(t, org.ID, "logo"), env.organisationFiles(t, org.ID, "avatar"); logos != 0 || avatars != 0 {
|
||||
t.Fatalf("after the delete: logo rows %d, avatar rows %d, want none", logos, avatars)
|
||||
}
|
||||
// The blobs (originals and thumbnails) go once the delete commits
|
||||
// (RC-IN-05).
|
||||
deadline := time.Now().Add(5 * time.Second)
|
||||
for _, disk := range []string{logoDisk, avatarDisk} {
|
||||
for {
|
||||
keys := env.blobKeysUnder(t, attach.PartitionDirectory(disk))
|
||||
if len(keys) == 0 {
|
||||
break
|
||||
}
|
||||
if time.Now().After(deadline) {
|
||||
t.Fatalf("blobs left after the delete: %v", keys)
|
||||
}
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user