first commit
This commit is contained in:
132
lib/mock-server/axios-adapter.ts
Normal file
132
lib/mock-server/axios-adapter.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
/**
|
||||
* Custom axios adapter for mocking API responses in development
|
||||
* Intercepts requests and routes them to mock handlers
|
||||
*/
|
||||
|
||||
import type { AxiosRequestConfig } from "axios"
|
||||
import { mockHandlers } from "./handlers"
|
||||
|
||||
interface MockRequest extends AxiosRequestConfig {
|
||||
url?: string
|
||||
}
|
||||
|
||||
export const createMockAdapter = () => {
|
||||
return async (config: MockRequest) => {
|
||||
const url = config.url || ""
|
||||
const method = (config.method || "get").toLowerCase()
|
||||
|
||||
try {
|
||||
if (method === "get") {
|
||||
if (url === "/products") {
|
||||
const response = await mockHandlers.getProducts()
|
||||
return Promise.resolve({ data: response.data, status: 200, config, headers: {}, statusText: "OK" })
|
||||
}
|
||||
|
||||
if (url.match(/^\/products\/\d+$/)) {
|
||||
const id = Number.parseInt(url.split("/")[2])
|
||||
const response = await mockHandlers.getProduct(id)
|
||||
return Promise.resolve({ data: response.data, status: 200, config, headers: {}, statusText: "OK" })
|
||||
}
|
||||
|
||||
if (url === "/categories") {
|
||||
const response = await mockHandlers.getCategories()
|
||||
return Promise.resolve({ data: response.data, status: 200, config, headers: {}, statusText: "OK" })
|
||||
}
|
||||
|
||||
if (url.match(/^\/categories\/.+$/)) {
|
||||
const slug = url.split("/")[2]
|
||||
const response = await mockHandlers.getCategory(slug)
|
||||
return Promise.resolve({ data: response.data, status: 200, config, headers: {}, statusText: "OK" })
|
||||
}
|
||||
|
||||
if (url === "/cart") {
|
||||
const response = await mockHandlers.getCart()
|
||||
return Promise.resolve({ data: response.data, status: 200, config, headers: {}, statusText: "OK" })
|
||||
}
|
||||
|
||||
if (url === "/favorites") {
|
||||
const response = await mockHandlers.getFavorites()
|
||||
return Promise.resolve({ data: response.data, status: 200, config, headers: {}, statusText: "OK" })
|
||||
}
|
||||
|
||||
if (url === "/orders") {
|
||||
const response = await mockHandlers.getOrders()
|
||||
return Promise.resolve({ data: response.data, status: 200, config, headers: {}, statusText: "OK" })
|
||||
}
|
||||
|
||||
if (url.match(/^\/orders\/\d+$/)) {
|
||||
const id = Number.parseInt(url.split("/")[2])
|
||||
const response = await mockHandlers.getOrder(id)
|
||||
return Promise.resolve({ data: response.data, status: 200, config, headers: {}, statusText: "OK" })
|
||||
}
|
||||
|
||||
if (url.match(/^\/search/)) {
|
||||
const params = new URLSearchParams(url.split("?")[1] || "")
|
||||
const query = params.get("q") || ""
|
||||
const response = await mockHandlers.search(query, {
|
||||
category: params.get("category") || undefined,
|
||||
priceFrom: params.get("priceFrom") ? Number.parseInt(params.get("priceFrom")!) : undefined,
|
||||
priceTo: params.get("priceTo") ? Number.parseInt(params.get("priceTo")!) : undefined,
|
||||
})
|
||||
return Promise.resolve({ data: response.data, status: 200, config, headers: {}, statusText: "OK" })
|
||||
}
|
||||
}
|
||||
|
||||
if (method === "post") {
|
||||
if (url === "/cart/items") {
|
||||
const { productId, quantity } = config.data
|
||||
const response = await mockHandlers.addToCart(productId, quantity)
|
||||
return Promise.resolve({ data: response.data, status: 201, config, headers: {}, statusText: "Created" })
|
||||
}
|
||||
|
||||
if (url === "/favorites") {
|
||||
const { productId } = config.data
|
||||
const response = await mockHandlers.addToFavorites(productId)
|
||||
return Promise.resolve({ data: response.data, status: 201, config, headers: {}, statusText: "Created" })
|
||||
}
|
||||
|
||||
if (url.match(/^\/orders\/\d+\/cancel$/)) {
|
||||
const orderId = Number.parseInt(url.split("/")[2])
|
||||
const response = await mockHandlers.cancelOrder(orderId)
|
||||
return Promise.resolve({ data: response.data, status: 200, config, headers: {}, statusText: "OK" })
|
||||
}
|
||||
}
|
||||
|
||||
if (method === "patch") {
|
||||
if (url.match(/^\/cart\/items\/\d+$/)) {
|
||||
const itemId = Number.parseInt(url.split("/")[3])
|
||||
const { quantity } = config.data
|
||||
const response = await mockHandlers.updateCartItemQuantity(itemId, quantity)
|
||||
return Promise.resolve({ data: response.data, status: 200, config, headers: {}, statusText: "OK" })
|
||||
}
|
||||
}
|
||||
|
||||
if (method === "delete") {
|
||||
if (url.match(/^\/cart\/items\/\d+$/)) {
|
||||
const itemId = Number.parseInt(url.split("/")[3])
|
||||
const response = await mockHandlers.removeFromCart(itemId)
|
||||
return Promise.resolve({ data: response.data, status: 200, config, headers: {}, statusText: "OK" })
|
||||
}
|
||||
|
||||
if (url.match(/^\/favorites\/\d+$/)) {
|
||||
const productId = Number.parseInt(url.split("/")[2])
|
||||
const response = await mockHandlers.removeFromFavorites(productId)
|
||||
return Promise.resolve({ data: response.data, status: 200, config, headers: {}, statusText: "OK" })
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback - endpoint not mocked
|
||||
return Promise.reject({
|
||||
response: { status: 404, data: { message: "Endpoint not found" }, config },
|
||||
})
|
||||
} catch (error: any) {
|
||||
return Promise.reject({
|
||||
response: {
|
||||
status: 400,
|
||||
data: { message: error.message },
|
||||
config,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
95
lib/mock-server/data.ts
Normal file
95
lib/mock-server/data.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Mock data for development and testing
|
||||
*/
|
||||
|
||||
export const mockProducts = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Premium Wireless Headphones",
|
||||
price: 199.99,
|
||||
category: "electronics",
|
||||
image: "/wireless-headphones.png",
|
||||
description: "High-quality sound with noise cancellation",
|
||||
stock: 50,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Classic Analog Watch",
|
||||
price: 89.99,
|
||||
category: "accessories",
|
||||
image: "/analog-watch.png",
|
||||
description: "Timeless design with precision movement",
|
||||
stock: 30,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Portable Charger 20000mAh",
|
||||
price: 49.99,
|
||||
category: "electronics",
|
||||
image: "/portable-charger-lifestyle.png",
|
||||
description: "Fast charging technology with dual ports",
|
||||
stock: 100,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "Leather Messenger Bag",
|
||||
price: 129.99,
|
||||
category: "accessories",
|
||||
image: "/leather-messenger-bag.png",
|
||||
description: "Premium leather construction",
|
||||
stock: 25,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "4K Webcam",
|
||||
price: 149.99,
|
||||
category: "electronics",
|
||||
image: "/4k-webcam.jpg",
|
||||
description: "Professional quality streaming camera",
|
||||
stock: 40,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: "USB-C Hub 7-in-1",
|
||||
price: 59.99,
|
||||
category: "electronics",
|
||||
image: "/usb-c-hub.jpg",
|
||||
description: "Multiple ports for connectivity",
|
||||
stock: 75,
|
||||
},
|
||||
]
|
||||
|
||||
export const mockCategories = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Electronics",
|
||||
slug: "electronics",
|
||||
image: "/electronics-category.png",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Accessories",
|
||||
slug: "accessories",
|
||||
image: "/accessories-category.png",
|
||||
},
|
||||
]
|
||||
|
||||
export const mockOrders = [
|
||||
{
|
||||
id: 101,
|
||||
status: "delivered" as const,
|
||||
total: 299.98,
|
||||
createdAt: new Date("2024-11-01").toISOString(),
|
||||
},
|
||||
{
|
||||
id: 102,
|
||||
status: "processing" as const,
|
||||
total: 199.99,
|
||||
createdAt: new Date("2024-11-05").toISOString(),
|
||||
},
|
||||
]
|
||||
|
||||
export const mockFavorites = [
|
||||
{ id: 1, productId: 1, addedAt: new Date().toISOString() },
|
||||
{ id: 2, productId: 3, addedAt: new Date().toISOString() },
|
||||
]
|
||||
181
lib/mock-server/handlers.ts
Normal file
181
lib/mock-server/handlers.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
/**
|
||||
* Mock HTTP handlers for development
|
||||
* Simulates API responses for TanStack Query testing
|
||||
*/
|
||||
|
||||
import { mockProducts, mockCategories, mockOrders, mockFavorites } from "./data"
|
||||
|
||||
interface MockCart {
|
||||
id: string
|
||||
items: Array<{ id: number; productId: number; quantity: number; price: number }>
|
||||
total: number
|
||||
}
|
||||
|
||||
// In-memory storage for development
|
||||
const mockCart: MockCart = {
|
||||
id: "cart-1",
|
||||
items: [],
|
||||
total: 0,
|
||||
}
|
||||
|
||||
let mockUserFavorites = [...mockFavorites]
|
||||
|
||||
/**
|
||||
* Simulate network delay for realistic testing
|
||||
*/
|
||||
export const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||
|
||||
/**
|
||||
* Mock API handlers
|
||||
*/
|
||||
export const mockHandlers = {
|
||||
// Products
|
||||
async getProducts() {
|
||||
await delay(300)
|
||||
return { data: mockProducts }
|
||||
},
|
||||
|
||||
async getProduct(id: number) {
|
||||
await delay(300)
|
||||
const product = mockProducts.find((p) => p.id === id)
|
||||
if (!product) throw new Error("Product not found")
|
||||
return { data: product }
|
||||
},
|
||||
|
||||
// Categories
|
||||
async getCategories() {
|
||||
await delay(300)
|
||||
return { data: mockCategories }
|
||||
},
|
||||
|
||||
async getCategory(slug: string) {
|
||||
await delay(300)
|
||||
const category = mockCategories.find((c) => c.slug === slug)
|
||||
if (!category) throw new Error("Category not found")
|
||||
const products = mockProducts.filter((p) => p.category === slug)
|
||||
return { data: { ...category, products } }
|
||||
},
|
||||
|
||||
// Cart operations
|
||||
async getCart() {
|
||||
await delay(200)
|
||||
return { data: mockCart }
|
||||
},
|
||||
|
||||
async addToCart(productId: number, quantity = 1) {
|
||||
await delay(300)
|
||||
const product = mockProducts.find((p) => p.id === productId)
|
||||
if (!product) throw new Error("Product not found")
|
||||
|
||||
const existingItem = mockCart.items.find((item) => item.productId === productId)
|
||||
if (existingItem) {
|
||||
existingItem.quantity += quantity
|
||||
} else {
|
||||
mockCart.items.push({
|
||||
id: Date.now(),
|
||||
productId,
|
||||
quantity,
|
||||
price: product.price,
|
||||
})
|
||||
}
|
||||
|
||||
mockCart.total = mockCart.items.reduce((sum, item) => sum + item.price * item.quantity, 0)
|
||||
return { data: mockCart }
|
||||
},
|
||||
|
||||
async removeFromCart(itemId: number) {
|
||||
await delay(300)
|
||||
mockCart.items = mockCart.items.filter((item) => item.id !== itemId)
|
||||
mockCart.total = mockCart.items.reduce((sum, item) => sum + item.price * item.quantity, 0)
|
||||
return { data: mockCart }
|
||||
},
|
||||
|
||||
async updateCartItemQuantity(itemId: number, quantity: number) {
|
||||
await delay(300)
|
||||
const item = mockCart.items.find((i) => i.id === itemId)
|
||||
if (!item) throw new Error("Item not found")
|
||||
item.quantity = Math.max(1, quantity)
|
||||
mockCart.total = mockCart.items.reduce((sum, i) => sum + i.price * i.quantity, 0)
|
||||
return { data: mockCart }
|
||||
},
|
||||
|
||||
// Favorites
|
||||
async getFavorites() {
|
||||
await delay(200)
|
||||
return { data: mockUserFavorites }
|
||||
},
|
||||
|
||||
async addToFavorites(productId: number) {
|
||||
await delay(300)
|
||||
const product = mockProducts.find((p) => p.id === productId)
|
||||
if (!product) throw new Error("Product not found")
|
||||
|
||||
const exists = mockUserFavorites.find((f) => f.productId === productId)
|
||||
if (!exists) {
|
||||
mockUserFavorites.push({
|
||||
id: Date.now(),
|
||||
productId,
|
||||
addedAt: new Date().toISOString(),
|
||||
})
|
||||
}
|
||||
return { data: mockUserFavorites }
|
||||
},
|
||||
|
||||
async removeFromFavorites(productId: number) {
|
||||
await delay(300)
|
||||
mockUserFavorites = mockUserFavorites.filter((f) => f.productId !== productId)
|
||||
return { data: mockUserFavorites }
|
||||
},
|
||||
|
||||
// Orders
|
||||
async getOrders() {
|
||||
await delay(300)
|
||||
return { data: mockOrders }
|
||||
},
|
||||
|
||||
async getOrder(id: number) {
|
||||
await delay(300)
|
||||
const order = mockOrders.find((o) => o.id === id)
|
||||
if (!order) throw new Error("Order not found")
|
||||
return { data: order }
|
||||
},
|
||||
|
||||
async cancelOrder(orderId: number) {
|
||||
await delay(300)
|
||||
const order = mockOrders.find((o) => o.id === orderId)
|
||||
if (!order) throw new Error("Order not found")
|
||||
if (order.status !== "processing" && order.status !== "pending") {
|
||||
throw new Error("Cannot cancel shipped or delivered orders")
|
||||
}
|
||||
order.status = "cancelled"
|
||||
return { data: order }
|
||||
},
|
||||
|
||||
// Search
|
||||
async search(query: string, filters?: { category?: string; priceFrom?: number; priceTo?: number }) {
|
||||
await delay(400)
|
||||
let results = mockProducts
|
||||
|
||||
if (query) {
|
||||
results = results.filter(
|
||||
(p) =>
|
||||
p.name.toLowerCase().includes(query.toLowerCase()) ||
|
||||
p.description.toLowerCase().includes(query.toLowerCase()),
|
||||
)
|
||||
}
|
||||
|
||||
if (filters?.category) {
|
||||
results = results.filter((p) => p.category === filters.category)
|
||||
}
|
||||
|
||||
if (filters?.priceFrom) {
|
||||
results = results.filter((p) => p.price >= filters.priceFrom!)
|
||||
}
|
||||
|
||||
if (filters?.priceTo) {
|
||||
results = results.filter((p) => p.price <= filters.priceTo!)
|
||||
}
|
||||
|
||||
return { data: { products: results, total: results.length } }
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user