Subchapter 2.17
troubleshooting/verification-guide.mdMarkdown17 KBView on GitHub
This guide provides systematic testing procedures for each component type in Medusa. Use these methods to verify your implementations work correctly.
Prices in Medusa are stored as-is, NOT in cents or smallest currency unit.
10 (not 1000)25.50 (not 2550)1000 (not 100000)Example:
{
"title": "T-Shirt",
"variants": [
{
"prices": [
{
"amount": 19.99, // $19.99, NOT 1999
"currency_code": "usd"
}
]
}
]
}Why this matters: Many payment systems (like Stripe) use cents, but Medusa handles the conversion internally. Always use the actual price value in your API requests and data models.
Verify module compiles without errors:
npm run buildExpected: Build succeeds without TypeScript errors.
If it fails: Check module definition, imports, and type definitions.
Verify database table created:
# Run migrations
npx medusa db:migrate
# Connect to database
psql your_database_name
# Check table exists
\dt brand
# Check table structure
\d brand
# Expected output:
# Columns: id, name, created_at, updated_atCreate a custom CLI script to verify service can be resolved:
// src/scripts/test-brand-service.ts
import { ExecArgs } from "@medusajs/framework/types"
export default async function testServiceResolution({ container }: ExecArgs) {
const brandService = container.resolve("brand")
console.log("Service resolved:", !!brandService)
console.log("Methods available:", typeof brandService.createBrands === "function")
}Execute the script:
npx medusa exec ./src/scripts/test-brand-service.tsExpected: Service resolves and has CRUD methods.
Create a custom CLI script to test CRUD operations:
// src/scripts/test-brand-crud.ts
import { ExecArgs } from "@medusajs/framework/types"
export default async function testCRUD({ container }: ExecArgs) {
const brandService = container.resolve("brand")
// Create
const [brand] = await brandService.createBrands([{ name: "Test Brand" }])
console.log("Created:", brand)
// Retrieve
const [retrieved] = await brandService.retrieveBrands([brand.id])
console.log("Retrieved:", retrieved)
// Update
const [updated] = await brandService.updateBrands([{
id: brand.id,
name: "Updated Brand"
}])
console.log("Updated:", updated)
// List
const brands = await brandService.listBrands()
console.log("Listed:", brands.length, "brands")
// Delete
await brandService.deleteBrands([brand.id])
console.log("Deleted successfully")
}Execute the script:
npx medusa exec ./src/scripts/test-brand-crud.tsExpected: All operations succeed without errors.
Verify workflow compiles:
npm run buildExpected: No errors about async/await, arrow functions, or workflow syntax.
Create a custom CLI script to test workflow execution:
// src/scripts/test-create-brand-workflow.ts
import { ExecArgs } from "@medusajs/framework/types"
import { createBrandWorkflow } from "../workflows/create-brand"
export default async function testWorkflowExecution({ container }: ExecArgs) {
const { result } = await createBrandWorkflow(container)
.run({ input: { name: "Nike" } })
console.log("Workflow result:", result)
}Execute the script:
npx medusa exec ./src/scripts/test-create-brand-workflow.tsExpected: Workflow completes successfully, brand created.
First, create a test workflow that intentionally fails:
// src/workflows/test-rollback.ts
import { createWorkflow, createStep, WorkflowResponse } from "@medusajs/framework/workflows-sdk"
import { createBrandStep } from "./create-brand"
// Create a step that will intentionally fail
const intentionalFailStep = createStep(
"intentional-fail",
async () => {
throw new Error("Intentional failure for rollback test")
}
)
// Create a workflow that intentionally fails after brand creation
export const testRollbackWorkflow = createWorkflow(
"test-rollback",
function (input) {
const brand = createBrandStep(input)
// This step will fail
intentionalFailStep()
return new WorkflowResponse(brand)
}
)Then create a custom CLI script to test rollback:
// src/scripts/test-rollback.ts
import { ExecArgs } from "@medusajs/framework/types"
import { testRollbackWorkflow } from "../workflows/test-rollback"
export default async function testRollback({ container }: ExecArgs) {
const brandService = container.resolve("brand")
const beforeCount = (await brandService.listBrands()).length
try {
await testRollbackWorkflow(container)
.run({ input: { name: "Will Be Rolled Back" } })
} catch (error) {
console.log("Expected error:", error.message)
}
const afterCount = (await brandService.listBrands()).length
console.log("Brand count before:", beforeCount)
console.log("Brand count after:", afterCount)
console.log("Rollback worked:", beforeCount === afterCount)
}Execute the script:
npx medusa exec ./src/scripts/test-rollback.tsExpected: Brand count unchanged (rollback succeeded).
Verify server starts without errors:
npm run devExpected:
All /admin routes require authentication. First, create an admin user and login to get an authentication token.
Create admin user (if not already created):
npx medusa user -e admin@test.com -p supersecretLogin to get authentication token:
curl -X POST http://localhost:9000/auth/user/emailpass \
-H "Content-Type: application/json" \
--data-raw '{
"email": "admin@test.com",
"password": "supersecret"
}'Expected response:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}Save the token - you’ll need it for all subsequent /admin requests:
# Set token as environment variable for convenience
export AUTH_TOKEN="your-token-here"Important: All /admin route requests must include the authentication token in the Authorization: Bearer header:
curl -X GET http://localhost:9000/admin/brands \
-H "Authorization: Bearer $AUTH_TOKEN"Test route responds to authenticated requests:
# First, authenticate and get token (see Step 2)
export AUTH_TOKEN="your-token-here"
# Then test the route
curl -X POST http://localhost:9000/admin/brands \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $AUTH_TOKEN" \
-d '{"name": "Nike"}'Expected:
If 404: Route not registered - check file location and function export name.
If 401: Authentication required - see authentication section below.
Test middleware validation works with authenticated requests:
# Authentication required (see Step 2)
# Send invalid data (missing required field)
curl -X POST http://localhost:9000/admin/brands \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $AUTH_TOKEN" \
-d '{}'Expected:
name fieldVerify data was persisted:
# After creating a brand via API
psql your_database -c "SELECT * FROM brand WHERE name = 'Nike';"Expected: Brand exists in database with correct data.
Test error responses with authenticated requests:
# Authentication required (see Step 2)
# Send malformed JSON
curl -X POST http://localhost:9000/admin/brands \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $AUTH_TOKEN" \
-d 'invalid json'Expected: 400 error with appropriate message.
Verify link table created:
# Sync links
npx medusa db:sync-links
# Run migrations
npx medusa db:migrate
# Check link table exists
psql your_database -c "\dt" | grep linkExpected: Link table exists (e.g., link_brand_product).
Verify link table structure:
psql your_database -c "\d link_brand_product"Expected columns:
Test creating a link:
import { Modules } from "@medusajs/framework/utils"
async function testLinkCreation() {
const link = container.resolve(ContainerRegistrationKeys.LINK)
await link.create({
[Modules.BRAND]: { brand_id: "brand_123" },
[Modules.PRODUCT]: { product_id: "prod_456" },
})
console.log("Link created successfully")
}Expected: Link created without errors.
Test querying linked records (requires authentication):
# Authentication required (see API Route Verification Step 2)
curl "http://localhost:9000/admin/brands?fields=id,name,products.*" \
-H "Authorization: Bearer $AUTH_TOKEN"Expected: Brands include products array with product details.
Verify hook doesn’t cause errors:
npm run devExpected:
Test hook runs when core workflow executes (requires authentication):
# Authentication required (see API Route Verification Step 2)
# Create product with brand_id in additional_data
curl -X POST http://localhost:9000/admin/products \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $AUTH_TOKEN" \
-d '{
"title": "Air Max 90",
"additional_data": {
"brand_id": "brand_123"
}
}'Expected:
Verify link was created by hook (requires authentication):
# Authentication required (see API Route Verification Step 2)
# Query brand with products
curl "http://localhost:9000/admin/brands?fields=id,name,products.*" \
-H "Authorization: Bearer $AUTH_TOKEN"Expected: Brand shows the newly created product in products array.
Test hook compensation works:
// Temporarily modify the workflow to fail after products are created
// The hook should create the link, then roll it back when workflow fails
async function testHookRollback() {
const linkService = container.resolve(ContainerRegistrationKeys.LINK)
// Count links before
const linksBefore = await linkService.list({})
try {
// This should fail and trigger rollback
await createProductsWorkflow(container).run({
input: {
products: [{ title: "Test", additional_data: { brand_id: "brand_123" } }]
}
})
} catch (error) {
console.log("Expected error:", error.message)
}
// Count links after
const linksAfter = await linkService.list({})
console.log("Links before:", linksBefore.length)
console.log("Links after:", linksAfter.length)
console.log("Rollback worked:", linksBefore.length === linksAfter.length)
}Expected: Link count unchanged (hook compensation ran).
All query tests require authentication (see API Route Verification Step 2).
Test Query.graph() retrieves data:
# Authentication required
curl "http://localhost:9000/admin/brands" \
-H "Authorization: Bearer $AUTH_TOKEN"Expected:
Test field selection:
# Authentication required
curl "http://localhost:9000/admin/brands?fields=id,name" \
-H "Authorization: Bearer $AUTH_TOKEN"Expected: Brands include only id and name fields.
Test pagination works correctly:
# Authentication required
# Page 1
curl "http://localhost:9000/admin/brands?limit=2&offset=0" \
-H "Authorization: Bearer $AUTH_TOKEN"
# Page 2
curl "http://localhost:9000/admin/brands?limit=2&offset=2" \
-H "Authorization: Bearer $AUTH_TOKEN"Expected: Different brands on each page, correct metadata.
Test including related data:
# Authentication required
curl "http://localhost:9000/admin/brands?fields=id,name,products.*" \
-H "Authorization: Bearer $AUTH_TOKEN"Expected: Each brand includes full products array with product details.
Manual test in browser:
Expected: Widget appears somewhere in the specified zone’s section (e.g., the main section for product.details). Its exact position is set by the admin user in the Editor view, so don’t treat “not at the top” as a failure.
In browser DevTools:
Expected:
fields parameter with +brand.*In browser with throttled connection:
Expected:
Temporarily modify widget to force error:
const { data, error } = useQuery({
queryFn: () => { throw new Error("Test error") },
queryKey: ["test-error"],
})
if (error) return <div>Error: {error.message}</div>Expected: Widget gracefully shows error state.
Manual test in browser:
Expected:
Manual test:
/app/brandsExpected:
Manual test on route page:
Expected:
Manual test (requires 15+ records):
Expected:
In browser DevTools:
Expected:
/admin/brands with query params (limit, offset)All /admin routes require authentication. See API Route Verification → Step 2: Admin Authentication for complete authentication setup instructions.
Quick reference:
Create admin user:
npx medusa user -e admin@test.com -p supersecretGet authentication token:
curl -X POST http://localhost:9000/auth/user/emailpass \
-H "Content-Type: application/json" \
--data-raw '{
"email": "admin@test.com",
"password": "supersecret"
}'Use token in requests:
export AUTH_TOKEN="your-token-here"
curl http://localhost:9000/admin/brands \
-H "Authorization: Bearer $AUTH_TOKEN"Systematic verification approach:
Tools used:
npm run build - Compilation verificationnpx medusa db:migrate - Database setupcurl - API testingpsql - Database inspectionjq - JSON parsingKey principle: Test each layer independently before testing integration. This isolates problems and makes debugging easier.
Common verification workflow:
Remember: A systematic testing approach catches issues early and gives confidence your implementation works correctly.
This file
Nearby