{
  "info": {
    "name": "DrawAndRoll API v1",
    "description": "Contract test suite for every endpoint in Docs/API_SCHEMA.md.\n\nRuns top to bottom as a single flow: reset -> guest login (captures {{token}}) -> profile -> shop -> IAP -> leaderboard -> daily reward -> challenges -> error handling -> logout. Each request asserts its real expected status and payload shape, so a green run means the server actually honours the contract - not just that it answered.\n\nRun it:\n  postman collection run MockServer/DrawAndRoll.postman_collection.json\n\nAgainst a real backend: change the baseUrl variable and skip the '0. Setup' and '9. Error handling' folders (they use mock-only /_test/ endpoints).\n\nConflict paths deliberately covered: double bootstrap (409), IAP replay (409), double daily claim (409), incomplete challenge claim (422), missing token (401).",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "http://localhost:5055/api/v1"
    },
    {
      "key": "token",
      "value": ""
    },
    {
      "key": "refreshToken",
      "value": ""
    },
    {
      "key": "challengeId",
      "value": ""
    },
    {
      "key": "coinsBefore",
      "value": "0"
    },
    {
      "key": "coinsBeforeMatches",
      "value": "0"
    },
    {
      "key": "deleteToken",
      "value": ""
    },
    {
      "key": "deleteUserId",
      "value": ""
    }
  ],
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{token}}",
        "type": "string"
      }
    ]
  },
  "event": [
    {
      "listen": "test",
      "script": {
        "type": "text/javascript",
        "exec": [
          "pm.test('response is a valid envelope', function () {",
          "    const b = pm.response.json();",
          "    pm.expect(b).to.have.property('success');",
          "    pm.expect(b).to.have.property('data');",
          "    pm.expect(b).to.have.property('error');",
          "    if (b.success) pm.expect(b.error, 'error must be null on success').to.be.null;",
          "    else pm.expect(b.error, 'error must be populated on failure').to.be.an('object').with.property('code');",
          "});"
        ]
      }
    }
  ],
  "item": [
    {
      "name": "0. Setup (mock only)",
      "item": [
        {
          "name": "Reset server state",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));",
                  "pm.collectionVariables.set('token', '');"
                ]
              }
            }
          ],
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/_test/reset",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "_test",
                "reset"
              ]
            },
            "description": "Mock only. Skip this folder against a real backend."
          }
        }
      ]
    },
    {
      "name": "1. Auth",
      "item": [
        {
          "name": "Guest login",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));",
                  "const d = pm.response.json().data;",
                  "pm.collectionVariables.set('token', d.session_token);",
                  "pm.collectionVariables.set('refreshToken', d.refresh_token);",
                  "pm.collectionVariables.set('coinsBefore', String(d.profile.coins));",
                  "pm.test('returns a session token', () => pm.expect(d.session_token).to.be.a('string').and.not.empty);",
                  "pm.test('returns a full profile', function () {",
                  "    pm.expect(d.profile).to.be.an('object');",
                  "    ['user_id','coins','score','owned_marble_ids','owned_board_ids','owned_background_ids','selected_marble_id']",
                  "        .forEach(k => pm.expect(d.profile, 'profile.' + k).to.have.property(k));",
                  "});",
                  "pm.test('bootstrap_required is a bool', () => pm.expect(d.bootstrap_required).to.be.a('boolean'));",
                  "pm.test('default items are owned', function () {",
                  "    pm.expect(d.profile.owned_marble_ids).to.include(0);",
                  "    pm.expect(d.profile.owned_board_ids).to.include(0);",
                  "    pm.expect(d.profile.owned_background_ids).to.include(0);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"device_id\": \"postman-device-1\",\n  \"device_type\": \"WindowsEditor\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/auth/guest",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "guest"
              ]
            }
          }
        },
        {
          "name": "Guest login again (same device = same account)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));",
                  "const d = pm.response.json().data;",
                  "pm.test('is_new_account is false the second time', () => pm.expect(d.is_new_account).to.be.false);",
                  "pm.collectionVariables.set('token', d.session_token);"
                ]
              }
            }
          ],
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"device_id\": \"postman-device-1\",\n  \"device_type\": \"WindowsEditor\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/auth/guest",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "guest"
              ]
            },
            "description": "A reinstall-free relaunch must resolve to the SAME account, not a fresh one."
          }
        },
        {
          "name": "Social login",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));",
                  "pm.test('issues its own session', () => pm.expect(pm.response.json().data.session_token).to.be.a('string').and.not.empty);"
                ]
              }
            }
          ],
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"provider\": \"google\",\n  \"provider_token\": \"fake-provider-token\",\n  \"name\": \"Osama\",\n  \"email\": \"osama@example.com\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/auth/social",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "social"
              ]
            },
            "description": "A real backend MUST verify provider_token with the provider before trusting it."
          }
        },
        {
          "name": "Social login without a token (400)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('400', () => pm.response.to.have.status(400));"
                ]
              }
            }
          ],
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"provider\": \"google\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/auth/social",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "social"
              ]
            }
          }
        },
        {
          "name": "Refresh",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));",
                  "const d = pm.response.json().data;",
                  "pm.collectionVariables.set('token', d.session_token);",
                  "pm.test('issues a fresh session token', () => pm.expect(d.session_token).to.be.a('string').and.not.empty);"
                ]
              }
            }
          ],
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"refresh_token\": \"{{refreshToken}}\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/auth/refresh",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "refresh"
              ]
            },
            "description": "Optional per API_SCHEMA 9.3 - drop this request if the backend issues long-lived session tokens instead."
          }
        }
      ]
    },
    {
      "name": "2. Profile",
      "item": [
        {
          "name": "Get profile",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));",
                  "const p = pm.response.json().data;",
                  "pm.test('has every documented field', function () {",
                  "    ['user_id','name','avatar_index','coins','score','games_played','wins','losses',",
                  "     'selected_marble_id','selected_board_id','selected_background_id',",
                  "     'owned_marble_ids','owned_board_ids','owned_background_ids',",
                  "     'challenges_completed_total','rank'].forEach(k => pm.expect(p).to.have.property(k));",
                  "});",
                  "pm.test('owned ids are integer arrays', function () {",
                  "    pm.expect(p.owned_marble_ids).to.be.an('array');",
                  "    p.owned_marble_ids.forEach(id => pm.expect(Number.isInteger(id)).to.be.true);",
                  "});",
                  "pm.test('selected items are owned', function () {",
                  "    pm.expect(p.owned_marble_ids).to.include(p.selected_marble_id);",
                  "    pm.expect(p.owned_board_ids).to.include(p.selected_board_id);",
                  "    pm.expect(p.owned_background_ids).to.include(p.selected_background_id);",
                  "});",
                  "pm.collectionVariables.set('coinsBeforeMatches', String(p.coins));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/profile",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "profile"
              ]
            }
          }
        },
        {
          "name": "Bootstrap (first login only)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));",
                  "const p = pm.response.json().data;",
                  "pm.test('uploaded coins are capped, not trusted outright', function () {",
                  "    pm.expect(p.coins, 'a client claiming 999999 coins must not get them').to.be.below(999999);",
                  "});",
                  "pm.test('local ownership was merged in', () => pm.expect(p.owned_marble_ids).to.include(3));",
                  "pm.test('historical counters were added', () => pm.expect(p.games_played).to.be.at.least(4));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"local_coins\": 999999,\n  \"local_score\": 150,\n  \"local_games_played\": 4,\n  \"local_wins\": 2,\n  \"local_losses\": 2,\n  \"local_owned_marble_ids\": [0, 3],\n  \"local_owned_board_ids\": [0],\n  \"local_owned_background_ids\": [0]\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/profile/bootstrap",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "profile",
                "bootstrap"
              ]
            },
            "description": "Deliberately claims 999999 coins: the server must cap the upload (API_SCHEMA 2.1), or a reinstall becomes a currency exploit."
          }
        },
        {
          "name": "Bootstrap twice (409)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('409 - bootstrap is one-time, not idempotent', () => pm.response.to.have.status(409));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"local_coins\": 999999\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/profile/bootstrap",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "profile",
                "bootstrap"
              ]
            }
          }
        },
        {
          "name": "Update profile",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));",
                  "const p = pm.response.json().data;",
                  "pm.test('name applied', () => pm.expect(p.name).to.eql('Osama'));",
                  "pm.test('avatar applied', () => pm.expect(p.avatar_index).to.eql(3));"
                ]
              }
            }
          ],
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Osama\",\n  \"avatar_index\": 3\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/profile",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "profile"
              ]
            }
          }
        }
      ]
    },
    {
      "name": "3. Shop",
      "item": [
        {
          "name": "Get catalog",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));",
                  "const c = pm.response.json().data;",
                  "pm.test('all three categories present', function () {",
                  "    ['marbles','boards','backgrounds'].forEach(k => pm.expect(c[k], k).to.be.an('array').that.is.not.empty);",
                  "});",
                  "pm.test('ids are dense and zero-based (client indexes prefab arrays by id)', function () {",
                  "    ['marbles','boards','backgrounds'].forEach(function (k) {",
                  "        c[k].forEach((item, i) => pm.expect(item.id, k + '[' + i + '].id').to.eql(i));",
                  "    });",
                  "});",
                  "pm.test('item 0 of each category is the free default', function () {",
                  "    ['marbles','boards','backgrounds'].forEach(function (k) {",
                  "        pm.expect(c[k][0].price, k + ' default price').to.eql(0);",
                  "        pm.expect(c[k][0].is_default, k + ' is_default').to.be.true;",
                  "    });",
                  "});",
                  "pm.test('client asset counts: 39 marbles / 11 boards / 4 backgrounds', function () {",
                  "    pm.expect(c.marbles.length).to.eql(39);",
                  "    pm.expect(c.boards.length).to.eql(11);",
                  "    pm.expect(c.backgrounds.length).to.eql(4);",
                  "});",
                  "pm.test('every item carries is_active', function () {",
                  "    ['marbles','boards','backgrounds'].forEach(k => c[k].forEach((item, i) => pm.expect(item.is_active, k + '[' + i + '].is_active').to.be.a('boolean')));",
                  "});",
                  "pm.test('deprecated marbles stay listed but inactive (ids never shift)', function () {",
                  "    const retired = [1, 2, 3, 4, 5, 14, 15, 16, 17, 18, 20, 21, 22, 23];",
                  "    c.marbles.forEach(m => pm.expect(m.is_active, 'marble ' + m.id).to.eql(retired.indexOf(m.id) === -1));",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/shop/catalog",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "catalog"
              ]
            }
          }
        },
        {
          "name": "Report purchase",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));",
                  "pm.test('item now owned', () => pm.expect(pm.response.json().data.owned_marble_ids).to.include(30));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"category\": \"marble\",\n  \"item_id\": 30\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/shop/purchase",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "purchase"
              ]
            }
          }
        },
        {
          "name": "Report the same purchase again (idempotent)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200 - a replayed ledger mirror must not error', () => pm.response.to.have.status(200));",
                  "const p = pm.response.json().data;",
                  "pm.test('item owned exactly once', function () {",
                  "    const hits = p.owned_marble_ids.filter(id => id === 30).length;",
                  "    pm.expect(hits, 'duplicate ownership row').to.eql(1);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"category\": \"marble\",\n  \"item_id\": 30\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/shop/purchase",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "purchase"
              ]
            },
            "description": "The client fires this fire-and-forget, so retries and duplicates WILL happen. Must not double-charge."
          }
        },
        {
          "name": "Report purchase of a deprecated marble (400)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('400 - a deprecated marble is no longer for sale', () => pm.response.to.have.status(400));",
                  "pm.test('error envelope', function () {",
                  "    const b = pm.response.json();",
                  "    pm.expect(b.success).to.be.false;",
                  "    pm.expect(b.error.code).to.eql('VALIDATION_ERROR');",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"category\": \"marble\",\n  \"item_id\": 15\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/shop/purchase",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "purchase"
              ]
            },
            "description": "The client fires this fire-and-forget, so retries and duplicates WILL happen. Must not double-charge."
          }
        },
        {
          "name": "Purchase an out-of-range item (400)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('400', () => pm.response.to.have.status(400));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"category\": \"marble\",\n  \"item_id\": 9999\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/shop/purchase",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "purchase"
              ]
            }
          }
        }
      ]
    },
    {
      "name": "4. IAP",
      "item": [
        {
          "name": "Coin packages",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));",
                  "const list = pm.response.json().data;",
                  "pm.test('is a non-empty list', () => pm.expect(list).to.be.an('array').that.is.not.empty);",
                  "pm.test('every package has both store product ids', function () {",
                  "    list.forEach(function (p) {",
                  "        pm.expect(p.google_product_id, 'google_product_id').to.be.a('string').and.not.empty;",
                  "        pm.expect(p.apple_product_id, 'apple_product_id').to.be.a('string').and.not.empty;",
                  "    });",
                  "});",
                  "pm.test('unlock_all_category is a valid category or null', function () {",
                  "    list.forEach(p => pm.expect([null,'marble','board','background']).to.include(p.unlock_all_category));",
                  "});",
                  "pm.test('coin packs grant coins, unlock-alls do not', function () {",
                  "    list.forEach(function (p) {",
                  "        if (p.unlock_all_category === null) pm.expect(p.coins, p.google_product_id).to.be.above(0);",
                  "    });",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/iap/coin-packages",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "iap",
                "coin-packages"
              ]
            }
          }
        },
        {
          "name": "Validate purchase",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));",
                  "const d = pm.response.json().data;",
                  "pm.test('grants the coins', () => pm.expect(d.granted_coins).to.eql(1000));",
                  "pm.test('returns the updated profile', () => pm.expect(d.profile).to.be.an('object').with.property('coins'));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"platform\": \"android\",\n  \"product_id\": \"coins_1000\",\n  \"receipt_payload\": \"fake-receipt\",\n  \"transaction_id\": \"txn-postman-1\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/iap/validate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "iap",
                "validate"
              ]
            },
            "description": "The real backend MUST verify receipt_payload with Google/Apple before granting. Non-negotiable."
          }
        },
        {
          "name": "Replay the same transaction (409)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('409 - dedupe by transaction_id', () => pm.response.to.have.status(409));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"platform\": \"android\",\n  \"product_id\": \"coins_1000\",\n  \"receipt_payload\": \"fake-receipt\",\n  \"transaction_id\": \"txn-postman-1\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/iap/validate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "iap",
                "validate"
              ]
            },
            "description": "IapManager holds purchases Pending until validation returns, so a redelivered purchase re-sends this. 409 is how the client knows it is safe to confirm."
          }
        },
        {
          "name": "Validate unlock-all",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));",
                  "const d = pm.response.json().data;",
                  "pm.test('reports the unlocked category', () => pm.expect(d.unlocked_category).to.eql('marble'));",
                  "pm.test('every marble is now owned', () => pm.expect(d.profile.owned_marble_ids.length).to.eql(39));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"platform\": \"android\",\n  \"product_id\": \"unlock_all_marbles\",\n  \"receipt_payload\": \"fake-receipt\",\n  \"transaction_id\": \"txn-postman-unlock-1\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/iap/validate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "iap",
                "validate"
              ]
            }
          }
        },
        {
          "name": "Validate an unknown product (404)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('404', () => pm.response.to.have.status(404));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"platform\": \"android\",\n  \"product_id\": \"not_a_real_sku\",\n  \"receipt_payload\": \"fake-receipt\",\n  \"transaction_id\": \"txn-postman-bogus\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/iap/validate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "iap",
                "validate"
              ]
            }
          }
        }
      ]
    },
    {
      "name": "5. Leaderboard",
      "item": [
        {
          "name": "Global top 50",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));",
                  "const d = pm.response.json().data;",
                  "pm.test('returns entries and my_rank', function () {",
                  "    pm.expect(d.entries).to.be.an('array');",
                  "    pm.expect(d.my_rank).to.be.a('number');",
                  "});",
                  "pm.test('respects the limit', () => pm.expect(d.entries.length).to.be.at.most(50));",
                  "pm.test('ranks are 1-based and ascending', function () {",
                  "    d.entries.forEach((e, i) => pm.expect(e.rank, 'entry ' + i).to.eql(i + 1));",
                  "});",
                  "pm.test('scores are descending', function () {",
                  "    for (let i = 1; i < d.entries.length; i++)",
                  "        pm.expect(d.entries[i].score).to.be.at.most(d.entries[i - 1].score);",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/leaderboard?scope=global&limit=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "leaderboard"
              ],
              "query": [
                {
                  "key": "scope",
                  "value": "global"
                },
                {
                  "key": "limit",
                  "value": "50"
                }
              ]
            }
          }
        }
      ]
    },
    {
      "name": "6. Daily reward",
      "item": [
        {
          "name": "Get state",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));",
                  "const d = pm.response.json().data;",
                  "pm.test('7-day track, server-config driven', function () {",
                  "    pm.expect(d.reward_track).to.be.an('array').with.lengthOf(7);",
                  "    d.reward_track.forEach((r, i) => pm.expect(r.day, 'day ' + i).to.eql(i + 1));",
                  "});",
                  "pm.test('claim state is present', function () {",
                  "    pm.expect(d.can_claim_today).to.be.a('boolean');",
                  "    pm.expect(d.current_streak_day).to.be.a('number');",
                  "    pm.expect(d.next_reset_utc).to.be.a('string');",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/daily-reward",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "daily-reward"
              ]
            }
          }
        },
        {
          "name": "Claim",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));",
                  "const d = pm.response.json().data;",
                  "pm.test('grants coins and advances the streak', function () {",
                  "    pm.expect(d.coins_granted).to.be.above(0);",
                  "    pm.expect(d.new_streak_day).to.be.within(1, 7);",
                  "});",
                  "pm.test('returns the updated profile', () => pm.expect(d.profile).to.have.property('coins'));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/daily-reward/claim",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "daily-reward",
                "claim"
              ]
            }
          }
        },
        {
          "name": "Claim twice in one UTC day (409)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('409', () => pm.response.to.have.status(409));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/daily-reward/claim",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "daily-reward",
                "claim"
              ]
            }
          }
        }
      ]
    },
    {
      "name": "7. Daily challenges",
      "item": [
        {
          "name": "Get today",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));",
                  "const list = pm.response.json().data;",
                  "pm.test('is a list of challenges', () => pm.expect(list).to.be.an('array').that.is.not.empty);",
                  "pm.test('each has the documented fields', function () {",
                  "    list.forEach(function (c) {",
                  "        ['id','description','target','progress','reward_coins','is_completed','is_claimed']",
                  "            .forEach(k => pm.expect(c, k).to.have.property(k));",
                  "    });",
                  "});",
                  "pm.test('progress never exceeds target', function () {",
                  "    list.forEach(c => pm.expect(c.progress, c.id).to.be.at.most(c.target));",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/daily-challenges",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "daily-challenges"
              ]
            }
          }
        },
        {
          "name": "Report win 1",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));",
                  "const d = pm.response.json().data;",
                  "pm.test('win grants the server-decided reward (100)', () => pm.expect(d.coins_granted).to.eql(100));",
                  "pm.test('returns the updated profile', () => pm.expect(d.profile).to.be.an('object').with.property('coins'));",
                  "pm.test('profile coins actually went up by the grant', function () {",
                  "    const before = Number(pm.collectionVariables.get('coinsBeforeMatches'));",
                  "    pm.expect(d.profile.coins).to.be.above(before);",
                  "    pm.collectionVariables.set('coinsBeforeMatches', String(d.profile.coins));",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"match_id\": \"m-postman-1\",\n  \"result\": \"win\",\n  \"board_id\": 0\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/game-results",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "game-results"
              ]
            }
          }
        },
        {
          "name": "Report win 2",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));",
                  "const d = pm.response.json().data;",
                  "pm.test('win grants the server-decided reward (100)', () => pm.expect(d.coins_granted).to.eql(100));",
                  "pm.test('returns the updated profile', () => pm.expect(d.profile).to.be.an('object').with.property('coins'));",
                  "pm.test('profile coins actually went up by the grant', function () {",
                  "    const before = Number(pm.collectionVariables.get('coinsBeforeMatches'));",
                  "    pm.expect(d.profile.coins).to.be.above(before);",
                  "    pm.collectionVariables.set('coinsBeforeMatches', String(d.profile.coins));",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"match_id\": \"m-postman-2\",\n  \"result\": \"win\",\n  \"board_id\": 1\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/game-results",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "game-results"
              ]
            }
          }
        },
        {
          "name": "Report win 3",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));",
                  "const d = pm.response.json().data;",
                  "pm.test('win grants the server-decided reward (100)', () => pm.expect(d.coins_granted).to.eql(100));",
                  "pm.test('returns the updated profile', () => pm.expect(d.profile).to.be.an('object').with.property('coins'));",
                  "pm.test('profile coins actually went up by the grant', function () {",
                  "    const before = Number(pm.collectionVariables.get('coinsBeforeMatches'));",
                  "    pm.expect(d.profile.coins).to.be.above(before);",
                  "    pm.collectionVariables.set('coinsBeforeMatches', String(d.profile.coins));",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"match_id\": \"m-postman-3\",\n  \"result\": \"win\",\n  \"board_id\": 2\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/game-results",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "game-results"
              ]
            }
          }
        },
        {
          "name": "Report a loss (no reward)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));",
                  "const d = pm.response.json().data;",
                  "pm.test('a loss grants 0 coins', () => pm.expect(d.coins_granted).to.eql(0));",
                  "pm.test('profile still returned', () => pm.expect(d.profile).to.have.property('coins'));",
                  "pm.test('coins unchanged by a loss', function () {",
                  "    pm.expect(d.profile.coins).to.eql(Number(pm.collectionVariables.get('coinsBeforeMatches')));",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"match_id\": \"m-postman-loss-1\",\n  \"result\": \"loss\",\n  \"board_id\": 0\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/game-results",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "game-results"
              ]
            },
            "description": "Only a win is rewarded; the server decides the amount."
          }
        },
        {
          "name": "Get today (progress must have moved)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));",
                  "const list = pm.response.json().data;",
                  "pm.test('reported matches drove challenge progress', function () {",
                  "    const moved = list.filter(c => c.progress > 0);",
                  "    pm.expect(moved.length, 'no challenge advanced after 3 reported wins').to.be.above(0);",
                  "});",
                  "const done = list.find(c => c.is_completed && !c.is_claimed);",
                  "pm.test('at least one challenge completed', () => pm.expect(done, 'nothing completed after 3 wins on 3 boards').to.not.be.undefined);",
                  "if (done) pm.collectionVariables.set('challengeId', done.id);",
                  "const open = list.find(c => !c.is_completed);",
                  "if (open) pm.collectionVariables.set('incompleteChallengeId', open.id);"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/daily-challenges",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "daily-challenges"
              ]
            }
          }
        },
        {
          "name": "Claim an incomplete challenge (422)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!pm.collectionVariables.get('incompleteChallengeId')) {",
                  "    pm.test.skip('no incomplete challenge to test against');",
                  "} else {",
                  "    pm.test('422', () => pm.response.to.have.status(422));",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/daily-challenges/{{incompleteChallengeId}}/claim",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "daily-challenges",
                "{{incompleteChallengeId}}",
                "claim"
              ]
            }
          }
        },
        {
          "name": "Claim a completed challenge",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));",
                  "pm.test('grants coins', () => pm.expect(pm.response.json().data.coins_granted).to.be.above(0));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/daily-challenges/{{challengeId}}/claim",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "daily-challenges",
                "{{challengeId}}",
                "claim"
              ]
            }
          }
        },
        {
          "name": "Claim it twice (409)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('409', () => pm.response.to.have.status(409));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/daily-challenges/{{challengeId}}/claim",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "daily-challenges",
                "{{challengeId}}",
                "claim"
              ]
            }
          }
        },
        {
          "name": "Claim an unknown challenge (404)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('404', () => pm.response.to.have.status(404));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/daily-challenges/c_not_a_real_challenge/claim",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "daily-challenges",
                "c_not_a_real_challenge",
                "claim"
              ]
            }
          }
        },
        {
          "name": "Report a 4P match with in-match events",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/game-results",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "game-results"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"match_id\": \"m-postman-events-4p\",\n  \"result\": \"loss\",\n  \"board_id\": 0,\n  \"game_mode\": 4,\n  \"events\": [\n    \"eight_to_start\",\n    \"first_send_home\",\n    \"fast_move\",\n    \"triple_sevens\",\n    \"all_five_out\"\n  ],\n  \"jokers_played\": 1,\n  \"duration_seconds\": 1800,\n  \"opponent_user_ids\": [],\n  \"forfeit\": false\n}"
            },
            "description": "The client reports what the player did; the server applies each event_* rule. A loss still earns the non-win event challenges."
          }
        },
        {
          "name": "Report a 2P forfeit win (win-conditional events must not count)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/game-results",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "game-results"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"match_id\": \"m-postman-forfeit-2p\",\n  \"result\": \"win\",\n  \"board_id\": 0,\n  \"game_mode\": 2,\n  \"events\": [],\n  \"jokers_played\": 0,\n  \"duration_seconds\": 60,\n  \"opponent_user_ids\": [],\n  \"forfeit\": true\n}"
            }
          }
        },
        {
          "name": "Event challenges after the 4P report and the forfeit",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));",
                  "const byId = {}; pm.response.json().data.forEach(c => byId[c.id] = c);",
                  "['lc_eight_to_start','lc_first_send_home','lc_move_under_3s','lc_triple_sevens','lc_all_five_out'].forEach(function (id) {",
                  "    pm.test(id + ' completed', () => pm.expect(byId[id], id).to.include({ is_completed: true }));",
                  "});",
                  "['lc_win_no_joker_2p','lc_win_under_15min','lc_beat_higher_rank'].forEach(function (id) {",
                  "    pm.test(id + ' not earned', () => pm.expect(byId[id].progress, id).to.eql(0));",
                  "});"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/daily-challenges",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "daily-challenges"
              ]
            }
          }
        },
        {
          "name": "Report a clean 2P win (no Joker, 10 minutes)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/game-results",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "game-results"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"match_id\": \"m-postman-clean-2p\",\n  \"result\": \"win\",\n  \"board_id\": 0,\n  \"game_mode\": 2,\n  \"events\": [],\n  \"jokers_played\": 0,\n  \"duration_seconds\": 600,\n  \"opponent_user_ids\": [],\n  \"forfeit\": false\n}"
            }
          }
        },
        {
          "name": "Win-conditional event challenges now earned",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));",
                  "const byId = {}; pm.response.json().data.forEach(c => byId[c.id] = c);",
                  "['lc_win_no_joker_2p','lc_win_under_15min'].forEach(function (id) {",
                  "    pm.test(id + ' completed', () => pm.expect(byId[id], id).to.include({ is_completed: true }));",
                  "});",
                  "['lc_beat_higher_rank'].forEach(function (id) {",
                  "    pm.test(id + ' not earned', () => pm.expect(byId[id].progress, id).to.eql(0));",
                  "});",
                  "pm.test('beat_higher_rank needs a real higher-ranked opponent id', () => pm.expect(byId['lc_beat_higher_rank'].is_completed).to.be.false);"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/daily-challenges",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "daily-challenges"
              ]
            }
          }
        }
      ]
    },
    {
      "name": "8. Auth failures",
      "item": [
        {
          "name": "Profile without a token (401)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('401', () => pm.response.to.have.status(401));",
                  "pm.test('error envelope carries a code', () => pm.expect(pm.response.json().error.code).to.be.a('string'));"
                ]
              }
            }
          ],
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/profile",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "profile"
              ]
            },
            "description": "The Unity client treats 401/403 as session expiry: it clears the stored session and fires OnSessionExpired."
          }
        },
        {
          "name": "Profile with a garbage token (401)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('401', () => pm.response.to.have.status(401));"
                ]
              }
            }
          ],
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "header": [
              {
                "key": "Authorization",
                "value": "Bearer not-a-real-token"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/profile",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "profile"
              ]
            }
          }
        }
      ]
    },
    {
      "name": "9. Mock-only controls",
      "item": [
        {
          "name": "Force an error (X-Mock-Status)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('500 on demand', () => pm.response.to.have.status(500));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "X-Mock-Status",
                "value": "500"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/profile",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "profile"
              ]
            },
            "description": "Mock only. Any endpoint returns the status in X-Mock-Status - try 401, 409, 429, 500."
          }
        },
        {
          "name": "Dump server state",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "auth": {
              "type": "noauth"
            },
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/_test/state",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "_test",
                "state"
              ]
            }
          }
        },
        {
          "name": "Expire my session",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/_test/expire-session",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "_test",
                "expire-session"
              ]
            },
            "description": "Mock only. The next authed call 401s - use it to exercise the client's OnSessionExpired path."
          }
        },
        {
          "name": "Profile after expiry (401)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('401 - the expired token is really dead', () => pm.response.to.have.status(401));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/profile",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "profile"
              ]
            }
          }
        }
      ]
    },
    {
      "name": "10. Account deletion",
      "description": "Runs on a throwaway guest account, so it is safe against a real backend.",
      "item": [
        {
          "name": "Guest login (throwaway account)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));",
                  "const d = pm.response.json().data;",
                  "pm.collectionVariables.set('deleteToken', d.session_token);",
                  "pm.collectionVariables.set('deleteUserId', d.user_id);"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/auth/guest",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "guest"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"device_id\": \"postman-delete-device\",\n  \"device_type\": \"postman\"\n}"
            },
            "auth": {
              "type": "noauth"
            }
          }
        },
        {
          "name": "Delete account",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));",
                  "pm.test('deleted', () => pm.expect(pm.response.json().data.deleted).to.be.true);"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/account",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "account"
              ]
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{deleteToken}}",
                  "type": "string"
                }
              ]
            }
          }
        },
        {
          "name": "Profile with the deleted account's token (401)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('401 - every session is revoked', () => pm.response.to.have.status(401));"
                ]
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/profile",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "profile"
              ]
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{deleteToken}}",
                  "type": "string"
                }
              ]
            }
          }
        },
        {
          "name": "Delete again (401)",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('401', () => pm.response.to.have.status(401));"
                ]
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/account",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "account"
              ]
            },
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{deleteToken}}",
                  "type": "string"
                }
              ]
            }
          }
        },
        {
          "name": "Guest login on the same device creates a NEW account",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('200', () => pm.response.to.have.status(200));",
                  "const d = pm.response.json().data;",
                  "pm.test('a brand-new account', function () {",
                  "    pm.expect(d.is_new_account).to.be.true;",
                  "    pm.expect(d.bootstrap_required).to.be.true;",
                  "    pm.expect(d.user_id).to.not.eql(pm.collectionVariables.get('deleteUserId'));",
                  "});",
                  "pm.test('nothing carried over', () => pm.expect(d.profile.owned_marble_ids).to.eql([0]));"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/auth/guest",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "guest"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"device_id\": \"postman-delete-device\",\n  \"device_type\": \"postman\"\n}"
            },
            "auth": {
              "type": "noauth"
            }
          }
        }
      ]
    }
  ]
}
