Files
MilkyShots/Lactose/WepApiTest.http
T
REDCODE b6386aad98 refactor(asset): rename fs-browse to directory and remove the DB-path browse endpoint
- Rename the filesystem browse route from /api/asset/fs-browse to /api/asset/directory
  (controller route, client URL, REST tests, and service log label).
- Remove the old /api/asset/browse endpoint and its BrowseAssets/GetDirectoryNames
  repository code, plus the client BrowseAsync method. Repoint AlbumAssetPicker's
  admin/curator folder browse to the /directory endpoint.
- Drop the now-unused pg_catalog.split_part DbFunction mapping.
- Refresh .env GIT_VERSION.
2026-08-21 01:00:38 +02:00

2284 lines
65 KiB
HTTP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@WepApiTest_HostAddress = http://localhost:5162
# =============================================================================
# SETUP — Create three users with distinct roles
# =============================================================================
### 1 Login as seeded admin
POST {{WepApiTest_HostAddress}}/api/auth/login
Content-Type: application/json
{
"identifier": "admin",
"password": "admin"
}
> {%
client.global.set("admin_token", jsonPath(response.body, "$.token"));
client.global.set("admin_id", jsonPath(response.body, "$.userId"));
client.test("Login as seeded admin", function () {
client.assert(response.status === 200)
});
%}
### 2 Create curator user (admin only)
< {%
client.global.set("curatorName", $random.alphabetic(15));
client.global.set("curatorPwd", "test" + $random.alphabetic(5));
%}
PUT {{WepApiTest_HostAddress}}/api/user
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"username": "{{curatorName}}",
"email": "{{curatorName}}@redcode.com",
"password": "{{curatorPwd}}",
"accessLevel": 2
}
> {%
client.global.set("curator_id", jsonPath(response.body, "$"));
client.test("Create curator user as admin", function () {
client.assert(response.status === 200)
});
%}
### 3 Login as curator
POST {{WepApiTest_HostAddress}}/api/auth/login
Content-Type: application/json
{
"identifier": "{{curatorName}}",
"password": "{{curatorPwd}}"
}
> {%
client.global.set("curator_token", jsonPath(response.body, "$.token"));
client.test("Login as curator", function () {
client.assert(response.status === 200)
});
%}
### 3.5 Enable registration for testing
POST {{WepApiTest_HostAddress}}/api/settings
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"name": "User Registration Enabled",
"value": "true",
"description": "Sets if user registration is enabled or not.",
"type": 2,
"displayType": 5
}
> {%
client.test("Enable registration", function () {
client.assert(response.status === 200);
});
%}
### 4 Register regular user
< {%
client.global.set("userName", $random.alphabetic(15));
client.global.set("userEmail", $random.alphabetic(5) + "@redcode.com");
client.global.set("userPwd", "test" + $random.alphabetic(5));
%}
POST {{WepApiTest_HostAddress}}/api/auth/register
Content-Type: application/json
{
"username": "{{userName}}",
"email": "{{userEmail}}",
"password": "{{userPwd}}"
}
> {%
client.test("Register regular user", function () {
client.assert(response.status === 200)
});
%}
### 5 Login as regular user
POST {{WepApiTest_HostAddress}}/api/auth/login
Content-Type: application/json
{
"identifier": "{{userName}}",
"password": "{{userPwd}}"
}
> {%
client.global.set("user_token", jsonPath(response.body, "$.token"));
client.global.set("user_refresh", jsonPath(response.body, "$.refreshToken"));
client.global.set("user_id", jsonPath(response.body, "$.userId"));
client.test("Login as regular user", function () {
client.assert(response.status === 200)
});
%}
# =============================================================================
# AUTH TESTS
# =============================================================================
### 6 Wrong auth route should 404
POST {{WepApiTest_HostAddress}}/api/auth
Content-Type: application/json
{}
> {%
client.test("Wrong auth route returns 404", function () {
client.assert(response.status === 404)
});
%}
### 7 Login with valid token (re-authentication)
POST {{WepApiTest_HostAddress}}/api/auth/login
Authorization: Bearer {{user_token}}
Content-Type: application/json
{"identifier": "{{userName}}", "password": "{{userPwd}}"}
> {%
client.test("Re-authenticate with valid token", function () {
client.assert(response.status === 200)
});
%}
### 8 Logout
POST {{WepApiTest_HostAddress}}/api/auth/logout
Authorization: Bearer {{user_token}}
> {%
client.test("Logout", function () {
client.assert(response.status === 200)
});
%}
### 9 Re-login user after logout
POST {{WepApiTest_HostAddress}}/api/auth/login
Content-Type: application/json
{
"identifier": "{{userName}}",
"password": "{{userPwd}}"
}
> {%
client.global.set("user_token", jsonPath(response.body, "$.token"));
client.global.set("user_refresh", jsonPath(response.body, "$.refreshToken"));
client.global.set("user_id", jsonPath(response.body, "$.userId"));
client.test("Re-login user after logout", function () {
client.assert(response.status === 200)
});
%}
### 10 Refresh token with empty body (should be 400)
POST {{WepApiTest_HostAddress}}/api/auth/refresh
Content-Type: application/json
{
"userId": "",
"refreshToken": ""
}
> {%
client.test("Refresh token with empty body returns 400", function () {
client.assert(response.status === 400)
});
%}
### 11 Refresh token with valid data
POST {{WepApiTest_HostAddress}}/api/auth/refresh
Content-Type: application/json
{
"userId": "{{user_id}}",
"refreshToken": "{{user_refresh}}"
}
> {%
client.global.set("user_token", jsonPath(response.body, "$.token"));
client.test("Refresh token with valid data", function () {
client.assert(response.status === 200)
client.assert(jsonPath(response.body, "$.token") != null)
});
%}
# =============================================================================
# USER CRUD TESTS
# =============================================================================
### 12 Get all users as admin (should succeed)
GET {{WepApiTest_HostAddress}}/api/user?page=0&pageSize=5
Authorization: Bearer {{admin_token}}
> {%
client.test("Get all users as admin", function () {
client.assert(response.status === 200)
});
%}
### 13 Get all users as regular user (should 403)
GET {{WepApiTest_HostAddress}}/api/user
Authorization: Bearer {{user_token}}
> {%
client.test("Get all users as regular user should 403", function () {
client.assert(response.status === 403)
});
%}
### 14 Get all users as curator (should succeed — curators now have access)
GET {{WepApiTest_HostAddress}}/api/user?page=0&pageSize=5
Authorization: Bearer {{curator_token}}
> {%
client.test("Get all users as curator", function () {
client.assert(response.status === 200)
});
%}
### 15 Create user as regular user (should 403)
< {%
client.global.set("userCreateAttempt", $random.alphabetic(10));
%}
PUT {{WepApiTest_HostAddress}}/api/user
Authorization: Bearer {{user_token}}
Content-Type: application/json
{
"username": "{{userCreateAttempt}}",
"email": "{{userCreateAttempt}}@redcode.com",
"password": "qwertyu",
"accessLevel": 0
}
> {%
client.test("Create user as regular user should 403", function () {
client.assert(response.status === 403)
});
%}
### 16 Create user as curator (should 403)
< {%
client.global.set("curatorCreateAttempt", $random.alphabetic(10));
%}
PUT {{WepApiTest_HostAddress}}/api/user
Authorization: Bearer {{curator_token}}
Content-Type: application/json
{
"username": "{{curatorCreateAttempt}}",
"email": "{{curatorCreateAttempt}}@redcode.com",
"password": "test1234",
"accessLevel": 0
}
> {%
client.test("Create user as curator should 403", function () {
client.assert(response.status === 403)
});
%}
### 17 Get own user profile as regular user
GET {{WepApiTest_HostAddress}}/api/user/{{user_id}}
Authorization: Bearer {{user_token}}
> {%
client.test("Get own profile as regular user", function () {
client.assert(response.status === 200)
});
%}
### 18 Update own profile as regular user
POST {{WepApiTest_HostAddress}}/api/user/update
Authorization: Bearer {{user_token}}
Content-Type: application/json
{
"id": "{{user_id}}",
"username": "{{userName}}"
}
> {%
client.test("Update own profile as regular user", function () {
client.assert(response.status === 200)
});
%}
> {%
client.test("Update own profile as regular user", function () {
client.assert(response.status === 200)
});
%}
# =============================================================================
# PERSON TESTS
# =============================================================================
### 19 Create person as regular user (should 403)
PUT {{WepApiTest_HostAddress}}/api/person
Authorization: Bearer {{user_token}}
Content-Type: application/json
{
"name": "hacker_create"
}
> {%
client.test("Create person as regular user should 403", function () {
client.assert(response.status === 403)
});
%}
### 20 Create person as curator (should succeed)
< {%
client.global.set("curatorPerson", $random.alphabetic(15));
%}
PUT {{WepApiTest_HostAddress}}/api/person
Authorization: Bearer {{curator_token}}
Content-Type: application/json
{
"name": "{{curatorPerson}}"
}
> {%
client.global.set("curatorPersonId", jsonPath(response.body, "$"));
client.test("Create person as curator", function () {
client.assert(response.status === 200)
});
%}
### 21 Delete person as curator (cleanup)
DELETE {{WepApiTest_HostAddress}}/api/person/{{curatorPersonId}}
Authorization: Bearer {{curator_token}}
> {%
client.test("Delete person as curator", function () {
client.assert(response.status === 200)
});
%}
### 22 Create test person as admin
PUT {{WepApiTest_HostAddress}}/api/person
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"name": "Test Cosplayer Created"
}
> {%
client.global.set("testPersonId", jsonPath(response.body, "$"));
client.test("Create test person as admin", function () {
client.assert(response.status === 200)
client.assert(jsonPath(response.body, "$") != null)
});
%}
### 23 Get all people as anonymous (should 401 — controller requires auth)
GET {{WepApiTest_HostAddress}}/api/person?page=0&pageSize=5
> {%
client.test("Get all people as anonymous should 401", function () {
client.assert(response.status === 401)
});
%}
### 24 Get all people as regular user
GET {{WepApiTest_HostAddress}}/api/person?page=0&pageSize=5
Authorization: Bearer {{user_token}}
> {%
client.test("Get all people as regular user", function () {
client.assert(response.status === 200)
});
%}
### 25 Get all people as curator
GET {{WepApiTest_HostAddress}}/api/person?page=0&pageSize=5
Authorization: Bearer {{curator_token}}
> {%
client.test("Get all people as curator", function () {
client.assert(response.status === 200)
});
%}
### 26 Get all people as admin
GET {{WepApiTest_HostAddress}}/api/person?page=0&pageSize=5
Authorization: Bearer {{admin_token}}
> {%
client.test("Get all people as admin", function () {
client.assert(response.status === 200)
});
%}
### 27 Get test person by ID as admin
GET {{WepApiTest_HostAddress}}/api/person/{{testPersonId}}
Authorization: Bearer {{admin_token}}
> {%
client.test("Get test person by ID as admin", function () {
client.assert(response.status === 200)
client.assert(jsonPath(response.body, "$.name") === "Test Cosplayer Created")
});
%}
### 28 Get non-existent person (should 404)
GET {{WepApiTest_HostAddress}}/api/person/00000000-0000-0000-0000-000000000000
Authorization: Bearer {{admin_token}}
> {%
client.test("Get non-existent person", function () {
client.assert(response.status === 404)
});
%}
### 29 Update person as regular user (should 403)
POST {{WepApiTest_HostAddress}}/api/person/{{testPersonId}}
Authorization: Bearer {{user_token}}
Content-Type: application/json
{
"name": "hacker_attempt"
}
> {%
client.test("Update person as regular user should 403", function () {
client.assert(response.status === 403)
});
%}
### 30 Update person as curator (should succeed)
POST {{WepApiTest_HostAddress}}/api/person/{{testPersonId}}
Authorization: Bearer {{curator_token}}
Content-Type: application/json
{
"name": "Updated by curator"
}
> {%
client.test("Update person as curator", function () {
client.assert(response.status === 200)
});
%}
### 31 Update person as admin
POST {{WepApiTest_HostAddress}}/api/person/{{testPersonId}}
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"name": "Updated via API Test"
}
> {%
client.test("Update person as admin", function () {
client.assert(response.status === 200)
});
%}
### 32 Verify person name update
GET {{WepApiTest_HostAddress}}/api/person/{{testPersonId}}
Authorization: Bearer {{admin_token}}
> {%
client.test("Verify person name update", function () {
client.assert(response.status === 200)
client.assert(jsonPath(response.body, "$.name") === "Updated via API Test")
});
%}
### 33 Update non-existent person (should 404)
POST {{WepApiTest_HostAddress}}/api/person/00000000-0000-0000-0000-000000000000
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"name": "ghost"
}
> {%
client.test("Update non-existent person", function () {
client.assert(response.status === 404)
});
%}
### 34 Delete person as regular user (should 403)
DELETE {{WepApiTest_HostAddress}}/api/person/{{testPersonId}}
Authorization: Bearer {{user_token}}
> {%
client.test("Delete person as regular user should 403", function () {
client.assert(response.status === 403)
});
%}
### 35 Bulk update person as regular user (should 403)
POST {{WepApiTest_HostAddress}}/api/person
Authorization: Bearer {{user_token}}
Content-Type: application/json
{
"ids": ["{{testPersonId}}"],
"data": { "name": "hacker_bulk" }
}
> {%
client.test("Bulk update person as regular user should 403", function () {
client.assert(response.status === 403)
});
%}
### 36 Bulk update person as admin
POST {{WepApiTest_HostAddress}}/api/person
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"ids": ["{{testPersonId}}"],
"data": { "name": "Bulk Updated Person" }
}
> {%
client.test("Bulk update person as admin", function () {
client.assert(response.status === 200)
});
%}
### 37 Bulk delete person as regular user (should 403)
DELETE {{WepApiTest_HostAddress}}/api/person
Authorization: Bearer {{user_token}}
Content-Type: application/json
["{{testPersonId}}"]
> {%
client.test("Bulk delete person as regular user should 403", function () {
client.assert(response.status === 403)
});
%}
# =============================================================================
# ALBUM TESTS
# =============================================================================
### 38 Create album as regular user (should 403)
< {%
client.global.set("albumHack", $random.alphabetic(10));
%}
PUT {{WepApiTest_HostAddress}}/api/album
Authorization: Bearer {{user_token}}
Content-Type: application/json
{
"name": "{{albumHack}}"
}
> {%
client.test("Create album as regular user should 403", function () {
client.assert(response.status === 403)
});
%}
### 39 Create album as curator (should succeed)
< {%
client.global.set("curatorAlbum", $random.alphabetic(15));
%}
PUT {{WepApiTest_HostAddress}}/api/album
Authorization: Bearer {{curator_token}}
Content-Type: application/json
{
"name": "{{curatorAlbum}}"
}
> {%
client.global.set("curatorAlbumId", jsonPath(response.body, "$"));
client.test("Create album as curator", function () {
client.assert(response.status === 200)
});
%}
### 40 Delete album as curator (cleanup)
DELETE {{WepApiTest_HostAddress}}/api/album/{{curatorAlbumId}}
Authorization: Bearer {{curator_token}}
> {%
client.test("Delete album as curator", function () {
client.assert(response.status === 200)
});
%}
### 41 Create test album as admin
PUT {{WepApiTest_HostAddress}}/api/album
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"name": "Test Album for E2E"
}
> {%
client.global.set("testAlbumId", jsonPath(response.body, "$"));
client.test("Create test album as admin", function () {
client.assert(response.status === 200)
client.assert(jsonPath(response.body, "$") != null)
});
%}
### 42 Assign test album to test person
POST {{WepApiTest_HostAddress}}/api/album/{{testAlbumId}}
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"person": "{{testPersonId}}"
}
> {%
client.test("Assign album to person", function () {
client.assert(response.status === 200)
});
%}
### 43 Update album without removePerson field (should keep person link)
POST {{WepApiTest_HostAddress}}/api/album/{{testAlbumId}}
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"name": "Test Album Updated"
}
> {%
client.test("Update album without removePerson", function () {
client.assert(response.status === 200)
});
%}
### 44 Remove person assignment from album
POST {{WepApiTest_HostAddress}}/api/album/{{testAlbumId}}
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"removePerson": true
}
> {%
client.test("Unlink person from album", function () {
client.assert(response.status === 200)
});
%}
### 45 Get all albums as anonymous (should 401 — controller requires auth)
GET {{WepApiTest_HostAddress}}/api/album?page=0&pageSize=5
> {%
client.test("Get all albums as anonymous should 401", function () {
client.assert(response.status === 401)
});
%}
### 46 Get all albums as regular user
GET {{WepApiTest_HostAddress}}/api/album?page=0&pageSize=5
Authorization: Bearer {{user_token}}
> {%
client.test("Get all albums as regular user", function () {
client.assert(response.status === 200)
});
%}
### 47 Get all albums as curator
GET {{WepApiTest_HostAddress}}/api/album?page=0&pageSize=5
Authorization: Bearer {{curator_token}}
> {%
client.test("Get all albums as curator", function () {
client.assert(response.status === 200)
});
%}
### 48 Get all albums as admin
GET {{WepApiTest_HostAddress}}/api/album?page=0&pageSize=5
Authorization: Bearer {{admin_token}}
> {%
client.test("Get all albums as admin", function () {
client.assert(response.status === 200)
});
%}
### 49 Search albums as admin
GET {{WepApiTest_HostAddress}}/api/album?search=Test&pageSize=5
Authorization: Bearer {{admin_token}}
> {%
client.test("Search albums as admin", function () {
client.assert(response.status === 200)
});
%}
### 50 Get album by ID as admin
GET {{WepApiTest_HostAddress}}/api/album/{{testAlbumId}}
Authorization: Bearer {{admin_token}}
> {%
client.test("Get album by ID as admin", function () {
client.assert(response.status === 200)
client.assert(jsonPath(response.body, "$.id") != null)
});
%}
### 51 Get non-existent album (should 404)
GET {{WepApiTest_HostAddress}}/api/album/00000000-0000-0000-0000-000000000000
Authorization: Bearer {{admin_token}}
> {%
client.test("Get non-existent album", function () {
client.assert(response.status === 404)
});
%}
### 52 Delete test album as admin
DELETE {{WepApiTest_HostAddress}}/api/album/{{testAlbumId}}
Authorization: Bearer {{admin_token}}
> {%
client.test("Delete test album", function () {
client.assert(response.status === 200)
});
%}
### 53 Create bulk test album A
< {%
client.global.set("bulkAlbumA", $random.alphabetic(15));
%}
PUT {{WepApiTest_HostAddress}}/api/album
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"name": "bulk-{{bulkAlbumA}}"
}
> {%
client.global.set("bulkAlbumAId", jsonPath(response.body, "$"));
client.test("Create bulk test album A", function () {
client.assert(response.status === 200)
client.assert(jsonPath(response.body, "$") != null)
});
%}
### 54 Create bulk test album B
< {%
client.global.set("bulkAlbumB", $random.alphabetic(15));
%}
PUT {{WepApiTest_HostAddress}}/api/album
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"name": "bulk-{{bulkAlbumB}}"
}
> {%
client.global.set("bulkAlbumBId", jsonPath(response.body, "$"));
client.test("Create bulk test album B", function () {
client.assert(response.status === 200)
client.assert(jsonPath(response.body, "$") != null)
});
%}
### 55 Bulk assign both albums to test person
POST {{WepApiTest_HostAddress}}/api/album
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"ids": ["{{bulkAlbumAId}}", "{{bulkAlbumBId}}"],
"data": { "person": "{{testPersonId}}" }
}
> {%
client.test("Bulk assign albums to person", function () {
client.assert(response.status === 200)
});
%}
### 56 Verify bulk assignment — album A
GET {{WepApiTest_HostAddress}}/api/album/{{bulkAlbumAId}}
Authorization: Bearer {{admin_token}}
> {%
client.test("Verify bulk assignment — album A", function () {
client.assert(response.status === 200)
client.assert(jsonPath(response.body, "$.person") !== undefined, "person field should exist")
client.assert(jsonPath(response.body, "$.person") !== null, "person should be assigned")
});
%}
### 57 Verify bulk assignment — album B
GET {{WepApiTest_HostAddress}}/api/album/{{bulkAlbumBId}}
Authorization: Bearer {{admin_token}}
> {%
client.test("Verify bulk assignment — album B", function () {
client.assert(response.status === 200)
client.assert(jsonPath(response.body, "$.person") !== undefined, "person field should exist")
client.assert(jsonPath(response.body, "$.person") !== null, "person should be assigned")
});
%}
### 58 Delete bulk test album A
DELETE {{WepApiTest_HostAddress}}/api/album/{{bulkAlbumAId}}
Authorization: Bearer {{admin_token}}
> {%
client.test("Delete bulk test album A", function () {
client.assert(response.status === 200)
});
%}
### 59 Delete bulk test album B
DELETE {{WepApiTest_HostAddress}}/api/album/{{bulkAlbumBId}}
Authorization: Bearer {{admin_token}}
> {%
client.test("Delete bulk test album B", function () {
client.assert(response.status === 200)
});
%}
# =============================================================================
# TAG TESTS
# =============================================================================
### 60 Search tags as admin
GET {{WepApiTest_HostAddress}}/api/tag?search=test&pageSize=5
Authorization: Bearer {{admin_token}}
> {%
client.test("Search tags as admin", function () {
client.assert(response.status === 200)
});
%}
### 61 Create tag as regular user (should 403)
PUT {{WepApiTest_HostAddress}}/api/tag
Authorization: Bearer {{user_token}}
Content-Type: application/json
{
"name": "hacker-tag"
}
> {%
client.test("Create tag as regular user should 403", function () {
client.assert(response.status === 403)
});
%}
### 62 Create tag as curator (should succeed)
< {%
client.global.set("e2eTag", $random.alphabetic(10));
%}
PUT {{WepApiTest_HostAddress}}/api/tag
Authorization: Bearer {{curator_token}}
Content-Type: application/json
{
"name": "e2e-{{e2eTag}}"
}
> {%
client.test("Create tag as curator", function () {
client.assert(response.status === 200)
});
%}
# =============================================================================
# ASSET TESTS
# =============================================================================
### 63 Search assets as regular user
GET {{WepApiTest_HostAddress}}/api/asset?page=0&pageSize=5
Authorization: Bearer {{user_token}}
> {%
client.test("Search assets as regular user", function () {
client.assert(response.status === 200)
});
%}
### 64 Search assets as anonymous
GET {{WepApiTest_HostAddress}}/api/asset?page=0&pageSize=5
> {%
client.test("Search assets as anonymous", function () {
client.assert(response.status === 200)
});
%}
# =============================================================================
# MAINTENANCE TESTS — broken report, retry, filesystem browse, animated job
# =============================================================================
### 64.1 Get broken assets as admin
GET {{WepApiTest_HostAddress}}/api/asset/broken?page=0&pageSize=5
Authorization: Bearer {{admin_token}}
> {%
client.test("Get broken assets as admin", function () {
client.assert(response.status === 200);
client.assert(response.headers.get("X-Total-Count") !== null, "X-Total-Count header should be present");
});
%}
### 64.2 Get broken assets as regular user (should 403)
GET {{WepApiTest_HostAddress}}/api/asset/broken?page=0&pageSize=5
Authorization: Bearer {{user_token}}
> {%
client.test("Get broken assets as regular user (should 403)", function () {
client.assert(response.status === 403)
});
%}
### 64.3 Get broken assets with invalid pagination (should 400)
GET {{WepApiTest_HostAddress}}/api/asset/broken?page=-1&pageSize=5
Authorization: Bearer {{admin_token}}
> {%
client.test("Get broken assets with invalid pagination (should 400)", function () {
client.assert(response.status === 400)
});
%}
### 64.4 Retry broken assets with empty list (should 400)
POST {{WepApiTest_HostAddress}}/api/asset/broken/retry
Authorization: Bearer {{admin_token}}
Content-Type: application/json
[]
> {%
client.test("Retry broken assets with empty list (should 400)", function () {
client.assert(response.status === 400)
});
%}
### 64.5 Retry broken assets as regular user (should 403)
POST {{WepApiTest_HostAddress}}/api/asset/broken/retry
Authorization: Bearer {{user_token}}
Content-Type: application/json
["00000000-0000-0000-0000-000000000001"]
> {%
client.test("Retry broken assets as regular user (should 403)", function () {
client.assert(response.status === 403)
});
%}
### 64.6 Filesystem browse non-existent folder (should 404)
GET {{WepApiTest_HostAddress}}/api/asset/directory?folderId=00000000-0000-0000-0000-000000000001&path=&page=0&pageSize=5
Authorization: Bearer {{admin_token}}
> {%
client.test("Filesystem browse non-existent folder (should 404)", function () {
client.assert(response.status === 404)
});
%}
### 64.7 Filesystem browse invalid pagination (should 400)
GET {{WepApiTest_HostAddress}}/api/asset/directory?folderId=00000000-0000-0000-0000-000000000001&path=&page=0&pageSize=0
Authorization: Bearer {{admin_token}}
> {%
client.test("Filesystem browse invalid pagination (should 400)", function () {
client.assert(response.status === 400)
});
%}
### 64.8 Filesystem browse as regular user (should 403)
GET {{WepApiTest_HostAddress}}/api/asset/directory?folderId=00000000-0000-0000-0000-000000000001&path=&page=0&pageSize=5
Authorization: Bearer {{user_token}}
> {%
client.test("Filesystem browse as regular user (should 403)", function () {
client.assert(response.status === 403)
});
%}
### 64.9 Enqueue animated conversion job as admin
PUT {{WepApiTest_HostAddress}}/api/jobs
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{ "jobType": "AnimatedConversion", "parameters": [] }
> {%
client.test("Enqueue animated conversion job as admin", function () {
client.assert(response.status === 200)
});
%}
### 64.10 Enqueue animated conversion job as regular user (should 403)
PUT {{WepApiTest_HostAddress}}/api/jobs
Authorization: Bearer {{user_token}}
Content-Type: application/json
{ "jobType": "AnimatedConversion", "parameters": [] }
> {%
client.test("Enqueue animated conversion job as regular user (should 403)", function () {
client.assert(response.status === 403)
});
%}
# =============================================================================
# STATS TESTS
# =============================================================================
### 65 Get stats as admin
GET {{WepApiTest_HostAddress}}/api/stats
Authorization: Bearer {{admin_token}}
> {%
client.test("Get stats as admin", function () {
client.assert(response.status === 200)
});
%}
### 66 Get stats as curator
GET {{WepApiTest_HostAddress}}/api/stats
Authorization: Bearer {{curator_token}}
> {%
client.test("Get stats as curator", function () {
client.assert(response.status === 200)
});
%}
### 67 Get stats as regular user (should 403)
GET {{WepApiTest_HostAddress}}/api/stats
Authorization: Bearer {{user_token}}
> {%
client.test("Get stats as regular user should 403", function () {
client.assert(response.status === 403)
});
%}
# =============================================================================
# SETTINGS TESTS
# =============================================================================
### 68 Get settings as admin
GET {{WepApiTest_HostAddress}}/api/settings
Authorization: Bearer {{admin_token}}
> {%
client.test("Get settings as admin", function () {
client.assert(response.status === 200)
});
%}
### 69 Get settings as curator (should 403)
GET {{WepApiTest_HostAddress}}/api/settings
Authorization: Bearer {{curator_token}}
> {%
client.test("Get settings as curator should 403", function () {
client.assert(response.status === 403)
});
%}
### 70 Get settings as regular user (should 403)
GET {{WepApiTest_HostAddress}}/api/settings
Authorization: Bearer {{user_token}}
> {%
client.test("Get settings as regular user should 403", function () {
client.assert(response.status === 403)
});
%}
# =============================================================================
# DATA VISIBILITY TESTS
# =============================================================================
# These tests verify that each access level only sees the data they are
# authorized to see. All test data needed is created inline so the tests
# are self-contained regardless of database state.
# =============================================================================
# ---------------------------------------------------------------------------
# Setup: Create maintainer relationship for visibility tests
# ---------------------------------------------------------------------------
### 71 Setup: Assign test user as maintainer of test person
POST {{WepApiTest_HostAddress}}/api/person/{{testPersonId}}
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"name": "Bulk Updated Person",
"maintainerUserIds": ["{{user_id}}"]
}
> {%
client.test("Assign maintainer to test person", function () {
client.assert(response.status === 200)
});
%}
# ---------------------------------------------------------------------------
# User data visibility (tests 6568)
# ---------------------------------------------------------------------------
### 72 Regular user: Get own profile — Email visible, maintainedPersonIds visible (self-view)
GET {{WepApiTest_HostAddress}}/api/user/{{user_id}}
Authorization: Bearer {{user_token}}
> {%
client.test("User sees own email (self-view)", function () {
client.assert(response.status === 200)
client.assert(jsonPath(response.body, "$.email") != null, "Self email should be visible")
});
client.test("User sees own maintainedPersonIds (self-view)", function () {
client.assert(jsonPath(response.body, "$.maintainedPersonIds") != null, "Self maintainedPersonIds should be visible")
var ids = jsonPath(response.body, "$.maintainedPersonIds");
client.assert(ids.length > 0, "maintainedPersonIds should contain at least the test person")
});
client.test("User does NOT see own deletedAt (admin-only)", function () {
client.assert(response.body.deletedAt == null, "Self deletedAt should be hidden")
});
%}
### 73 Regular user: Get admin profile — Email, accessLevel, maintainedPersonIds hidden
GET {{WepApiTest_HostAddress}}/api/user/{{admin_id}}
Authorization: Bearer {{user_token}}
> {%
client.test("User does NOT see admin email", function () {
client.assert(response.status === 200)
client.assert(response.body.email === null, "Other user email should be null")
});
client.test("User does NOT see admin deletedAt", function () {
client.assert(response.body.deletedAt === null)
});
client.test("User does NOT see admin accessLevel (defaults to User=0)", function () {
client.assert(response.body.accessLevel === 0, "AccessLevel should default to 0 for other users")
});
client.test("User does NOT see admin maintainedPersonIds", function () {
client.assert(response.body.maintainedPersonIds === null)
});
%}
### 74 Curator: Get admin profile — Email and maintainedPersonIds hidden (not self, not admin)
GET {{WepApiTest_HostAddress}}/api/user/{{admin_id}}
Authorization: Bearer {{curator_token}}
> {%
client.test("Curator does NOT see admin email", function () {
client.assert(response.status === 200)
client.assert(response.body.email === null)
});
client.test("Curator does NOT see admin maintainedPersonIds", function () {
client.assert(response.body.maintainedPersonIds === null)
});
%}
### 75 Admin: Get regular user profile — full data visible (email, accessLevel)
GET {{WepApiTest_HostAddress}}/api/user/{{user_id}}
Authorization: Bearer {{admin_token}}
> {%
client.test("Admin sees other user's email", function () {
client.assert(response.status === 200)
client.assert(jsonPath(response.body, "$.email") != null)
});
client.test("Admin sees other user's accessLevel", function () {
client.assert(jsonPath(response.body, "$.accessLevel") != null)
});
%}
# ---------------------------------------------------------------------------
# Maintainer-role data visibility (tests 68.568.8)
# ---------------------------------------------------------------------------
### 68.5. Promote regular user to Maintainer
POST {{WepApiTest_HostAddress}}/api/user/update
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"id": "{{user_id}}",
"accessLevel": 1
}
> {%
client.test("Promote user to Maintainer", function () {
client.assert(response.status === 200)
});
%}
### 68.6. Re-login as promoted maintainer
POST {{WepApiTest_HostAddress}}/api/auth/login
Content-Type: application/json
{
"identifier": "{{userName}}",
"password": "{{userPwd}}"
}
> {%
client.global.set("maintainer_token", jsonPath(response.body, "$.token"));
client.test("Login as maintainer", function () {
client.assert(response.status === 200)
});
%}
### 68.7. Maintainer: Get admin profile — email, accessLevel, maintainedPersonIds hidden
GET {{WepApiTest_HostAddress}}/api/user/{{admin_id}}
Authorization: Bearer {{maintainer_token}}
> {%
client.test("Maintainer does NOT see admin email", function () {
client.assert(response.status === 200)
client.assert(response.body.email === null, "Other user email should be null")
});
client.test("Maintainer does NOT see admin deletedAt", function () {
client.assert(response.body.deletedAt === null)
});
client.test("Maintainer does NOT see admin accessLevel (defaults to User=0)", function () {
client.assert(response.body.accessLevel === 0, "AccessLevel should default to 0 for non-admin viewing other user")
});
client.test("Maintainer does NOT see admin maintainedPersonIds", function () {
client.assert(response.body.maintainedPersonIds === null)
});
%}
### 68.8. Demote maintainer back to regular user
POST {{WepApiTest_HostAddress}}/api/user/update
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"id": "{{user_id}}",
"accessLevel": 0
}
> {%
client.test("Demote maintainer back to User", function () {
client.assert(response.status === 200)
});
%}
# ---------------------------------------------------------------------------
# Person data visibility (tests 6971)
# ---------------------------------------------------------------------------
### 76 Regular user: Get person detail — maintainerUserIds public, Visibility hidden
GET {{WepApiTest_HostAddress}}/api/person/{{testPersonId}}
Authorization: Bearer {{user_token}}
> {%
client.test("User gets person detail", function () {
client.assert(response.status === 200)
});
client.test("User sees maintainerUserIds (public info)", function () {
client.assert(response.body.maintainerUserIds != null, "maintainerUserIds should be visible to all users")
});
client.test("User sees maintainerUsernames (public info)", function () {
client.assert(response.body.maintainerUsernames != null, "maintainerUsernames should be visible to all users")
});
client.test("User does NOT see visibility (sensitive)", function () {
client.assert(response.body.visibility === null, "visibility should be null for regular user")
});
%}
### 77 Curator: Get person detail — maintainerUserIds and visibility visible
GET {{WepApiTest_HostAddress}}/api/person/{{testPersonId}}
Authorization: Bearer {{curator_token}}
> {%
client.test("Curator gets person detail", function () {
client.assert(response.status === 200)
});
client.test("Curator sees maintainerUserIds", function () {
client.assert(jsonPath(response.body, "$.maintainerUserIds") != null)
});
client.test("Curator sees visibility", function () {
client.assert(jsonPath(response.body, "$.visibility") != null, "visibility should be non-null for curator")
});
%}
### 78 Admin: Get person detail — all fields visible
GET {{WepApiTest_HostAddress}}/api/person/{{testPersonId}}
Authorization: Bearer {{admin_token}}
> {%
client.test("Admin gets person detail", function () {
client.assert(response.status === 200)
});
client.test("Admin sees maintainerUserIds", function () {
client.assert(jsonPath(response.body, "$.maintainerUserIds") != null)
});
client.test("Admin sees visibility value", function () {
client.assert(jsonPath(response.body, "$.visibility") != null)
});
%}
# ---------------------------------------------------------------------------
# Asset data visibility (tests 7277)
# ---------------------------------------------------------------------------
### 79 Admin: Find first asset and verify uploader is loaded
GET {{WepApiTest_HostAddress}}/api/asset?page=0&pageSize=1
Authorization: Bearer {{admin_token}}
> {%
var firstAsset = response.body[0];
var assetId = firstAsset != null ? firstAsset.id : null;
client.global.set("testAssetId", assetId);
client.global.set("hasAssets", assetId != null ? "true" : "false");
client.test("Asset search succeeds", function () {
client.assert(response.status === 200)
});
%}
### 80 Regular user: Get asset detail — FileName hidden, uploader email hidden
GET {{WepApiTest_HostAddress}}/api/asset/{{testAssetId}}
Authorization: Bearer {{user_token}}
> {%
// Only run assertions if an asset was found — skip gracefully otherwise
var hasAssets = client.global.get("hasAssets");
if (hasAssets === "true") {
client.test("User gets asset detail", function () {
client.assert(response.status === 200)
});
client.test("User does NOT see original fileName", function () {
client.assert(response.body.fileName === null, "fileName should be null for regular user but was '" + response.body.fileName + "'")
});
client.test("User does NOT see uploader email", function () {
client.assert(response.body.uploadedBy === null || response.body.uploadedBy.email === null, "uploader email should be null for regular user")
});
client.test("User does NOT see asset visibility", function () {
client.assert(response.body.visibility === null)
});
} else {
client.test("No assets in database — skipping asset visibility tests", function () {
client.assert(true)
});
}
%}
### 81 Curator: Get asset detail — FileName visible, uploader email hidden (unless self)
GET {{WepApiTest_HostAddress}}/api/asset/{{testAssetId}}
Authorization: Bearer {{curator_token}}
> {%
var hasAssets = client.global.get("hasAssets");
if (hasAssets === "true") {
client.test("Curator gets asset detail", function () {
client.assert(response.status === 200)
});
client.test("Curator sees original fileName", function () {
client.assert(response.body.fileName != null, "fileName should be visible to curator but was null")
});
client.test("Curator does NOT see uploader email (unless self)", function () {
client.assert(response.body.uploadedBy == null || response.body.uploadedBy.email === null, "uploader email should be null for curator viewing another user's asset")
});
} else {
client.test("No assets in database — skipping asset visibility tests", function () {
client.assert(true)
});
}
%}
### 82 Admin: Get asset detail — FileName and uploader email visible
GET {{WepApiTest_HostAddress}}/api/asset/{{testAssetId}}
Authorization: Bearer {{admin_token}}
> {%
var hasAssets = client.global.get("hasAssets");
if (hasAssets === "true") {
client.test("Admin gets asset detail", function () {
client.assert(response.status === 200)
});
client.test("Admin sees original fileName", function () {
client.assert(response.body.fileName != null)
});
client.test("Admin sees uploader email (or placeholder if server-scanned)", function () {
// uploadedBy is always present (real user or "deleted" placeholder).
// If it's a real uploader (non-empty GUID), email must be visible.
// If server-scanned (Guid.Empty placeholder), email can be null.
var uploader = response.body.uploadedBy;
client.assert(uploader != null, "uploadedBy field must be present");
if (uploader.id !== "00000000-0000-0000-0000-000000000000") {
client.assert(uploader.email != null, "real uploader email should be visible to admin")
}
});
client.test("Admin sees asset visibility", function () {
client.assert(response.body.visibility != null)
});
} else {
client.test("No assets in database — skipping asset visibility tests", function () {
client.assert(true)
});
}
%}
### 83 Regular user: Search assets — FileName hidden in previews
GET {{WepApiTest_HostAddress}}/api/asset?page=0&pageSize=3
Authorization: Bearer {{user_token}}
> {%
client.test("User searches assets", function () {
client.assert(response.status === 200)
});
client.test("User does NOT see fileName in preview list", function () {
var first = response.body[0];
client.assert(first === undefined || first === null || first.fileName === null, "fileName should be null in preview for regular user")
});
%}
### 84 Curator: Search assets — FileName visible in previews
GET {{WepApiTest_HostAddress}}/api/asset?page=0&pageSize=3
Authorization: Bearer {{curator_token}}
> {%
client.test("Curator searches assets", function () {
client.assert(response.status === 200)
});
client.test("Curator sees fileName in preview list", function () {
var first = response.body[0];
client.assert(first != null && first.fileName != null, "fileName should be visible in preview for curator")
});
%}
# ---------------------------------------------------------------------------
# Authorization boundary tests (tests 7881)
# ---------------------------------------------------------------------------
### 85 Regular user: Cannot access stats (should 403)
GET {{WepApiTest_HostAddress}}/api/stats
Authorization: Bearer {{user_token}}
> {%
client.test("User cannot access stats", function () {
client.assert(response.status === 403)
});
%}
### 86 Regular user: Cannot access settings (should 403)
GET {{WepApiTest_HostAddress}}/api/settings
Authorization: Bearer {{user_token}}
> {%
client.test("User cannot access settings", function () {
client.assert(response.status === 403)
});
%}
### 87 Regular user: Cannot access user list (should 403)
GET {{WepApiTest_HostAddress}}/api/user
Authorization: Bearer {{user_token}}
> {%
client.test("User cannot list all users", function () {
client.assert(response.status === 403)
});
%}
### 88 Curator: Cannot access settings (should 403)
GET {{WepApiTest_HostAddress}}/api/settings
Authorization: Bearer {{curator_token}}
> {%
client.test("Curator cannot access settings", function () {
client.assert(response.status === 403)
});
%}
# =============================================================================
# VISIBILITY GATE TESTS (R2R4, R6)
# =============================================================================
# These tests verify that entity-level visibility gating is correctly enforced
# per the formal rules established in issue #129:
# R2 — Asset visibility (independent per-asset gate)
# R3 — Album access gated by album's own visibility
# R4 — Person access gated by person's own visibility
# R6 — Edit mode: privileged users receive Private data, frontend decides display
# =============================================================================
# ---------------------------------------------------------------------------
# R4: Person visibility gating
# ---------------------------------------------------------------------------
### 89 Create person for visibility gating tests
< {%
client.global.set("visPersonName", $random.alphabetic(15));
%}
PUT {{WepApiTest_HostAddress}}/api/person
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"name": "{{visPersonName}}"
}
> {%
client.global.set("visPersonId", jsonPath(response.body, "$"));
client.test("Create visibility test person", function () {
client.assert(response.status === 200)
client.assert(jsonPath(response.body, "$") != null)
});
%}
### 90 R4: User gets new person (Public by default — should 200)
GET {{WepApiTest_HostAddress}}/api/person/{{visPersonId}}
Authorization: Bearer {{user_token}}
> {%
client.test("R4: User gets Public person (should 200)", function () {
client.assert(response.status === 200)
});
%}
### 91 R4: Update person to Private visibility
POST {{WepApiTest_HostAddress}}/api/person/{{visPersonId}}
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"name": "{{visPersonName}}",
"visibility": 2
}
> {%
client.test("R4: Update person to Private", function () {
client.assert(response.status === 200)
});
%}
### 92 R4: User gets Private person (should 404)
GET {{WepApiTest_HostAddress}}/api/person/{{visPersonId}}
Authorization: Bearer {{user_token}}
> {%
client.test("R4: User gets Private person (should 404)", function () {
client.assert(response.status === 404)
});
%}
### 93 R4: Admin gets Private person (should 200)
GET {{WepApiTest_HostAddress}}/api/person/{{visPersonId}}
Authorization: Bearer {{admin_token}}
> {%
client.test("R4: Admin gets Private person (should 200)", function () {
client.assert(response.status === 200)
});
%}
### 94 R4: Curator gets Private person (should 200)
GET {{WepApiTest_HostAddress}}/api/person/{{visPersonId}}
Authorization: Bearer {{curator_token}}
> {%
client.test("R4: Curator gets Private person (should 200)", function () {
client.assert(response.status === 200)
});
%}
### 95 R4: Update person back to Public visibility
POST {{WepApiTest_HostAddress}}/api/person/{{visPersonId}}
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"name": "{{visPersonName}}",
"visibility": 0
}
> {%
client.test("R4: Update person to Public", function () {
client.assert(response.status === 200)
});
%}
### 96 R4: User gets Public person (should 200)
GET {{WepApiTest_HostAddress}}/api/person/{{visPersonId}}
Authorization: Bearer {{user_token}}
> {%
client.test("R4: User gets Public person (should 200)", function () {
client.assert(response.status === 200)
});
%}
### 97 R4: Cleanup — Delete visibility test person
DELETE {{WepApiTest_HostAddress}}/api/person/{{visPersonId}}
Authorization: Bearer {{admin_token}}
> {%
client.test("R4: Delete visibility test person", function () {
client.assert(response.status === 200)
});
%}
# ---------------------------------------------------------------------------
# R3: Album visibility gating
# ---------------------------------------------------------------------------
### 98 R3: Create Public album for visibility gating tests
< {%
client.global.set("visPublicAlbum", $random.alphabetic(15));
%}
PUT {{WepApiTest_HostAddress}}/api/album
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"name": "{{visPublicAlbum}}",
"visibility": 0
}
> {%
client.global.set("visPublicAlbumId", jsonPath(response.body, "$"));
client.test("R3: Create Public album", function () {
client.assert(response.status === 200)
});
%}
### 99 R3: Create Private album for visibility gating tests
< {%
client.global.set("visPrivateAlbum", $random.alphabetic(15));
%}
PUT {{WepApiTest_HostAddress}}/api/album
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"name": "{{visPrivateAlbum}}",
"visibility": 2
}
> {%
client.global.set("visPrivateAlbumId", jsonPath(response.body, "$"));
client.test("R3: Create Private album", function () {
client.assert(response.status === 200)
});
%}
### 100 R3: Create Protected album for visibility gating tests
< {%
client.global.set("visProtectedAlbum", $random.alphabetic(15));
%}
PUT {{WepApiTest_HostAddress}}/api/album
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"name": "{{visProtectedAlbum}}",
"visibility": 1
}
> {%
client.global.set("visProtectedAlbumId", jsonPath(response.body, "$"));
client.test("R3: Create Protected album", function () {
client.assert(response.status === 200)
});
%}
### 101 R3: User gets Public album (should 200)
GET {{WepApiTest_HostAddress}}/api/album/{{visPublicAlbumId}}
Authorization: Bearer {{user_token}}
> {%
client.test("R3: User gets Public album (should 200)", function () {
client.assert(response.status === 200)
});
%}
### 102 R3: User gets Protected album (should 200)
GET {{WepApiTest_HostAddress}}/api/album/{{visProtectedAlbumId}}
Authorization: Bearer {{user_token}}
> {%
client.test("R3: User gets Protected album (should 200)", function () {
client.assert(response.status === 200)
});
%}
### 103 R3: User gets Private album (should 404)
GET {{WepApiTest_HostAddress}}/api/album/{{visPrivateAlbumId}}
Authorization: Bearer {{user_token}}
> {%
client.test("R3: User gets Private album (should 404)", function () {
client.assert(response.status === 404)
});
%}
### 104 R3: Admin gets Private album (should 200)
GET {{WepApiTest_HostAddress}}/api/album/{{visPrivateAlbumId}}
Authorization: Bearer {{admin_token}}
> {%
client.test("R3: Admin gets Private album (should 200)", function () {
client.assert(response.status === 200)
});
%}
### 105 R3: Curator gets Private album (should 200)
GET {{WepApiTest_HostAddress}}/api/album/{{visPrivateAlbumId}}
Authorization: Bearer {{curator_token}}
> {%
client.test("R3: Curator gets Private album (should 200)", function () {
client.assert(response.status === 200)
});
%}
### 106 R3: Delete Public album
DELETE {{WepApiTest_HostAddress}}/api/album/{{visPublicAlbumId}}
Authorization: Bearer {{admin_token}}
> {%
client.test("R3: Delete Public album", function () {
client.assert(response.status === 200)
});
%}
### 107 R3: Delete Private album
DELETE {{WepApiTest_HostAddress}}/api/album/{{visPrivateAlbumId}}
Authorization: Bearer {{admin_token}}
> {%
client.test("R3: Delete Private album", function () {
client.assert(response.status === 200)
});
%}
### 108 R3: Delete Protected album
DELETE {{WepApiTest_HostAddress}}/api/album/{{visProtectedAlbumId}}
Authorization: Bearer {{admin_token}}
> {%
client.test("R3: Delete Protected album", function () {
client.assert(response.status === 200)
});
%}
# ---------------------------------------------------------------------------
# MERGE & BULK VISIBILITY TESTS (tests 109130)
# All test data is self-contained and cleaned up within this section.
# ---------------------------------------------------------------------------
### 109 Album merge: destination in source IDs returns 400
POST {{WepApiTest_HostAddress}}/api/album/merge
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"destinationId": "00000000-0000-0000-0000-000000000001",
"sourceIds": ["00000000-0000-0000-0000-000000000001", "00000000-0000-0000-0000-000000000002"]
}
> {%
client.test("Album merge: destination in sources returns 400", function () {
client.assert(response.status === 400)
});
%}
### 110 Person merge: destination in source IDs returns 400
POST {{WepApiTest_HostAddress}}/api/person/merge
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"destinationId": "00000000-0000-0000-0000-000000000001",
"sourceIds": ["00000000-0000-0000-0000-000000000001"]
}
> {%
client.test("Person merge: destination in sources returns 400", function () {
client.assert(response.status === 400)
});
%}
### 111 Person merge: nonexistent destination returns 404
POST {{WepApiTest_HostAddress}}/api/person/merge
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"destinationId": "00000000-0000-0000-0000-0000000000ff",
"sourceIds": ["00000000-0000-0000-0000-000000000001"]
}
> {%
client.test("Person merge: nonexistent destination returns 404", function () {
client.assert(response.status === 404)
});
%}
### 112 Create merge destination album
< {%
client.global.set("mergeDestAlbum", $random.alphabetic(15));
%}
PUT {{WepApiTest_HostAddress}}/api/album
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"name": "{{mergeDestAlbum}}"
}
> {%
client.global.set("mergeDestAlbumId", jsonPath(response.body, "$"));
client.test("Create merge destination album", function () {
client.assert(response.status === 200)
});
%}
### 113 Create merge source album
< {%
client.global.set("mergeSourceAlbum", $random.alphabetic(15));
%}
PUT {{WepApiTest_HostAddress}}/api/album
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"name": "{{mergeSourceAlbum}}"
}
> {%
client.global.set("mergeSourceAlbumId", jsonPath(response.body, "$"));
client.test("Create merge source album", function () {
client.assert(response.status === 200)
});
%}
### 114 Merge source album into destination
POST {{WepApiTest_HostAddress}}/api/album/merge
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"destinationId": "{{mergeDestAlbumId}}",
"sourceIds": ["{{mergeSourceAlbumId}}"]
}
> {%
client.test("Merge albums succeeds", function () {
client.assert(response.status === 200)
});
%}
### 115 Verify destination album still exists
GET {{WepApiTest_HostAddress}}/api/album/{{mergeDestAlbumId}}
Authorization: Bearer {{admin_token}}
> {%
client.test("Destination album exists after merge", function () {
client.assert(response.status === 200)
});
%}
### 116 Verify source album is hard-deleted
GET {{WepApiTest_HostAddress}}/api/album/{{mergeSourceAlbumId}}
Authorization: Bearer {{admin_token}}
> {%
client.test("Source album is hard-deleted after merge", function () {
client.assert(response.status === 404)
});
%}
### 117 Create merge destination person
< {%
client.global.set("mergeDestPerson", $random.alphabetic(15));
%}
PUT {{WepApiTest_HostAddress}}/api/person
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"name": "{{mergeDestPerson}}"
}
> {%
client.global.set("mergeDestPersonId", jsonPath(response.body, "$"));
client.test("Create merge destination person", function () {
client.assert(response.status === 200)
});
%}
### 118 Create merge source person
< {%
client.global.set("mergeSourcePerson", $random.alphabetic(15));
%}
PUT {{WepApiTest_HostAddress}}/api/person
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"name": "{{mergeSourcePerson}}"
}
> {%
client.global.set("mergeSourcePersonId", jsonPath(response.body, "$"));
client.test("Create merge source person", function () {
client.assert(response.status === 200)
});
%}
### 119 Create album owned by source person
< {%
client.global.set("mergePersonAlbum", $random.alphabetic(15));
%}
PUT {{WepApiTest_HostAddress}}/api/album
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"name": "{{mergePersonAlbum}}",
"person": "{{mergeSourcePersonId}}"
}
> {%
client.global.set("mergePersonAlbumId", jsonPath(response.body, "$"));
client.test("Create album for source person", function () {
client.assert(response.status === 200)
});
%}
### 120 Merge source person into destination
POST {{WepApiTest_HostAddress}}/api/person/merge
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"destinationId": "{{mergeDestPersonId}}",
"sourceIds": ["{{mergeSourcePersonId}}"]
}
> {%
client.test("Merge people succeeds", function () {
client.assert(response.status === 200)
});
%}
### 121 Verify destination person exists and has the album
GET {{WepApiTest_HostAddress}}/api/person/{{mergeDestPersonId}}
Authorization: Bearer {{admin_token}}
> {%
client.test("Destination person exists after merge", function () {
client.assert(response.status === 200)
});
client.test("Destination person has the merged album", function () {
var albums = response.body.albums || [];
var found = albums.some(function(a) { return a.id === client.global.get("mergePersonAlbumId"); });
client.assert(found, "Expected the merged album to belong to the destination person");
});
%}
### 122 Verify source person is hard-deleted
GET {{WepApiTest_HostAddress}}/api/person/{{mergeSourcePersonId}}
Authorization: Bearer {{admin_token}}
> {%
client.test("Source person is hard-deleted after merge", function () {
client.assert(response.status === 404)
});
%}
### 123 Cleanup: delete merge test person and album
DELETE {{WepApiTest_HostAddress}}/api/person/{{mergeDestPersonId}}
Authorization: Bearer {{admin_token}}
> {%
client.test("Delete merge destination person", function () {
client.assert(response.status === 200)
});
%}
### 124 Create person for bulk visibility test
< {%
client.global.set("bulkVisPerson", $random.alphabetic(15));
%}
PUT {{WepApiTest_HostAddress}}/api/person
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"name": "{{bulkVisPerson}}"
}
> {%
client.global.set("bulkVisPersonId", jsonPath(response.body, "$"));
client.test("Create person for bulk visibility test", function () {
client.assert(response.status === 200)
});
%}
### 125 Bulk visibility update (people) as regular user (should 403)
POST {{WepApiTest_HostAddress}}/api/person
Authorization: Bearer {{user_token}}
Content-Type: application/json
{
"ids": ["{{bulkVisPersonId}}"],
"data": { "name": "{{bulkVisPerson}}", "visibility": 1 }
}
> {%
client.test("Bulk visibility update (people) as regular user should 403", function () {
client.assert(response.status === 403)
});
%}
### 126 Bulk visibility update (people) as admin (should succeed)
POST {{WepApiTest_HostAddress}}/api/person
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"ids": ["{{bulkVisPersonId}}"],
"data": { "name": "{{bulkVisPerson}}", "visibility": 1 }
}
> {%
client.test("Bulk visibility update (people) as admin succeeds", function () {
client.assert(response.status === 200)
});
%}
### 127 Verify person visibility was updated
GET {{WepApiTest_HostAddress}}/api/person/{{bulkVisPersonId}}
Authorization: Bearer {{admin_token}}
> {%
client.test("Person visibility updated to Protected", function () {
client.assert(response.status === 200);
client.assert(response.body.visibility === 1, "Expected visibility 1 (Protected) but got " + response.body.visibility)
});
%}
### 128 Bulk visibility update (assets) as admin
POST {{WepApiTest_HostAddress}}/api/asset
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"ids": ["{{testAssetId}}"],
"data": { "visibility": 1 }
}
> {%
var hasAssets = client.global.get("hasAssets");
if (hasAssets === "true") {
client.test("Bulk visibility update (assets) succeeds", function () {
client.assert(response.status === 200)
});
} else {
client.test("No assets — skipping asset bulk visibility test", function () {
client.assert(true)
});
}
%}
### 129 Revert asset visibility back to Public
POST {{WepApiTest_HostAddress}}/api/asset
Authorization: Bearer {{admin_token}}
Content-Type: application/json
{
"ids": ["{{testAssetId}}"],
"data": { "visibility": 0 }
}
> {%
var hasAssets = client.global.get("hasAssets");
if (hasAssets === "true") {
client.test("Revert asset visibility to Public", function () {
client.assert(response.status === 200)
});
} else {
client.test("No assets — skipping asset revert test", function () {
client.assert(true)
});
}
%}
### 130 Cleanup: delete bulk visibility test person
DELETE {{WepApiTest_HostAddress}}/api/person/{{bulkVisPersonId}}
Authorization: Bearer {{admin_token}}
> {%
client.test("Delete bulk visibility test person", function () {
client.assert(response.status === 200)
});
%}
# ---------------------------------------------------------------------------
# R2: Asset visibility gating — existing assets test
# ---------------------------------------------------------------------------
### 131 R2: Anonymous gets asset detail (should 200 for Public assets)
GET {{WepApiTest_HostAddress}}/api/asset/{{testAssetId}}
> {%
var hasAssets = client.global.get("hasAssets");
if (hasAssets === "true") {
client.test("R2: Anonymous gets asset detail", function () {
client.assert(response.status === 200)
});
} else {
client.test("R2: No assets — skipping", function () {
client.assert(true)
});
}
%}
# =============================================================================
# CLEANUP
# =============================================================================
### 132 Delete test person as admin
DELETE {{WepApiTest_HostAddress}}/api/person/{{testPersonId}}
Authorization: Bearer {{admin_token}}
> {%
client.test("Delete test person as admin", function () {
client.assert(response.status === 200)
});
%}
### 133 Verify test person is deleted
GET {{WepApiTest_HostAddress}}/api/person/{{testPersonId}}
Authorization: Bearer {{admin_token}}
> {%
client.test("Verify test person is deleted", function () {
client.assert(response.status === 404)
});
%}
### 134 Delete curator user as admin
DELETE {{WepApiTest_HostAddress}}/api/user/{{curator_id}}
Authorization: Bearer {{admin_token}}
> {%
client.test("Delete curator user as admin", function () {
client.assert(response.status === 200)
});
%}
### 135 Verify curator user is soft-deleted
GET {{WepApiTest_HostAddress}}/api/user/{{curator_id}}
Authorization: Bearer {{admin_token}}
> {%
client.test("Verify curator user is soft-deleted", function () {
client.assert(response.status === 200);
client.assert(response.body.deletedAt !== null, "deletedAt should be set after soft delete");
});
%}
### 136 Delete regular user as admin
DELETE {{WepApiTest_HostAddress}}/api/user/{{user_id}}
Authorization: Bearer {{admin_token}}
> {%
client.test("Delete regular user as admin", function () {
client.assert(response.status === 200)
});
%}
### 137 Verify regular user is soft-deleted
GET {{WepApiTest_HostAddress}}/api/user/{{user_id}}
Authorization: Bearer {{admin_token}}
> {%
client.test("Verify regular user is soft-deleted", function () {
client.assert(response.status === 200);
client.assert(response.body.deletedAt !== null, "deletedAt should be set after soft delete");
});
%}
# =============================================================================
# STRESS TEST — Auth rate limit bucket
# =============================================================================
# Sends 15 rapid login attempts with invalid credentials to exceed the auth
# rate-limit bucket (PermitLimit=10, WindowSeconds=60). Requests 1-10 should
# return 401 (invalid credentials), request 11+ should return 429 (rate limited).
# =============================================================================
### 138 Auth rate limit stress test (15 iterations)
# @repeat = 15
POST {{WepApiTest_HostAddress}}/api/auth/login
Content-Type: application/json
{ "identifier": "stress-load-test-user", "password": "invalid-pwd" }
> {%
var idx = parseInt("{{$repeatIndex}}") + 1;
var seen429 = parseInt(client.global.get("stressSeen429") || "0");
if (response.status === 429) {
seen429 = 1;
client.global.set("stressSeen429", "1");
}
if (idx === 15) {
client.test("[" + idx + "/15] At least one 429 was triggered", function () {
client.assert(seen429 === 1, "Expected at least one 429 response within 15 requests");
});
client.global.set("stressSeen429", "0");
}
client.test("[" + idx + "/15] Status is 401 or 429", function () {
client.assert(
response.status === 401 || response.status === 429,
"Expected 401 (invalid) or 429 (rate limited) but got " + response.status
);
});
%}