Compare commits
3 الالتزامات
المؤلف | SHA1 | التاريخ | |
---|---|---|---|
39949bceaf | |||
80e4f88192 | |||
96e9ddd94a |
25
Dockerfile
Normal file
25
Dockerfile
Normal file
@@ -0,0 +1,25 @@
|
||||
FROM node:20-alpine AS build
|
||||
WORKDIR /app
|
||||
|
||||
# انسخ package.json و package-lock.json
|
||||
COPY package*.json ./
|
||||
|
||||
# نزّل الـ dependencies
|
||||
RUN npm install
|
||||
|
||||
# انسخ باقي الملفات
|
||||
COPY . .
|
||||
|
||||
# نفّذ build باستخدام Vite
|
||||
RUN npm run build
|
||||
|
||||
# المرحلة الثانية: Nginx
|
||||
FROM nginx:alpine
|
||||
# انسخ ملفات Vite build (موجودة بـ dist)
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
|
||||
# افتح بورت 80
|
||||
EXPOSE 80
|
||||
|
||||
# شغّل Nginx
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
654
src/App.tsx
654
src/App.tsx
@@ -195,6 +195,660 @@ const mockData: APITestData = {
|
||||
}
|
||||
},
|
||||
"status": "failed"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"title": "Valid userId without includePosts",
|
||||
"input": {
|
||||
"urlParams": {
|
||||
"userId": 456
|
||||
},
|
||||
"query": {},
|
||||
"headers": {
|
||||
"Authorization": "Bearer valid_token"
|
||||
},
|
||||
"body": {}
|
||||
},
|
||||
"expectedResponse": {
|
||||
"statusCode": 200,
|
||||
"body": {
|
||||
"id": 456,
|
||||
"name": "Jane Smith"
|
||||
}
|
||||
},
|
||||
"actualResponse": {
|
||||
"statusCode": 200,
|
||||
"body": {
|
||||
"id": 456,
|
||||
"name": "Jane Smith"
|
||||
}
|
||||
},
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"title": "Invalid token format",
|
||||
"input": {
|
||||
"urlParams": {
|
||||
"userId": 123
|
||||
},
|
||||
"query": {
|
||||
"includePosts": false
|
||||
},
|
||||
"headers": {
|
||||
"Authorization": "InvalidToken"
|
||||
},
|
||||
"body": {}
|
||||
},
|
||||
"expectedResponse": {
|
||||
"statusCode": 401,
|
||||
"body": {
|
||||
"error": "Invalid token format"
|
||||
}
|
||||
},
|
||||
"actualResponse": {
|
||||
"statusCode": 401,
|
||||
"body": {
|
||||
"error": "Invalid token format"
|
||||
}
|
||||
},
|
||||
"status": "passed"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Create New Post",
|
||||
"method": "POST",
|
||||
"endpoint": "/posts",
|
||||
"description": "Create a new blog post",
|
||||
"urlParams": [],
|
||||
"query": [],
|
||||
"headers": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"type": "string",
|
||||
"example": "Bearer <token>",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"type": "string",
|
||||
"example": "application/json",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"required": true,
|
||||
"fields": [
|
||||
{
|
||||
"name": "title",
|
||||
"type": "string",
|
||||
"example": "My First Post",
|
||||
"required": true,
|
||||
"description": "Title of the post"
|
||||
},
|
||||
{
|
||||
"name": "content",
|
||||
"type": "string",
|
||||
"example": "This is the post content",
|
||||
"required": true,
|
||||
"description": "Main content of the post"
|
||||
},
|
||||
{
|
||||
"name": "userId",
|
||||
"type": "integer",
|
||||
"example": 123,
|
||||
"required": true,
|
||||
"description": "ID of the user creating the post"
|
||||
},
|
||||
{
|
||||
"name": "tags",
|
||||
"type": "array",
|
||||
"example": ["tech", "programming"],
|
||||
"required": false,
|
||||
"description": "Post tags"
|
||||
}
|
||||
]
|
||||
},
|
||||
"testCases": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Create post with all fields",
|
||||
"input": {
|
||||
"urlParams": {},
|
||||
"query": {},
|
||||
"headers": {
|
||||
"Authorization": "Bearer valid_token",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"title": "My First Post",
|
||||
"content": "This is the post content",
|
||||
"userId": 123,
|
||||
"tags": ["tech", "programming"]
|
||||
}
|
||||
},
|
||||
"expectedResponse": {
|
||||
"statusCode": 201,
|
||||
"body": {
|
||||
"id": 1,
|
||||
"title": "My First Post",
|
||||
"content": "This is the post content",
|
||||
"userId": 123,
|
||||
"tags": ["tech", "programming"],
|
||||
"createdAt": "2025-10-02T10:00:00Z"
|
||||
}
|
||||
},
|
||||
"actualResponse": {
|
||||
"statusCode": 201,
|
||||
"body": {
|
||||
"id": 1,
|
||||
"title": "My First Post",
|
||||
"content": "This is the post content",
|
||||
"userId": 123,
|
||||
"tags": ["tech", "programming"],
|
||||
"createdAt": "2025-10-02T10:00:00Z"
|
||||
}
|
||||
},
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "Missing required field - title",
|
||||
"input": {
|
||||
"urlParams": {},
|
||||
"query": {},
|
||||
"headers": {
|
||||
"Authorization": "Bearer valid_token",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"content": "This is the post content",
|
||||
"userId": 123
|
||||
}
|
||||
},
|
||||
"expectedResponse": {
|
||||
"statusCode": 400,
|
||||
"body": {
|
||||
"error": "Missing required field: title"
|
||||
}
|
||||
},
|
||||
"actualResponse": {
|
||||
"statusCode": 400,
|
||||
"body": {
|
||||
"error": "Missing required field: title"
|
||||
}
|
||||
},
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"title": "Invalid userId",
|
||||
"input": {
|
||||
"urlParams": {},
|
||||
"query": {},
|
||||
"headers": {
|
||||
"Authorization": "Bearer valid_token",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"title": "New Post",
|
||||
"content": "Content here",
|
||||
"userId": 9999
|
||||
}
|
||||
},
|
||||
"expectedResponse": {
|
||||
"statusCode": 404,
|
||||
"body": {
|
||||
"error": "User not found"
|
||||
}
|
||||
},
|
||||
"actualResponse": {
|
||||
"statusCode": 400,
|
||||
"body": {
|
||||
"error": "Invalid userId"
|
||||
}
|
||||
},
|
||||
"status": "failed"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"title": "Empty content",
|
||||
"input": {
|
||||
"urlParams": {},
|
||||
"query": {},
|
||||
"headers": {
|
||||
"Authorization": "Bearer valid_token",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"title": "Post with no content",
|
||||
"content": "",
|
||||
"userId": 123
|
||||
}
|
||||
},
|
||||
"expectedResponse": {
|
||||
"statusCode": 400,
|
||||
"body": {
|
||||
"error": "Content cannot be empty"
|
||||
}
|
||||
},
|
||||
"actualResponse": {
|
||||
"statusCode": 400,
|
||||
"body": {
|
||||
"error": "Content cannot be empty"
|
||||
}
|
||||
},
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"title": "Unauthorized request",
|
||||
"input": {
|
||||
"urlParams": {},
|
||||
"query": {},
|
||||
"headers": {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"title": "Unauthorized Post",
|
||||
"content": "This should not be created",
|
||||
"userId": 123
|
||||
}
|
||||
},
|
||||
"expectedResponse": {
|
||||
"statusCode": 401,
|
||||
"body": {
|
||||
"error": "Unauthorized"
|
||||
}
|
||||
},
|
||||
"actualResponse": {
|
||||
"statusCode": 401,
|
||||
"body": {
|
||||
"error": "Unauthorized"
|
||||
}
|
||||
},
|
||||
"status": "passed"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Update User Profile",
|
||||
"method": "PUT",
|
||||
"endpoint": "/users/{userId}",
|
||||
"description": "Update user profile information",
|
||||
"urlParams": [
|
||||
{
|
||||
"name": "userId",
|
||||
"type": "integer",
|
||||
"example": 123,
|
||||
"required": true,
|
||||
"description": "ID of the user to update"
|
||||
}
|
||||
],
|
||||
"query": [],
|
||||
"headers": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"type": "string",
|
||||
"example": "Bearer <token>",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"name": "Content-Type",
|
||||
"type": "string",
|
||||
"example": "application/json",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"required": true,
|
||||
"fields": [
|
||||
{
|
||||
"name": "name",
|
||||
"type": "string",
|
||||
"example": "John Doe",
|
||||
"required": false,
|
||||
"description": "User's full name"
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"type": "string",
|
||||
"example": "john@example.com",
|
||||
"required": false,
|
||||
"description": "User's email address"
|
||||
},
|
||||
{
|
||||
"name": "phone",
|
||||
"type": "string",
|
||||
"example": "+1234567890",
|
||||
"required": false,
|
||||
"description": "User's phone number"
|
||||
}
|
||||
]
|
||||
},
|
||||
"testCases": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Update user name",
|
||||
"input": {
|
||||
"urlParams": {
|
||||
"userId": 123
|
||||
},
|
||||
"query": {},
|
||||
"headers": {
|
||||
"Authorization": "Bearer valid_token",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"name": "John Smith"
|
||||
}
|
||||
},
|
||||
"expectedResponse": {
|
||||
"statusCode": 200,
|
||||
"body": {
|
||||
"id": 123,
|
||||
"name": "John Smith",
|
||||
"email": "john@example.com",
|
||||
"phone": "+1234567890"
|
||||
}
|
||||
},
|
||||
"actualResponse": {
|
||||
"statusCode": 200,
|
||||
"body": {
|
||||
"id": 123,
|
||||
"name": "John Smith",
|
||||
"email": "john@example.com",
|
||||
"phone": "+1234567890"
|
||||
}
|
||||
},
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "Update multiple fields",
|
||||
"input": {
|
||||
"urlParams": {
|
||||
"userId": 123
|
||||
},
|
||||
"query": {},
|
||||
"headers": {
|
||||
"Authorization": "Bearer valid_token",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"name": "Jane Doe",
|
||||
"email": "jane@example.com"
|
||||
}
|
||||
},
|
||||
"expectedResponse": {
|
||||
"statusCode": 200,
|
||||
"body": {
|
||||
"id": 123,
|
||||
"name": "Jane Doe",
|
||||
"email": "jane@example.com",
|
||||
"phone": "+1234567890"
|
||||
}
|
||||
},
|
||||
"actualResponse": {
|
||||
"statusCode": 200,
|
||||
"body": {
|
||||
"id": 123,
|
||||
"name": "Jane Doe",
|
||||
"email": "jane@example.com",
|
||||
"phone": "+1234567890"
|
||||
}
|
||||
},
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"title": "Invalid email format",
|
||||
"input": {
|
||||
"urlParams": {
|
||||
"userId": 123
|
||||
},
|
||||
"query": {},
|
||||
"headers": {
|
||||
"Authorization": "Bearer valid_token",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"email": "invalid-email"
|
||||
}
|
||||
},
|
||||
"expectedResponse": {
|
||||
"statusCode": 400,
|
||||
"body": {
|
||||
"error": "Invalid email format"
|
||||
}
|
||||
},
|
||||
"actualResponse": {
|
||||
"statusCode": 400,
|
||||
"body": {
|
||||
"error": "Invalid email format"
|
||||
}
|
||||
},
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"title": "Update non-existent user",
|
||||
"input": {
|
||||
"urlParams": {
|
||||
"userId": 9999
|
||||
},
|
||||
"query": {},
|
||||
"headers": {
|
||||
"Authorization": "Bearer valid_token",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"name": "Ghost User"
|
||||
}
|
||||
},
|
||||
"expectedResponse": {
|
||||
"statusCode": 404,
|
||||
"body": {
|
||||
"error": "User not found"
|
||||
}
|
||||
},
|
||||
"actualResponse": {
|
||||
"statusCode": 404,
|
||||
"body": {
|
||||
"error": "User not found"
|
||||
}
|
||||
},
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"title": "Forbidden - update another user",
|
||||
"input": {
|
||||
"urlParams": {
|
||||
"userId": 456
|
||||
},
|
||||
"query": {},
|
||||
"headers": {
|
||||
"Authorization": "Bearer user_123_token",
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
"body": {
|
||||
"name": "Hacker Name"
|
||||
}
|
||||
},
|
||||
"expectedResponse": {
|
||||
"statusCode": 403,
|
||||
"body": {
|
||||
"error": "Forbidden: Cannot update another user's profile"
|
||||
}
|
||||
},
|
||||
"actualResponse": {
|
||||
"statusCode": 401,
|
||||
"body": {
|
||||
"error": "Unauthorized"
|
||||
}
|
||||
},
|
||||
"status": "failed"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Delete Post",
|
||||
"method": "DELETE",
|
||||
"endpoint": "/posts/{postId}",
|
||||
"description": "Delete a blog post by ID",
|
||||
"urlParams": [
|
||||
{
|
||||
"name": "postId",
|
||||
"type": "integer",
|
||||
"example": 42,
|
||||
"required": true,
|
||||
"description": "ID of the post to delete"
|
||||
}
|
||||
],
|
||||
"query": [],
|
||||
"headers": [
|
||||
{
|
||||
"name": "Authorization",
|
||||
"type": "string",
|
||||
"example": "Bearer <token>",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"body": {
|
||||
"required": false,
|
||||
"fields": []
|
||||
},
|
||||
"testCases": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Successfully delete post",
|
||||
"input": {
|
||||
"urlParams": {
|
||||
"postId": 42
|
||||
},
|
||||
"query": {},
|
||||
"headers": {
|
||||
"Authorization": "Bearer valid_token"
|
||||
},
|
||||
"body": {}
|
||||
},
|
||||
"expectedResponse": {
|
||||
"statusCode": 204,
|
||||
"body": {}
|
||||
},
|
||||
"actualResponse": {
|
||||
"statusCode": 204,
|
||||
"body": {}
|
||||
},
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "Delete non-existent post",
|
||||
"input": {
|
||||
"urlParams": {
|
||||
"postId": 9999
|
||||
},
|
||||
"query": {},
|
||||
"headers": {
|
||||
"Authorization": "Bearer valid_token"
|
||||
},
|
||||
"body": {}
|
||||
},
|
||||
"expectedResponse": {
|
||||
"statusCode": 404,
|
||||
"body": {
|
||||
"error": "Post not found"
|
||||
}
|
||||
},
|
||||
"actualResponse": {
|
||||
"statusCode": 404,
|
||||
"body": {
|
||||
"error": "Post not found"
|
||||
}
|
||||
},
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"title": "Delete without authorization",
|
||||
"input": {
|
||||
"urlParams": {
|
||||
"postId": 42
|
||||
},
|
||||
"query": {},
|
||||
"headers": {},
|
||||
"body": {}
|
||||
},
|
||||
"expectedResponse": {
|
||||
"statusCode": 401,
|
||||
"body": {
|
||||
"error": "Unauthorized"
|
||||
}
|
||||
},
|
||||
"actualResponse": {
|
||||
"statusCode": 401,
|
||||
"body": {
|
||||
"error": "Unauthorized"
|
||||
}
|
||||
},
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"title": "Delete post owned by another user",
|
||||
"input": {
|
||||
"urlParams": {
|
||||
"postId": 42
|
||||
},
|
||||
"query": {},
|
||||
"headers": {
|
||||
"Authorization": "Bearer wrong_user_token"
|
||||
},
|
||||
"body": {}
|
||||
},
|
||||
"expectedResponse": {
|
||||
"statusCode": 403,
|
||||
"body": {
|
||||
"error": "Forbidden: Cannot delete another user's post"
|
||||
}
|
||||
},
|
||||
"actualResponse": {
|
||||
"statusCode": 403,
|
||||
"body": {
|
||||
"error": "Forbidden: Cannot delete another user's post"
|
||||
}
|
||||
},
|
||||
"status": "passed"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"title": "Invalid postId format",
|
||||
"input": {
|
||||
"urlParams": {
|
||||
"postId": "invalid"
|
||||
},
|
||||
"query": {},
|
||||
"headers": {
|
||||
"Authorization": "Bearer valid_token"
|
||||
},
|
||||
"body": {}
|
||||
},
|
||||
"expectedResponse": {
|
||||
"statusCode": 400,
|
||||
"body": {
|
||||
"error": "Invalid postId format"
|
||||
}
|
||||
},
|
||||
"actualResponse": {
|
||||
"statusCode": 500,
|
||||
"body": {
|
||||
"error": "Internal server error"
|
||||
}
|
||||
},
|
||||
"status": "failed"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
المرجع في مشكلة جديدة
حظر مستخدم