docs: add comprehensive README with testing and deployment instructions
Includes development setup, Docker deployment, API reference, WebSocket events, troubleshooting guide, and curl-based test script. Updates App plan.md with implementation completion status. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
314
App plan.md
Normal file
314
App plan.md
Normal file
@@ -0,0 +1,314 @@
|
||||
Here’s a concrete, **handoff-ready plan** you can give to another AI to implement a first version (v1) of a **Spelljammer Ship Tracker** with **multi-ship CRUD, constrained editing, SQLite persistence, and <1s live updates**.
|
||||
|
||||
---
|
||||
|
||||
## 0) Product goal (v1)
|
||||
|
||||
A small phone-friendly web app where your group can:
|
||||
|
||||
* Create/select/delete ships (delete requires confirm modal)
|
||||
* View & edit **core ship stats** + **current weapons on the ship**
|
||||
* See changes propagate to all connected clients within ~1s
|
||||
* Persist “real data” server-side in **SQLite** (ship data + weapons + optional reference content)
|
||||
* Show a **read-only Rules/Reference** screen for battle lookups
|
||||
|
||||
No authentication. Anyone can edit anything.
|
||||
|
||||
---
|
||||
|
||||
## 1) Recommended stack (simple + robust)
|
||||
|
||||
**Option A (recommended): Node.js + Fastify + Socket.IO + SQLite (better-sqlite3) + React (Vite)**
|
||||
|
||||
* Fast to implement, great websocket support, easy Docker build.
|
||||
* Socket.IO simplifies reconnects & room-based updates (per ship).
|
||||
* better-sqlite3 is reliable and fast for low-concurrency apps.
|
||||
|
||||
**Option B:** Node.js + Express + ws + SQLite
|
||||
(also fine, but more manual reconnection & rooms logic)
|
||||
|
||||
Either way: **single Docker Compose** with one container; SQLite is a mounted file.
|
||||
|
||||
---
|
||||
|
||||
## 2) Architecture
|
||||
|
||||
### Backend
|
||||
|
||||
* REST API for CRUD and initial state fetch.
|
||||
* WebSocket (Socket.IO) for real-time updates:
|
||||
|
||||
* Clients join a “ship room” when viewing a ship.
|
||||
* When any field changes, server:
|
||||
|
||||
1. validates & persists to SQLite
|
||||
2. broadcasts an update event to that room
|
||||
|
||||
### Frontend
|
||||
|
||||
Phone-first layout, 3–4 screens:
|
||||
|
||||
1. **Ship List** (create/select/delete)
|
||||
2. **Ship Dashboard** (core stats + weapons list)
|
||||
3. **Edit Weapon** (or inline editing)
|
||||
4. **Rules/Reference** (read-only)
|
||||
|
||||
Use constrained controls:
|
||||
|
||||
* numeric steppers/sliders for numbers
|
||||
* dropdowns for enums (size category, maneuverability class, etc.)
|
||||
* toggles for booleans
|
||||
* “Add weapon” form with select + numeric inputs
|
||||
|
||||
---
|
||||
|
||||
## 3) Data model (SQLite)
|
||||
|
||||
Implement migrations on startup.
|
||||
|
||||
### Tables
|
||||
|
||||
**ships**
|
||||
|
||||
* `id` TEXT (uuid, PK)
|
||||
* `name` TEXT (unique-ish, not required but recommended)
|
||||
* Core stats (examples; adjust to your desired fields):
|
||||
|
||||
* `hull_current` INTEGER NOT NULL
|
||||
* `hull_max` INTEGER NOT NULL
|
||||
* `armor_current` INTEGER NOT NULL
|
||||
* `armor_max` INTEGER NOT NULL
|
||||
* `ac` INTEGER NOT NULL
|
||||
* `con_save` INTEGER NULL (or `con_score`)
|
||||
* `speed` INTEGER NULL
|
||||
* `maneuver_class` TEXT NULL (enum: A/B/C/D/E/F/S per your rules)
|
||||
* `size_category` TEXT NULL
|
||||
* `notes` TEXT NULL
|
||||
* `updated_at` INTEGER (unix ms)
|
||||
|
||||
**weapons**
|
||||
|
||||
* `id` TEXT (uuid, PK)
|
||||
* `ship_id` TEXT (FK -> ships.id, indexed)
|
||||
* `name` TEXT NOT NULL
|
||||
* `type` TEXT NULL (optional enum: siege/arcane/etc.)
|
||||
* `attack_mod` INTEGER NULL
|
||||
* `damage` TEXT NULL (freeform like “3d10”)
|
||||
* `range` TEXT NULL
|
||||
* `ammo_current` INTEGER NULL
|
||||
* `ammo_max` INTEGER NULL
|
||||
* `status` TEXT NULL (enum: ok/damaged/disabled)
|
||||
* `notes` TEXT NULL
|
||||
* `sort_order` INTEGER NOT NULL default 0
|
||||
* `updated_at` INTEGER
|
||||
|
||||
**rules_pages** (optional; for read-only reference)
|
||||
|
||||
* `id` TEXT PK (e.g., “battle”, “ship-stats”)
|
||||
* `title` TEXT
|
||||
* `content_markdown` TEXT
|
||||
* `updated_at` INTEGER
|
||||
|
||||
> Keep rules content as markdown stored in DB **or** ship it as static markdown files in the frontend. For v1, static markdown is simplest.
|
||||
|
||||
---
|
||||
|
||||
## 4) API design (REST)
|
||||
|
||||
### Ships
|
||||
|
||||
* `GET /api/ships` → list ships `{id, name, updated_at}`
|
||||
* `POST /api/ships` → create `{name, initialStats...}` → returns full ship
|
||||
* `GET /api/ships/:id` → full ship + weapons
|
||||
* `PATCH /api/ships/:id` → update core stats (validated + constrained)
|
||||
* `DELETE /api/ships/:id` → delete ship + weapons
|
||||
|
||||
### Weapons
|
||||
|
||||
* `POST /api/ships/:id/weapons` → add weapon
|
||||
* `PATCH /api/weapons/:weaponId` → update weapon fields
|
||||
* `DELETE /api/weapons/:weaponId` → delete weapon
|
||||
|
||||
Validation rules (server-enforced):
|
||||
|
||||
* current values must be `0 <= current <= max`
|
||||
* max values must be `>= 0`
|
||||
* enums must be in allowed set
|
||||
* name length limits
|
||||
|
||||
---
|
||||
|
||||
## 5) WebSocket events (Socket.IO)
|
||||
|
||||
### Client → Server
|
||||
|
||||
* `ship:join` `{shipId}`
|
||||
* `ship:leave` `{shipId}`
|
||||
* `ship:update` `{shipId, patch}` // core stats patch
|
||||
* `weapon:create` `{shipId, weapon}`
|
||||
* `weapon:update` `{weaponId, patch}`
|
||||
* `weapon:delete` `{weaponId}`
|
||||
|
||||
### Server → Client (broadcast to ship room)
|
||||
|
||||
* `ship:state` `{ship, weapons}` // on join, or after major changes
|
||||
* `ship:patched` `{shipId, patch, updated_at}`
|
||||
* `weapon:created` `{shipId, weapon}`
|
||||
* `weapon:patched` `{shipId, weaponId, patch, updated_at}`
|
||||
* `weapon:deleted` `{shipId, weaponId}`
|
||||
|
||||
Implementation approach:
|
||||
|
||||
* On any mutation, server writes to DB then emits event.
|
||||
* On join, server emits current `ship:state`.
|
||||
* Clients apply patches locally; on reconnect, re-join and refresh state.
|
||||
|
||||
Conflict model:
|
||||
|
||||
* **Last write wins** is fine (your requirement). Keep it simple.
|
||||
|
||||
---
|
||||
|
||||
## 6) Frontend UI details (phone-first)
|
||||
|
||||
### Screen 1: Ship List
|
||||
|
||||
* List cards: name + “last updated”
|
||||
* Buttons:
|
||||
|
||||
* “Create ship” (modal with name + optional initial stats)
|
||||
* Tap card to open
|
||||
* Delete icon → confirm modal (“Delete ship X?”)
|
||||
|
||||
### Screen 2: Ship Dashboard
|
||||
|
||||
Sections:
|
||||
|
||||
1. **Vitals**
|
||||
|
||||
* Hull: current/max (stepper)
|
||||
* Armor: current/max
|
||||
* AC (stepper)
|
||||
2. **Mobility**
|
||||
|
||||
* Speed (stepper)
|
||||
* Maneuver class (dropdown)
|
||||
* Size category (dropdown)
|
||||
3. **Weapons**
|
||||
|
||||
* List weapons with quick fields (ammo, status)
|
||||
* Tap weapon to open “Weapon details”
|
||||
* “Add weapon” button
|
||||
4. **Notes**
|
||||
|
||||
* Multi-line text
|
||||
|
||||
Include a top bar:
|
||||
|
||||
* Ship name
|
||||
* “Rules” link/button
|
||||
* “Back to ships”
|
||||
|
||||
### Screen 3: Weapon Details (or modal)
|
||||
|
||||
Constrained inputs for ammo/status/attack_mod.
|
||||
Freeform for damage/range/notes.
|
||||
|
||||
### Screen 4: Rules/Reference
|
||||
|
||||
* Static markdown viewer with headings + quick links
|
||||
* Optional “search within rules” (client-side filter)
|
||||
|
||||
---
|
||||
|
||||
## 7) Persistence + backups
|
||||
|
||||
* SQLite file at `/data/spelljammer.sqlite`
|
||||
* Docker volume mount that path.
|
||||
* Optional: periodic backup script/cron outside app (copy DB file nightly).
|
||||
|
||||
---
|
||||
|
||||
## 8) Docker Compose (single service)
|
||||
|
||||
* Build a single image that serves both:
|
||||
|
||||
* backend API + websockets
|
||||
* frontend static files
|
||||
* Expose one port (e.g., 3000) to Caddy reverse proxy.
|
||||
|
||||
Example structure (for the implementing AI):
|
||||
|
||||
* `/server` Fastify app
|
||||
* `/web` React app build output served by server (or Caddy, but keep simple)
|
||||
|
||||
---
|
||||
|
||||
## 9) Implementation steps (what the AI should do in order)
|
||||
|
||||
1. Create repo scaffold with `/server` and `/web`
|
||||
2. Implement SQLite schema + migrations
|
||||
3. Implement REST endpoints + validation
|
||||
4. Add Socket.IO with rooms:
|
||||
|
||||
* join/leave
|
||||
* mutation events that persist + broadcast
|
||||
5. Build React UI:
|
||||
|
||||
* routing for screens
|
||||
* state store (zustand or simple context)
|
||||
* socket connection + reconnection handling
|
||||
6. Wire CRUD flows:
|
||||
|
||||
* list → select → join room → render state
|
||||
* edit fields → emit update → optimistic UI
|
||||
7. Add delete confirmation modal
|
||||
8. Add rules markdown page (static)
|
||||
9. Dockerize + compose + test behind Caddy
|
||||
|
||||
---
|
||||
|
||||
## 10) Acceptance criteria checklist
|
||||
|
||||
* ✅ Create ship, appears in list, persists after restart
|
||||
* ✅ Select ship, see dashboard with core stats + weapons
|
||||
* ✅ Delete ship requires confirm, removes weapons too
|
||||
* ✅ All edits propagate to another browser within <1s
|
||||
* ✅ App is usable on phone (single column layout, large touch targets)
|
||||
* ✅ SQLite file persists via mounted volume
|
||||
* ✅ WebSocket reconnect works (refresh state on reconnect)
|
||||
* ✅ No auth, no user accounts
|
||||
|
||||
---
|
||||
|
||||
## 11) “Reference guide” integration (v1)
|
||||
|
||||
Since you want read-only rules “for during battle”:
|
||||
Start with a **curated markdown** page that copies only the snippets you frequently need (e.g., maneuverability classes, hull/armor notes, weapon usage, combat steps). You can refine later.
|
||||
|
||||
(If you want, I can extract a concise “Battle Reference” page from your PDF and format it as markdown for the AI to drop into `/web/src/rules/battle.md`.)
|
||||
|
||||
---
|
||||
|
||||
## 12) v1 Implementation Status
|
||||
|
||||
**Status: v1 COMPLETE** (implemented 2026-02-19)
|
||||
|
||||
All 14 commits have been implemented:
|
||||
|
||||
1. ✅ Project scaffold (Fastify + Vite + TypeScript)
|
||||
2. ✅ SQLite connection + migration system
|
||||
3. ✅ Ship REST API with Zod validation
|
||||
4. ✅ Weapon REST API with validation
|
||||
5. ✅ Socket.IO with rooms + real-time events
|
||||
6. ✅ React routing + layout (mobile-first dark theme)
|
||||
7. ✅ Zustand stores + Socket.IO client
|
||||
8. ✅ Ship List page with create modal
|
||||
9. ✅ Ship Dashboard (vitals + mobility)
|
||||
10. ✅ Weapons section (add/edit/detail modals + notes)
|
||||
11. ✅ Delete confirmation polish
|
||||
12. ✅ Rules/Reference page (collapsible battle reference)
|
||||
13. ✅ Dockerfile + docker-compose
|
||||
14. ✅ README with testing + deployment docs
|
||||
|
||||
See `README.md` for full setup, testing, and deployment instructions.
|
||||
183
README.md
183
README.md
@@ -4,19 +4,190 @@ A phone-friendly web app for tracking Spelljammer ships during D&D sessions. Sup
|
||||
|
||||
See `App plan.md` for the full product spec.
|
||||
|
||||
## Quick Start (Development)
|
||||
## Tech Stack
|
||||
|
||||
- **Backend:** Node.js + Fastify + Socket.IO + better-sqlite3 + Zod
|
||||
- **Frontend:** React 19 + Vite + react-router-dom + zustand + socket.io-client
|
||||
- **Language:** TypeScript everywhere
|
||||
- **Database:** SQLite (file-based, zero config)
|
||||
- **Deployment:** Docker + Docker Compose
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Node.js** >= 18 (tested with v18.20.8)
|
||||
- **npm** >= 8
|
||||
- **Docker + Docker Compose** (for production deployment only)
|
||||
|
||||
## Development Setup
|
||||
|
||||
```bash
|
||||
npm run install:all
|
||||
npm run dev
|
||||
# Open http://localhost:5173
|
||||
# Install dependencies for both server and web
|
||||
cd server && npm install
|
||||
cd ../web && npm install
|
||||
cd ..
|
||||
|
||||
# Or use the convenience script:
|
||||
npm install # installs root deps (concurrently)
|
||||
npm run install:all # installs server + web deps
|
||||
```
|
||||
|
||||
## Quick Start (Docker)
|
||||
### Running in Development
|
||||
|
||||
**Option A: Two terminals**
|
||||
|
||||
```bash
|
||||
# Terminal 1 — Backend (port 3000)
|
||||
cd server && npm run dev
|
||||
|
||||
# Terminal 2 — Frontend (port 5173, proxies API to 3000)
|
||||
cd web && npm run dev
|
||||
```
|
||||
|
||||
**Option B: Single command**
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then open **http://localhost:5173** in your browser.
|
||||
|
||||
The Vite dev server proxies `/api` and `/socket.io` requests to the Fastify server, so everything works from a single URL.
|
||||
|
||||
## Production Deployment (Docker)
|
||||
|
||||
```bash
|
||||
# Build and start
|
||||
docker compose up -d --build
|
||||
|
||||
# Open http://localhost:3000
|
||||
```
|
||||
|
||||
*Full setup, testing, and deployment docs will be added once implementation is complete.*
|
||||
The Docker image:
|
||||
- Multi-stage build (web build → server build → production)
|
||||
- Serves both API and frontend from a single container on port 3000
|
||||
- SQLite database stored in a Docker volume at `/data/spelljammer.sqlite`
|
||||
|
||||
### Stopping
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
```
|
||||
|
||||
### Database Backup
|
||||
|
||||
```bash
|
||||
# Copy the SQLite file out of the container
|
||||
docker cp spelljammer-ships:/data/spelljammer.sqlite ./backup-$(date +%F).sqlite
|
||||
```
|
||||
|
||||
### Reverse Proxy (Optional)
|
||||
|
||||
If you want HTTPS, put Caddy or nginx in front:
|
||||
|
||||
```
|
||||
# Example Caddyfile
|
||||
spelljammer.yourdomain.com {
|
||||
reverse_proxy localhost:3000
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### API Tests
|
||||
|
||||
Start the server, then run the test script:
|
||||
|
||||
```bash
|
||||
cd server && npm run dev
|
||||
# In another terminal:
|
||||
bash server/test-api.sh
|
||||
```
|
||||
|
||||
This runs through the full CRUD lifecycle: create ship → update stats → add weapons → fire (reduce ammo) → delete.
|
||||
|
||||
### Real-Time Sync Test
|
||||
|
||||
1. Open the app in two browser tabs
|
||||
2. Create a ship in one tab — it should appear in the other when you refresh the list
|
||||
3. Open the same ship in both tabs
|
||||
4. Change hull points in one — the other should update within ~1 second
|
||||
5. Add/edit/delete weapons — verify sync
|
||||
|
||||
### Mobile Test
|
||||
|
||||
- Use Chrome DevTools (F12 → device toggle) to simulate a phone
|
||||
- Or open the URL from your phone on the same network
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
├── server/ # Fastify backend
|
||||
│ ├── src/
|
||||
│ │ ├── index.ts # Entry point (Fastify + Socket.IO bootstrap)
|
||||
│ │ ├── config.ts # Environment config
|
||||
│ │ ├── db/ # SQLite connection + migrations
|
||||
│ │ ├── routes/ # REST API endpoints
|
||||
│ │ ├── services/ # Database query logic
|
||||
│ │ ├── socket/ # Socket.IO event handlers
|
||||
│ │ └── validation/ # Zod schemas
|
||||
│ └── test-api.sh # curl-based API test script
|
||||
├── web/ # React frontend
|
||||
│ ├── src/
|
||||
│ │ ├── App.tsx # Router setup
|
||||
│ │ ├── socket.ts # Socket.IO client
|
||||
│ │ ├── store/ # Zustand state stores
|
||||
│ │ ├── pages/ # Route pages
|
||||
│ │ ├── components/ # UI components
|
||||
│ │ ├── rules/ # Battle reference content
|
||||
│ │ └── types/ # TypeScript interfaces
|
||||
│ └── vite.config.ts # Dev proxy config
|
||||
├── Dockerfile # Multi-stage production build
|
||||
├── docker-compose.yml # Single-service compose
|
||||
└── App plan.md # Full product specification
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### Ships
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
| -------- | ----------------- | ------------------------ |
|
||||
| `GET` | `/api/ships` | List all ships |
|
||||
| `POST` | `/api/ships` | Create a new ship |
|
||||
| `GET` | `/api/ships/:id` | Get ship with weapons |
|
||||
| `PATCH` | `/api/ships/:id` | Update ship fields |
|
||||
| `DELETE` | `/api/ships/:id` | Delete ship + weapons |
|
||||
|
||||
### Weapons
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
| -------- | --------------------------- | ----------------- |
|
||||
| `POST` | `/api/ships/:id/weapons` | Add weapon to ship|
|
||||
| `PATCH` | `/api/weapons/:weaponId` | Update weapon |
|
||||
| `DELETE` | `/api/weapons/:weaponId` | Delete weapon |
|
||||
|
||||
### WebSocket Events
|
||||
|
||||
| Direction | Event | Purpose |
|
||||
| ---------------- | ----------------- | ---------------------------------- |
|
||||
| Client → Server | `ship:join` | Join a ship's room |
|
||||
| Client → Server | `ship:leave` | Leave a ship's room |
|
||||
| Client → Server | `ship:update` | Update ship stats |
|
||||
| Client → Server | `weapon:create` | Add a weapon |
|
||||
| Client → Server | `weapon:update` | Update a weapon |
|
||||
| Client → Server | `weapon:delete` | Delete a weapon |
|
||||
| Server → Client | `ship:state` | Full ship+weapons state (on join) |
|
||||
| Server → Client | `ship:patched` | Ship field(s) updated |
|
||||
| Server → Client | `weapon:created` | New weapon added |
|
||||
| Server → Client | `weapon:patched` | Weapon field(s) updated |
|
||||
| Server → Client | `weapon:deleted` | Weapon removed |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Server won't start:** Check that port 3000 is free (`lsof -i :3000` or `netstat -an | grep 3000`).
|
||||
|
||||
**WebSocket not connecting:** In development, make sure Vite proxy is configured (check `web/vite.config.ts`). The proxy forwards `/socket.io` to port 3000.
|
||||
|
||||
**Database locked:** If the server crashes without clean shutdown, the SQLite WAL/journal files may remain. Delete `data/spelljammer.sqlite-wal` and `data/spelljammer.sqlite-shm`, then restart.
|
||||
|
||||
**Docker build fails on better-sqlite3:** The `node:18-alpine` image includes prebuilt binaries. If issues persist, switch to `node:18-slim` in the Dockerfile.
|
||||
|
||||
74
server/test-api.sh
Normal file
74
server/test-api.sh
Normal file
@@ -0,0 +1,74 @@
|
||||
#!/bin/bash
|
||||
# API test script for Spelljammer Ship Tracker
|
||||
# Run with: bash server/test-api.sh
|
||||
# Requires: curl, jq (optional, for pretty output)
|
||||
|
||||
BASE=http://localhost:3000/api
|
||||
JQ="cat"
|
||||
command -v jq > /dev/null 2>&1 && JQ="jq ."
|
||||
|
||||
echo "=== Health Check ==="
|
||||
curl -s $BASE/health | $JQ
|
||||
echo ""
|
||||
|
||||
echo "=== Create Ship ==="
|
||||
SHIP=$(curl -s -X POST $BASE/ships \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"name":"Astral Clipper","hull_max":100,"hull_current":100,"armor_max":50,"armor_current":50,"ac":15,"speed":8,"maneuver_class":"B"}')
|
||||
echo "$SHIP" | $JQ
|
||||
SHIP_ID=$(echo "$SHIP" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||
echo "Ship ID: $SHIP_ID"
|
||||
echo ""
|
||||
|
||||
echo "=== List Ships ==="
|
||||
curl -s $BASE/ships | $JQ
|
||||
echo ""
|
||||
|
||||
echo "=== Get Ship (with weapons) ==="
|
||||
curl -s $BASE/ships/$SHIP_ID | $JQ
|
||||
echo ""
|
||||
|
||||
echo "=== Update Ship (take 15 hull damage) ==="
|
||||
curl -s -X PATCH $BASE/ships/$SHIP_ID \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"hull_current":85}' | $JQ
|
||||
echo ""
|
||||
|
||||
echo "=== Add Weapon: Ballista ==="
|
||||
WEAPON=$(curl -s -X POST $BASE/ships/$SHIP_ID/weapons \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"name":"Ballista","attack_mod":6,"damage":"3d10","range":"120/480","ammo_max":10,"ammo_current":10}')
|
||||
echo "$WEAPON" | $JQ
|
||||
WEAPON_ID=$(echo "$WEAPON" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4)
|
||||
echo "Weapon ID: $WEAPON_ID"
|
||||
echo ""
|
||||
|
||||
echo "=== Add Weapon: Spellcannon ==="
|
||||
curl -s -X POST $BASE/ships/$SHIP_ID/weapons \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"name":"Spellcannon","attack_mod":8,"damage":"4d8","range":"60/240","ammo_max":5,"ammo_current":5}' | $JQ
|
||||
echo ""
|
||||
|
||||
echo "=== Update Weapon (fire, reduce ammo) ==="
|
||||
curl -s -X PATCH $BASE/weapons/$WEAPON_ID \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"ammo_current":9}' | $JQ
|
||||
echo ""
|
||||
|
||||
echo "=== Get Ship (verify weapons attached) ==="
|
||||
curl -s $BASE/ships/$SHIP_ID | $JQ
|
||||
echo ""
|
||||
|
||||
echo "=== Delete Weapon ==="
|
||||
curl -s -X DELETE $BASE/weapons/$WEAPON_ID | $JQ
|
||||
echo ""
|
||||
|
||||
echo "=== Delete Ship (cascades to remaining weapons) ==="
|
||||
curl -s -X DELETE $BASE/ships/$SHIP_ID | $JQ
|
||||
echo ""
|
||||
|
||||
echo "=== List Ships (should be empty or minus the deleted one) ==="
|
||||
curl -s $BASE/ships | $JQ
|
||||
echo ""
|
||||
|
||||
echo "=== All tests completed ==="
|
||||
Reference in New Issue
Block a user