// Test definitions - 108 tests across 14 categories const tests = [ { category: "Authentication", tests: [ { name: "Login with valid credentials", method: "POST", endpoint: "/auth/login", auth: false, body: () => ({ username: document.getElementById('username').value, password: document.getElementById('password').value }), expect: (res) => res.token && res.role, expectedFields: ['token', 'username', 'role'], onSuccess: (res) => { authToken = res.token; } }, { name: "Login with invalid credentials", method: "POST", endpoint: "/auth/login", auth: false, body: { username: "wronguser", password: "wrongpass" }, expectFail: true, expect: (res, status) => status === 401, expectedFields: ['message'] }, { name: "Access protected route without token", method: "GET", endpoint: "/households", auth: false, expectFail: true, expect: (res, status) => status === 401 } ] }, { category: "Households", tests: [ { name: "Get user's households", method: "GET", endpoint: "/households", auth: true, expect: (res) => Array.isArray(res), onSuccess: (res) => { if (res.length > 0) householdId = res[0].id; } }, { name: "Create new household", method: "POST", endpoint: "/households", auth: true, body: { name: `Test Household ${Date.now()}` }, expect: (res) => res.household && res.household.invite_code, expectedFields: ['message', 'household', 'household.id', 'household.name', 'household.invite_code'] }, { name: "Get household details", method: "GET", endpoint: () => `/households/${householdId}`, auth: true, skip: () => !householdId, expect: (res) => res.id === householdId, expectedFields: ['id', 'name', 'invite_code', 'created_at'] }, { name: "Update household name", method: "PATCH", endpoint: () => `/households/${householdId}`, auth: true, skip: () => !householdId, body: { name: `Updated Household ${Date.now()}` }, expect: (res) => res.household, expectedFields: ['message', 'household', 'household.id', 'household.name'] }, { name: "Refresh invite code", method: "POST", endpoint: () => `/households/${householdId}/invite/refresh`, auth: true, skip: () => !householdId, expect: (res) => res.household && res.household.invite_code, expectedFields: ['message', 'household', 'household.invite_code'] }, { name: "Join household with invalid code", method: "POST", endpoint: "/households/join/INVALID123", auth: true, expectFail: true, expect: (res, status) => status === 404 }, { name: "Create household with empty name (validation)", method: "POST", endpoint: "/households", auth: true, body: { name: "" }, expectFail: true, expect: (res, status) => status === 400, expectedFields: ['error'] } ] }, { category: "Members", tests: [ { name: "Get household members", method: "GET", endpoint: () => `/households/${householdId}/members`, auth: true, skip: () => !householdId, expect: (res) => Array.isArray(res) && res.length > 0, onSuccess: (res) => { testUserId = res[0].user_id; } }, { name: "Update member role (non-admin attempting)", method: "PATCH", endpoint: () => `/households/${householdId}/members/${testUserId}/role`, auth: true, skip: () => !householdId || !testUserId, body: { role: "user" }, expectFail: true, expect: (res, status) => status === 400 || status === 403 } ] }, { category: "Stores", tests: [ { name: "Get all stores catalog", method: "GET", endpoint: "/stores", auth: true, expect: (res) => Array.isArray(res), onSuccess: (res) => { if (res.length > 0) storeId = res[0].id; } }, { name: "Get household stores", method: "GET", endpoint: () => `/stores/household/${householdId}`, auth: true, skip: () => !householdId, expect: (res) => Array.isArray(res) }, { name: "Add store to household", method: "POST", endpoint: () => `/stores/household/${householdId}`, auth: true, skip: () => !householdId || !storeId, body: () => ({ storeId: storeId, isDefault: true }), expect: (res) => res.store, expectedFields: ['message', 'store', 'store.id', 'store.name'] }, { name: "Set default store", method: "PATCH", endpoint: () => `/stores/household/${householdId}/${storeId}/default`, auth: true, skip: () => !householdId || !storeId, expect: (res) => res.message }, { name: "Add invalid store to household", method: "POST", endpoint: () => `/stores/household/${householdId}`, auth: true, skip: () => !householdId, body: { storeId: 99999 }, expectFail: true, expect: (res, status) => status === 404 } ] }, { category: "Advanced Household Tests", tests: [ { name: "Create household for complex workflows", method: "POST", endpoint: "/households", auth: true, body: { name: `Workflow Test ${Date.now()}` }, expect: (res) => res.household && res.household.id, onSuccess: (res) => { createdHouseholdId = res.household.id; inviteCode = res.household.invite_code; } }, { name: "Verify invite code format (7 chars)", method: "GET", endpoint: () => `/households/${createdHouseholdId}`, auth: true, skip: () => !createdHouseholdId, expect: (res) => res.invite_code && res.invite_code.length === 7 && res.invite_code.startsWith('H') }, { name: "Get household with no stores added yet", method: "GET", endpoint: () => `/stores/household/${createdHouseholdId}`, auth: true, skip: () => !createdHouseholdId, expect: (res) => Array.isArray(res) && res.length === 0 }, { name: "Update household with very long name (validation)", method: "PATCH", endpoint: () => `/households/${createdHouseholdId}`, auth: true, skip: () => !createdHouseholdId, body: { name: "A".repeat(101) }, expectFail: true, expect: (res, status) => status === 400 }, { name: "Refresh invite code changes value", method: "POST", endpoint: () => `/households/${createdHouseholdId}/invite/refresh`, auth: true, skip: () => !createdHouseholdId || !inviteCode, expect: (res) => res.household && res.household.invite_code !== inviteCode, onSuccess: (res) => { inviteCode = res.household.invite_code; } }, { name: "Join same household twice (idempotent)", method: "POST", endpoint: () => `/households/join/${inviteCode}`, auth: true, skip: () => !inviteCode, expect: (res, status) => status === 200 && res.message.includes("already a member") }, { name: "Get non-existent household", method: "GET", endpoint: "/households/99999", auth: true, expectFail: true, expect: (res, status) => status === 404 }, { name: "Update non-existent household", method: "PATCH", endpoint: "/households/99999", auth: true, body: { name: "Test" }, expectFail: true, expect: (res, status) => status === 403 || status === 404 } ] }, { category: "Member Management Edge Cases", tests: [ { name: "Get members for created household", method: "GET", endpoint: () => `/households/${createdHouseholdId}/members`, auth: true, skip: () => !createdHouseholdId, expect: (res) => Array.isArray(res) && res.length >= 1 && res[0].role === 'admin' }, { name: "Update own role (should fail)", method: "PATCH", endpoint: () => `/households/${createdHouseholdId}/members/${testUserId}/role`, auth: true, skip: () => !createdHouseholdId || !testUserId, body: { role: "user" }, expectFail: true, expect: (res, status) => status === 400 && res.error && res.error.includes("own role") }, { name: "Update role with invalid value", method: "PATCH", endpoint: () => `/households/${createdHouseholdId}/members/1/role`, auth: true, skip: () => !createdHouseholdId, body: { role: "superadmin" }, expectFail: true, expect: (res, status) => status === 400 }, { name: "Remove non-existent member", method: "DELETE", endpoint: () => `/households/${createdHouseholdId}/members/99999`, auth: true, skip: () => !createdHouseholdId, expectFail: true, expect: (res, status) => status === 404 || status === 500 } ] }, { category: "Store Management Advanced", tests: [ { name: "Add multiple stores to household", method: "POST", endpoint: () => `/stores/household/${createdHouseholdId}`, auth: true, skip: () => !createdHouseholdId || !storeId, body: () => ({ storeId: storeId, isDefault: false }), expect: (res) => res.store }, { name: "Add same store twice (duplicate check)", method: "POST", endpoint: () => `/stores/household/${createdHouseholdId}`, auth: true, skip: () => !createdHouseholdId || !storeId, body: () => ({ storeId: storeId, isDefault: false }), expectFail: true, expect: (res, status) => status === 400 || status === 409 || status === 500 }, { name: "Set default store for household", method: "PATCH", endpoint: () => `/stores/household/${createdHouseholdId}/${storeId}/default`, auth: true, skip: () => !createdHouseholdId || !storeId, expect: (res) => res.message }, { name: "Verify default store is first in list", method: "GET", endpoint: () => `/stores/household/${createdHouseholdId}`, auth: true, skip: () => !createdHouseholdId || !storeId, expect: (res) => Array.isArray(res) && res.length > 0 && res[0].is_default === true }, { name: "Set non-existent store as default", method: "PATCH", endpoint: () => `/stores/household/${createdHouseholdId}/99999/default`, auth: true, skip: () => !createdHouseholdId, expectFail: true, expect: (res, status) => status === 404 || status === 500 }, { name: "Remove store from household", method: "DELETE", endpoint: () => `/stores/household/${createdHouseholdId}/${storeId}`, auth: true, skip: () => !createdHouseholdId || !storeId, expect: (res) => res.message }, { name: "Verify store removed from household", method: "GET", endpoint: () => `/stores/household/${createdHouseholdId}`, auth: true, skip: () => !createdHouseholdId, expect: (res) => Array.isArray(res) && res.length === 0 } ] }, { category: "Data Integrity & Cleanup", tests: [ { name: "Create second household for testing", method: "POST", endpoint: "/households", auth: true, body: { name: `Second Test ${Date.now()}` }, expect: (res) => res.household && res.household.id, onSuccess: (res) => { secondHouseholdId = res.household.id; } }, { name: "Verify user belongs to multiple households", method: "GET", endpoint: "/households", auth: true, expect: (res) => Array.isArray(res) && res.length >= 3 }, { name: "Delete created test household", method: "DELETE", endpoint: () => `/households/${createdHouseholdId}`, auth: true, skip: () => !createdHouseholdId, expect: (res) => res.message }, { name: "Verify deleted household is gone", method: "GET", endpoint: () => `/households/${createdHouseholdId}`, auth: true, skip: () => !createdHouseholdId, expectFail: true, expect: (res, status) => status === 404 || status === 403 }, { name: "Delete second test household", method: "DELETE", endpoint: () => `/households/${secondHouseholdId}`, auth: true, skip: () => !secondHouseholdId, expect: (res) => res.message }, { name: "Verify households list updated", method: "GET", endpoint: "/households", auth: true, expect: (res) => Array.isArray(res) } ] }, { category: "List Operations", tests: [ { name: "Get grocery list for household+store", method: "GET", endpoint: () => `/households/${householdId}/stores/${storeId}/list`, auth: true, skip: () => !householdId || !storeId, expect: (res) => Array.isArray(res), expectedFields: ['items'] }, { name: "Add item to list", method: "POST", endpoint: () => `/households/${householdId}/stores/${storeId}/list/add`, auth: true, skip: () => !householdId || !storeId, body: { item_name: "Test API Item", quantity: "2 units" }, expect: (res) => res.item, expectedFields: ['item', 'item.id', 'item.item_name', 'item.quantity'] }, { name: "Add duplicate item (should update quantity)", method: "POST", endpoint: () => `/households/${householdId}/stores/${storeId}/list/add`, auth: true, skip: () => !householdId || !storeId, body: { item_name: "Test API Item", quantity: "3 units" }, expect: (res) => res.item && res.item.quantity === "3 units" }, { name: "Mark item as bought", method: "PATCH", endpoint: () => `/households/${householdId}/stores/${storeId}/list/item`, auth: true, skip: () => !householdId || !storeId, body: { item_name: "Test API Item", bought: true }, expect: (res) => res.message, expectedFields: ['message'] }, { name: "Unmark item (set bought to false)", method: "PATCH", endpoint: () => `/households/${householdId}/stores/${storeId}/list/item`, auth: true, skip: () => !householdId || !storeId, body: { item_name: "Test API Item", bought: false }, expect: (res) => res.message }, { name: "Update item details", method: "PUT", endpoint: () => `/households/${householdId}/stores/${storeId}/list/item`, auth: true, skip: () => !householdId || !storeId, body: { item_name: "Test API Item", quantity: "5 units", notes: "Updated via API test" }, expect: (res) => res.item, expectedFields: ['item', 'item.quantity', 'item.notes'] }, { name: "Get suggestions based on history", method: "GET", endpoint: () => `/households/${householdId}/stores/${storeId}/list/suggestions?query=test`, auth: true, skip: () => !householdId || !storeId, expect: (res) => Array.isArray(res) }, { name: "Get recently bought items", method: "GET", endpoint: () => `/households/${householdId}/stores/${storeId}/list/recent`, auth: true, skip: () => !householdId || !storeId, expect: (res) => Array.isArray(res) }, { name: "Delete item from list", method: "DELETE", endpoint: () => `/households/${householdId}/stores/${storeId}/list/item`, auth: true, skip: () => !householdId || !storeId, body: { item_name: "Test API Item" }, expect: (res) => res.message }, { name: "Try to add item with empty name", method: "POST", endpoint: () => `/households/${householdId}/stores/${storeId}/list/add`, auth: true, skip: () => !householdId || !storeId, body: { item_name: "", quantity: "1" }, expectFail: true, expect: (res, status) => status === 400 } ] }, { category: "Item Classifications", tests: [ { name: "Get item classification", method: "GET", endpoint: () => `/households/${householdId}/stores/${storeId}/list/classification?item_name=Milk`, auth: true, skip: () => !householdId || !storeId, expect: (res) => res.classification !== undefined, expectedFields: ['classification'] }, { name: "Set item classification", method: "POST", endpoint: () => `/households/${householdId}/stores/${storeId}/list/classification`, auth: true, skip: () => !householdId || !storeId, body: { item_name: "Test Classified Item", classification: "dairy" }, expect: (res) => res.message || res.classification }, { name: "Update item classification", method: "POST", endpoint: () => `/households/${householdId}/stores/${storeId}/list/classification`, auth: true, skip: () => !householdId || !storeId, body: { item_name: "Test Classified Item", classification: "produce" }, expect: (res) => res.message || res.classification }, { name: "Verify classification persists", method: "GET", endpoint: () => `/households/${householdId}/stores/${storeId}/list/classification?item_name=Test Classified Item`, auth: true, skip: () => !householdId || !storeId, expect: (res) => res.classification === "produce" } ] }, { category: "Account Management", tests: [ { name: "Get current user profile", method: "GET", endpoint: "/users/me", auth: true, expect: (res) => res.username, expectedFields: ['id', 'username', 'name', 'display_name', 'role'], onSuccess: (res) => { testUserId = res.id; } }, { name: "Update display name", method: "PATCH", endpoint: "/users/me/display-name", auth: true, body: { display_name: "Test Display Name" }, expect: (res) => res.message, expectedFields: ['message'] }, { name: "Verify display name updated", method: "GET", endpoint: "/users/me", auth: true, expect: (res) => res.display_name === "Test Display Name" }, { name: "Clear display name (set to null)", method: "PATCH", endpoint: "/users/me/display-name", auth: true, body: { display_name: null }, expect: (res) => res.message }, { name: "Update password", method: "PATCH", endpoint: "/users/me/password", auth: true, body: () => ({ currentPassword: document.getElementById('password').value, newPassword: document.getElementById('password').value }), expect: (res) => res.message }, { name: "Try to update password with wrong current password", method: "PATCH", endpoint: "/users/me/password", auth: true, body: { currentPassword: "wrongpassword", newPassword: "newpass123" }, expectFail: true, expect: (res, status) => status === 401 } ] }, { category: "Config Endpoints", tests: [ { name: "Get classifications list", method: "GET", endpoint: "/config/classifications", auth: false, expect: (res) => Array.isArray(res), expectedFields: ['[0].value', '[0].label', '[0].color'] }, { name: "Get system config", method: "GET", endpoint: "/config", auth: false, expect: (res) => res.classifications, expectedFields: ['classifications'] } ] }, { category: "Advanced List Scenarios", tests: [ { name: "Add multiple items rapidly", method: "POST", endpoint: () => `/households/${householdId}/stores/${storeId}/list/add`, auth: true, skip: () => !householdId || !storeId, body: { item_name: "Rapid Test Item 1", quantity: "1" }, expect: (res) => res.item }, { name: "Add second rapid item", method: "POST", endpoint: () => `/households/${householdId}/stores/${storeId}/list/add`, auth: true, skip: () => !householdId || !storeId, body: { item_name: "Rapid Test Item 2", quantity: "1" }, expect: (res) => res.item }, { name: "Verify list contains both items", method: "GET", endpoint: () => `/households/${householdId}/stores/${storeId}/list`, auth: true, skip: () => !householdId || !storeId, expect: (res) => res.items && res.items.length >= 2 }, { name: "Mark both items as bought", method: "PATCH", endpoint: () => `/households/${householdId}/stores/${storeId}/list/item`, auth: true, skip: () => !householdId || !storeId, body: { item_name: "Rapid Test Item 1", bought: true }, expect: (res) => res.message }, { name: "Mark second item as bought", method: "PATCH", endpoint: () => `/households/${householdId}/stores/${storeId}/list/item`, auth: true, skip: () => !householdId || !storeId, body: { item_name: "Rapid Test Item 2", bought: true }, expect: (res) => res.message }, { name: "Verify recent items includes bought items", method: "GET", endpoint: () => `/households/${householdId}/stores/${storeId}/list/recent`, auth: true, skip: () => !householdId || !storeId, expect: (res) => Array.isArray(res) && res.length > 0 }, { name: "Delete first rapid test item", method: "DELETE", endpoint: () => `/households/${householdId}/stores/${storeId}/list/item`, auth: true, skip: () => !householdId || !storeId, body: { item_name: "Rapid Test Item 1" }, expect: (res) => res.message }, { name: "Delete second rapid test item", method: "DELETE", endpoint: () => `/households/${householdId}/stores/${storeId}/list/item`, auth: true, skip: () => !householdId || !storeId, body: { item_name: "Rapid Test Item 2" }, expect: (res) => res.message } ] }, { category: "Edge Cases & Error Handling", tests: [ { name: "Access non-existent household", method: "GET", endpoint: "/households/99999", auth: true, expectFail: true, expect: (res, status) => status === 403 || status === 404 }, { name: "Access non-existent store in household", method: "GET", endpoint: () => `/households/${householdId}/stores/99999/list`, auth: true, skip: () => !householdId, expectFail: true, expect: (res, status) => status === 403 || status === 404 }, { name: "Try to update non-existent item", method: "PUT", endpoint: () => `/households/${householdId}/stores/${storeId}/list/item`, auth: true, skip: () => !householdId || !storeId, body: { item_name: "Non Existent Item 999", quantity: "1" }, expectFail: true, expect: (res, status) => status === 404 }, { name: "Try to delete non-existent item", method: "DELETE", endpoint: () => `/households/${householdId}/stores/${storeId}/list/item`, auth: true, skip: () => !householdId || !storeId, body: { item_name: "Non Existent Item 999" }, expectFail: true, expect: (res, status) => status === 404 }, { name: "Invalid classification value", method: "POST", endpoint: () => `/households/${householdId}/stores/${storeId}/list/classification`, auth: true, skip: () => !householdId || !storeId, body: { item_name: "Test Item", classification: "invalid_category_xyz" }, expectFail: true, expect: (res, status) => status === 400 }, { name: "Empty household name on creation", method: "POST", endpoint: "/households", auth: true, body: { name: "" }, expectFail: true, expect: (res, status) => status === 400 } ] } ];