9 Commits
v0.7.0 ... main

Author SHA1 Message Date
aacb33cfa5 Merge pull request #121 from aykhans/feat/config
🔨 Remove default 'http' schema from request URL
2025-06-28 23:45:11 +04:00
4a7db48351 🔨 Remove default 'http' schema from request URL 2025-06-28 23:44:04 +04:00
b73087dce5 Merge pull request #120 from aykhans/dependabot/go_modules/github.com/brianvoe/gofakeit/v7-7.3.0
Bump github.com/brianvoe/gofakeit/v7 from 7.2.1 to 7.3.0
2025-06-27 18:04:40 +04:00
20a46feab8 Bump github.com/brianvoe/gofakeit/v7 from 7.2.1 to 7.3.0
Bumps [github.com/brianvoe/gofakeit/v7](https://github.com/brianvoe/gofakeit) from 7.2.1 to 7.3.0.
- [Release notes](https://github.com/brianvoe/gofakeit/releases)
- [Commits](https://github.com/brianvoe/gofakeit/compare/v7.2.1...v7.3.0)

---
updated-dependencies:
- dependency-name: github.com/brianvoe/gofakeit/v7
  dependency-version: 7.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-27 00:56:38 +00:00
0adde6e04e Merge pull request #118 from aykhans/core/refactor
General Refactoring
2025-06-08 23:39:02 +04:00
ca50de4e2f ⬆️ bump version to 0.7.1 2025-06-08 20:57:01 +04:00
c99e7c66d9 📚 Update docs 2025-06-08 20:56:00 +04:00
280e5f5c4e 📚 Add EXAMPLES.md 2025-06-08 20:55:45 +04:00
47dfad6046 🔧 Add more template functions 2025-06-08 20:55:02 +04:00
6 changed files with 985 additions and 14 deletions

934
EXAMPLES.md Normal file
View File

@ -0,0 +1,934 @@
# Dodo Usage Examples
This document provides comprehensive examples of using Dodo with various configuration combinations. Each example includes three methods: CLI usage, YAML configuration, and JSON configuration.
## Table of Contents
1. [Basic HTTP Stress Testing](#1-basic-http-stress-testing)
2. [POST Request with Form Data](#2-post-request-with-form-data)
3. [API Testing with Authentication](#3-api-testing-with-authentication)
4. [Testing with Custom Headers and Cookies](#4-testing-with-custom-headers-and-cookies)
5. [Load Testing with Proxy Rotation](#5-load-testing-with-proxy-rotation)
6. [JSON API Testing with Dynamic Data](#6-json-api-testing-with-dynamic-data)
7. [File Upload Testing](#7-file-upload-testing)
8. [E-commerce Cart Testing](#8-e-commerce-cart-testing)
9. [GraphQL API Testing](#9-graphql-api-testing)
10. [WebSocket-style HTTP Testing](#10-websocket-style-http-testing)
11. [Multi-tenant Application Testing](#11-multi-tenant-application-testing)
12. [Rate Limiting Testing](#12-rate-limiting-testing)
---
## 1. Basic HTTP Stress Testing
Test a simple website with basic GET requests to measure performance under load.
### CLI Usage
```bash
dodo -u https://httpbin.org/get \
-m GET \
-d 5 \
-r 100 \
-t 5s \
-o 30s \
--skip-verify=false \
-y
```
### YAML Configuration
```yaml
method: "GET"
url: "https://httpbin.org/get"
yes: true
timeout: "5s"
dodos: 5
requests: 100
duration: "30s"
skip_verify: false
```
### JSON Configuration
```json
{
"method": "GET",
"url": "https://httpbin.org/get",
"yes": true,
"timeout": "5s",
"dodos": 5,
"requests": 100,
"duration": "30s",
"skip_verify": false
}
```
---
## 2. POST Request with Form Data
Test form submission endpoints with randomized form data.
### CLI Usage
```bash
dodo -u https://httpbin.org/post \
-m POST \
-d 3 \
-r 50 \
-t 10s \
--skip-verify=false \
-H "Content-Type:application/x-www-form-urlencoded" \
-b "username={{ fakeit_Username }}&password={{ fakeit_Password true true true true true 12 }}&email={{ fakeit_Email }}" \
-b "username={{ fakeit_Username }}&password={{ fakeit_Password true true true true true 8 }}&email={{ fakeit_Email }}" \
-y
```
### YAML Configuration
```yaml
method: "POST"
url: "https://httpbin.org/post"
yes: true
timeout: "10s"
dodos: 3
requests: 50
skip_verify: false
headers:
- Content-Type: "application/x-www-form-urlencoded"
body:
- "username={{ fakeit_Username }}&password={{ fakeit_Password true true true true true 12 }}&email={{ fakeit_Email }}"
- "username={{ fakeit_Username }}&password={{ fakeit_Password true true true true true 8 }}&email={{ fakeit_Email }}"
```
### JSON Configuration
```json
{
"method": "POST",
"url": "https://httpbin.org/post",
"yes": true,
"timeout": "10s",
"dodos": 3,
"requests": 50,
"skip_verify": false,
"headers": [{ "Content-Type": "application/x-www-form-urlencoded" }],
"body": [
"username={{ fakeit_Username }}&password={{ fakeit_Password true true true true true 12 }}&email={{ fakeit_Email }}",
"username={{ fakeit_Username }}&password={{ fakeit_Password true true true true true 8 }}&email={{ fakeit_Email }}"
]
}
```
---
## 3. API Testing with Authentication
Test protected API endpoints with various authentication methods.
### CLI Usage
```bash
dodo -u https://httpbin.org/bearer \
-m GET \
-d 4 \
-r 200 \
-t 8s \
--skip-verify=false \
-H "Authorization:Bearer {{ fakeit_LetterN 32 }}" \
-H "User-Agent:{{ fakeit_UserAgent }}" \
-H "X-Request-ID:{{ fakeit_Int }}" \
-H "Accept:application/json" \
-p "api_version=v1" \
-p "format=json" \
-p "client_id=mobile" -p "client_id=web" -p "client_id=desktop" \
-y
```
### YAML Configuration
```yaml
method: "GET"
url: "https://httpbin.org/bearer"
yes: true
timeout: "8s"
dodos: 4
requests: 200
skip_verify: false
params:
- api_version: "v1"
- format: "json"
- client_id: ["mobile", "web", "desktop"]
headers:
- Authorization: "Bearer {{ fakeit_LetterN 32 }}"
- User-Agent: "{{ fakeit_UserAgent }}"
- X-Request-ID: "{{ fakeit_Int }}"
- Accept: "application/json"
```
### JSON Configuration
```json
{
"method": "GET",
"url": "https://httpbin.org/bearer",
"yes": true,
"timeout": "8s",
"dodos": 4,
"requests": 200,
"skip_verify": false,
"params": [
{ "api_version": "v1" },
{ "format": "json" },
{ "client_id": ["mobile", "web", "desktop"] }
],
"headers": [
{ "Authorization": "Bearer {{ fakeit_LetterN 32 }}" },
{ "User-Agent": "{{ fakeit_UserAgent }}" },
{ "X-Request-ID": "{{ fakeit_Int }}" },
{ "Accept": "application/json" }
]
}
```
---
## 4. Testing with Custom Headers and Cookies
Test applications that require specific headers and session cookies.
### CLI Usage
```bash
dodo -u https://httpbin.org/cookies \
-m GET \
-d 6 \
-r 75 \
-t 5s \
--skip-verify=false \
-H 'Accept-Language:{{ strings_Join "," (fakeit_LanguageAbbreviation) (fakeit_LanguageAbbreviation) (fakeit_LanguageAbbreviation) }}' \
-H "X-Forwarded-For:{{ fakeit_IPv4Address }}" \
-H "X-Real-IP:{{ fakeit_IPv4Address }}" \
-H "Accept-Encoding:gzip" -H "Accept-Encoding:deflate" -H "Accept-Encoding:br" \
-c "session_id={{ fakeit_UUID }}" \
-c 'user_pref={{ fakeit_RandomString "a1" "b2" "c3" }}' \
-c "theme=dark" -c "theme=light" -c "theme=auto" \
-c "lang=en" -c "lang=es" -c "lang=fr" -c "lang=de" \
-y
```
### YAML Configuration
```yaml
method: "GET"
url: "https://httpbin.org/cookies"
yes: true
timeout: "5s"
dodos: 6
requests: 75
skip_verify: false
headers:
- Accept-Language: '{{ strings_Join "," (fakeit_LanguageAbbreviation) (fakeit_LanguageAbbreviation) (fakeit_LanguageAbbreviation) }}'
- X-Forwarded-For: "{{ fakeit_IPv4Address }}"
- X-Real-IP: "{{ fakeit_IPv4Address }}"
- Accept-Encoding: ["gzip", "deflate", "br"]
cookies:
- session_id: "{{ fakeit_UUID }}"
- user_pref: '{{ fakeit_RandomString "a1" "b2" "c3" }}'
- theme: ["dark", "light", "auto"]
- lang: ["en", "es", "fr", "de"]
```
### JSON Configuration
```json
{
"method": "GET",
"url": "https://httpbin.org/cookies",
"yes": true,
"timeout": "5s",
"dodos": 6,
"requests": 75,
"skip_verify": false,
"headers": [
{
"Accept-Language": "{{ strings_Join \",\" (fakeit_LanguageAbbreviation) (fakeit_LanguageAbbreviation) (fakeit_LanguageAbbreviation) }}"
},
{ "X-Forwarded-For": "{{ fakeit_IPv4Address }}" },
{ "X-Real-IP": "{{ fakeit_IPv4Address }}" },
{ "Accept-Encoding": ["gzip", "deflate", "br"] }
],
"cookies": [
{ "session_id": "{{ fakeit_UUID }}" },
{ "user_pref": "{{ fakeit_RandomString \"a1\" \"b2\" \"c3\" }}" },
{ "theme": ["dark", "light", "auto"] },
{ "lang": ["en", "es", "fr", "de"] }
]
}
```
---
## 5. Load Testing with Proxy Rotation
Test through multiple proxies for distributed load testing.
### CLI Usage
```bash
dodo -u https://httpbin.org/ip \
-m GET \
-d 8 \
-r 300 \
-t 15s \
--skip-verify=false \
-x "http://proxy1.example.com:8080" \
-x "http://proxy2.example.com:8080" \
-x "socks5://proxy3.example.com:1080" \
-x "http://username:password@proxy4.example.com:8080" \
-H "User-Agent:{{ fakeit_UserAgent }}" \
-H "Accept:application/json" \
-y
```
### YAML Configuration
```yaml
method: "GET"
url: "https://httpbin.org/ip"
yes: true
timeout: "15s"
dodos: 8
requests: 300
skip_verify: false
proxy:
- "http://proxy1.example.com:8080"
- "http://proxy2.example.com:8080"
- "socks5://proxy3.example.com:1080"
- "http://username:password@proxy4.example.com:8080"
headers:
- User-Agent: "{{ fakeit_UserAgent }}"
- Accept: "application/json"
```
### JSON Configuration
```json
{
"method": "GET",
"url": "https://httpbin.org/ip",
"yes": true,
"timeout": "15s",
"dodos": 8,
"requests": 300,
"skip_verify": false,
"proxy": [
"http://proxy1.example.com:8080",
"http://proxy2.example.com:8080",
"socks5://proxy3.example.com:1080",
"http://username:password@proxy4.example.com:8080"
],
"headers": [
{ "User-Agent": "{{ fakeit_UserAgent }}" },
{ "Accept": "application/json" }
]
}
```
---
## 6. JSON API Testing with Dynamic Data
Test REST APIs with realistic JSON payloads.
### CLI Usage
```bash
dodo -u https://httpbin.org/post \
-m POST \
-d 5 \
-r 150 \
-t 12s \
--skip-verify=false \
-H "Content-Type:application/json" \
-H "Accept:application/json" \
-H "X-API-Version:2023-10-01" \
-b '{"user_id":{{ fakeit_Uint }},"name":"{{ fakeit_Name }}","email":"{{ fakeit_Email }}","created_at":"{{ fakeit_Date }}"}' \
-b '{"product_id":{{ fakeit_Uint }},"name":"{{ fakeit_ProductName }}","price":{{ fakeit_Price 10 1000 }},"category":"{{ fakeit_ProductCategory }}"}' \
-b '{"order_id":"{{ fakeit_UUID }}","items":[{"id":{{ fakeit_Uint }},"quantity":{{ fakeit_IntRange 1 10 }}}],"total":{{ fakeit_Price 50 500 }}}' \
-y
```
### YAML Configuration
```yaml
method: "POST"
url: "https://httpbin.org/post"
yes: true
timeout: "12s"
dodos: 5
requests: 150
skip_verify: false
headers:
- Content-Type: "application/json"
- Accept: "application/json"
- X-API-Version: "2023-10-01"
body:
- '{"user_id":{{ fakeit_Uint }},"name":"{{ fakeit_Name }}","email":"{{ fakeit_Email }}","created_at":"{{ fakeit_Date }}"}'
- '{"product_id":{{ fakeit_Uint }},"name":"{{ fakeit_ProductName }}","price":{{ fakeit_Price 10 1000 }},"category":"{{ fakeit_ProductCategory }}"}'
- '{"order_id":"{{ fakeit_UUID }}","items":[{"id":{{ fakeit_Uint }},"quantity":{{ fakeit_IntRange 1 10 }}}],"total":{{ fakeit_Price 50 500 }}}'
```
### JSON Configuration
```json
{
"method": "POST",
"url": "https://httpbin.org/post",
"yes": true,
"timeout": "12s",
"dodos": 5,
"requests": 150,
"skip_verify": false,
"headers": [
{ "Content-Type": "application/json" },
{ "Accept": "application/json" },
{ "X-API-Version": "2023-10-01" }
],
"body": [
"{\"user_id\":{{ fakeit_Uint }},\"name\":\"{{ fakeit_Name }}\",\"email\":\"{{ fakeit_Email }}\",\"created_at\":\"{{ fakeit_Date }}\"}",
"{\"product_id\":{{ fakeit_Uint }},\"name\":\"{{ fakeit_ProductName }}\",\"price\":{{ fakeit_Price 10 1000 }},\"category\":\"{{ fakeit_ProductCategory }}\"}",
"{\"order_id\":\"{{ fakeit_UUID }}\",\"items\":[{\"id\":{{ fakeit_Uint }},\"quantity\":{{ fakeit_IntRange 1 10 }}}],\"total\":{{ fakeit_Price 50 500 }}}"
]
}
```
---
## 7. File Upload Testing
Test file upload endpoints with multipart form data.
### CLI Usage
```bash
dodo -u https://httpbin.org/post \
-m POST \
-d 3 \
-r 25 \
-t 30s \
--skip-verify=false \
-H "X-Upload-Source:dodo-test" \
-H "User-Agent:{{ fakeit_UserAgent }}" \
-b '{{ body_FormData (dict_Str "filename" (fakeit_UUID) "content" (fakeit_Paragraph 3 5 10 " ")) }}' \
-b '{{ body_FormData (dict_Str "file" (fakeit_UUID) "description" (fakeit_Sentence 10) "category" "image") }}' \
-y
```
### YAML Configuration
```yaml
method: "POST"
url: "https://httpbin.org/post"
yes: true
timeout: "30s"
dodos: 3
requests: 25
skip_verify: false
headers:
- X-Upload-Source: "dodo-test"
- User-Agent: "{{ fakeit_UserAgent }}"
body:
- '{{ body_FormData (dict_Str "filename" (fakeit_UUID) "content" (fakeit_Paragraph 3 5 10 " ")) }}'
- '{{ body_FormData (dict_Str "file" (fakeit_UUID) "description" (fakeit_Sentence 10) "category" "image") }}'
```
### JSON Configuration
```json
{
"method": "POST",
"url": "https://httpbin.org/post",
"yes": true,
"timeout": "30s",
"dodos": 3,
"requests": 25,
"skip_verify": false,
"headers": [
{ "X-Upload-Source": "dodo-test" },
{ "User-Agent": "{{ fakeit_UserAgent }}" }
],
"body": [
"{{ body_FormData (dict_Str \"filename\" (fakeit_UUID) \"content\" (fakeit_Paragraph 3 5 10 \" \")) }}",
"{{ body_FormData (dict_Str \"file\" (fakeit_UUID) \"description\" (fakeit_Sentence 10) \"category\" \"image\") }}"
]
}
```
---
## 8. E-commerce Cart Testing
Test shopping cart operations with realistic product data.
### CLI Usage
```bash
dodo -u https://api.example-shop.com/cart \
-m POST \
-d 10 \
-r 500 \
-t 8s \
--skip-verify=false \
-H "Content-Type:application/json" \
-H "Authorization:Bearer {{ fakeit_LetterN 32 }}" \
-H "X-Client-Version:1.2.3" \
-H "User-Agent:{{ fakeit_UserAgent }}" \
-c "cart_session={{ fakeit_UUID }}" \
-c "user_pref=guest" -c "user_pref=member" -c "user_pref=premium" \
-c "region=US" -c "region=EU" -c "region=ASIA" \
-p "currency=USD" -p "currency=EUR" -p "currency=GBP" \
-p "locale=en-US" -p "locale=en-GB" -p "locale=de-DE" -p "locale=fr-FR" \
-b '{"action":"add","product_id":"{{ fakeit_UUID }}","quantity":{{ fakeit_IntRange 1 5 }},"user_id":"{{ fakeit_UUID }}"}' \
-b '{"action":"remove","product_id":"{{ fakeit_UUID }}","user_id":"{{ fakeit_UUID }}"}' \
-b '{"action":"update","product_id":"{{ fakeit_UUID }}","quantity":{{ fakeit_IntRange 1 10 }},"user_id":"{{ fakeit_UUID }}"}' \
-y
```
### YAML Configuration
```yaml
method: "POST"
url: "https://api.example-shop.com/cart"
yes: true
timeout: "8s"
dodos: 10
requests: 500
skip_verify: false
headers:
- Content-Type: "application/json"
- Authorization: "Bearer {{ fakeit_LetterN 32 }}"
- X-Client-Version: "1.2.3"
- User-Agent: "{{ fakeit_UserAgent }}"
cookies:
- cart_session: "{{ fakeit_UUID }}"
- user_pref: ["guest", "member", "premium"]
- region: ["US", "EU", "ASIA"]
params:
- currency: ["USD", "EUR", "GBP"]
- locale: ["en-US", "en-GB", "de-DE", "fr-FR"]
body:
- '{"action":"add","product_id":"{{ fakeit_UUID }}","quantity":{{ fakeit_IntRange 1 5 }},"user_id":"{{ fakeit_UUID }}"}'
- '{"action":"remove","product_id":"{{ fakeit_UUID }}","user_id":"{{ fakeit_UUID }}"}'
- '{"action":"update","product_id":"{{ fakeit_UUID }}","quantity":{{ fakeit_IntRange 1 10 }},"user_id":"{{ fakeit_UUID }}"}'
```
### JSON Configuration
```json
{
"method": "POST",
"url": "https://api.example-shop.com/cart",
"yes": true,
"timeout": "8s",
"dodos": 10,
"requests": 500,
"skip_verify": false,
"headers": [
{ "Content-Type": "application/json" },
{ "Authorization": "Bearer {{ fakeit_LetterN 32 }}" },
{ "X-Client-Version": "1.2.3" },
{ "User-Agent": "{{ fakeit_UserAgent }}" }
],
"cookies": [
{ "cart_session": "{{ fakeit_UUID }}" },
{ "user_pref": ["guest", "member", "premium"] },
{ "region": ["US", "EU", "ASIA"] }
],
"params": [
{ "currency": ["USD", "EUR", "GBP"] },
{ "locale": ["en-US", "en-GB", "de-DE", "fr-FR"] }
],
"body": [
"{\"action\":\"add\",\"product_id\":\"{{ fakeit_UUID }}\",\"quantity\":{{ fakeit_IntRange 1 5 }},\"user_id\":\"{{ fakeit_UUID }}\"}",
"{\"action\":\"remove\",\"product_id\":\"{{ fakeit_UUID }}\",\"user_id\":\"{{ fakeit_UUID }}\"}",
"{\"action\":\"update\",\"product_id\":\"{{ fakeit_UUID }}\",\"quantity\":{{ fakeit_IntRange 1 10 }},\"user_id\":\"{{ fakeit_UUID }}\"}"
]
}
```
---
## 9. GraphQL API Testing
Test GraphQL endpoints with various queries and mutations.
### CLI Usage
```bash
dodo -u https://api.example.com/graphql \
-m POST \
-d 4 \
-r 100 \
-t 10s \
--skip-verify=false \
-H "Content-Type:application/json" \
-H "Authorization:Bearer {{ fakeit_UUID }}" \
-H "X-GraphQL-Client:dodo-test" \
-b '{"query":"query GetUser($id: ID!) { user(id: $id) { id name email } }","variables":{"id":"{{ fakeit_UUID }}"}}' \
-b '{"query":"query GetPosts($limit: Int) { posts(limit: $limit) { id title content } }","variables":{"limit":{{ fakeit_IntRange 5 20 }}}}' \
-b '{"query":"mutation CreatePost($input: PostInput!) { createPost(input: $input) { id title } }","variables":{"input":{"title":"{{ fakeit_Sentence 5 }}","content":"{{ fakeit_Paragraph 2 3 5 " "}}","authorId":"{{ fakeit_UUID }}"}}}' \
-y
```
### YAML Configuration
```yaml
method: "POST"
url: "https://api.example.com/graphql"
yes: true
timeout: "10s"
dodos: 4
requests: 100
skip_verify: false
headers:
- Content-Type: "application/json"
- Authorization: "Bearer {{ fakeit_UUID }}"
- X-GraphQL-Client: "dodo-test"
body:
- '{"query":"query GetUser($id: ID!) { user(id: $id) { id name email } }","variables":{"id":"{{ fakeit_UUID }}"}}'
- '{"query":"query GetPosts($limit: Int) { posts(limit: $limit) { id title content } }","variables":{"limit":{{ fakeit_IntRange 5 20 }}}}'
- '{"query":"mutation CreatePost($input: PostInput!) { createPost(input: $input) { id title } }","variables":{"input":{"title":"{{ fakeit_Sentence 5 }}","content":"{{ fakeit_Paragraph 2 3 5 " "}}","authorId":"{{ fakeit_UUID }}"}}}'
```
### JSON Configuration
```json
{
"method": "POST",
"url": "https://api.example.com/graphql",
"yes": true,
"timeout": "10s",
"dodos": 4,
"requests": 100,
"skip_verify": false,
"headers": [
{ "Content-Type": "application/json" },
{ "Authorization": "Bearer {{ fakeit_UUID }}" },
{ "X-GraphQL-Client": "dodo-test" }
],
"body": [
"{\"query\":\"query GetUser($id: ID!) { user(id: $id) { id name email } }\",\"variables\":{\"id\":\"{{ fakeit_UUID }}\"}}",
"{\"query\":\"query GetPosts($limit: Int) { posts(limit: $limit) { id title content } }\",\"variables\":{\"limit\":{{ fakeit_IntRange 5 20 }}}}",
"{\"query\":\"mutation CreatePost($input: PostInput!) { createPost(input: $input) { id title } }\",\"variables\":{\"input\":{\"title\":\"{{ fakeit_Sentence 5 }}\",\"content\":\"{{ fakeit_Paragraph 2 3 5 \\\" \\\"}}\",\"authorId\":\"{{ fakeit_UUID }}\"}}}"
]
}
```
---
## 10. WebSocket-style HTTP Testing
Test real-time applications with WebSocket-like HTTP endpoints.
### CLI Usage
```bash
dodo -u https://api.realtime-app.com/events \
-m POST \
-d 15 \
-r 1000 \
-t 5s \
-o 60s \
--skip-verify=false \
-H "Content-Type:application/json" \
-H "X-Event-Type:{{ fakeit_LetterNN 4 12 }}" \
-H "Connection:keep-alive" \
-H "Cache-Control:no-cache" \
-c "connection_id={{ fakeit_UUID }}" \
-c "session_token={{ fakeit_UUID }}" \
-p "channel=general" -p "channel=notifications" -p "channel=alerts" -p "channel=updates" \
-p "version=v1" -p "version=v2" \
-b '{"event":"{{ fakeit_Word }}","data":{"timestamp":"{{ fakeit_Date }}","user_id":"{{ fakeit_UUID }}","message":"{{ fakeit_Sentence 8 }}"}}' \
-b '{"event":"ping","data":{"timestamp":"{{ fakeit_Date }}","client_id":"{{ fakeit_UUID }}"}}' \
-b '{"event":"status_update","data":{"status":"{{ fakeit_Word }}","user_id":"{{ fakeit_UUID }}","timestamp":"{{ fakeit_Date }}"}}' \
-y
```
### YAML Configuration
```yaml
method: "POST"
url: "https://api.realtime-app.com/events"
yes: true
timeout: "5s"
dodos: 15
requests: 1000
duration: "60s"
skip_verify: false
headers:
- Content-Type: "application/json"
- X-Event-Type: "{{ fakeit_LetterNN 4 12 }}"
- Connection: "keep-alive"
- Cache-Control: "no-cache"
cookies:
- connection_id: "{{ fakeit_UUID }}"
- session_token: "{{ fakeit_UUID }}"
params:
- channel: ["general", "notifications", "alerts", "updates"]
- version: ["v1", "v2"]
body:
- '{"event":"{{ fakeit_Word }}","data":{"timestamp":"{{ fakeit_Date }}","user_id":"{{ fakeit_UUID }}","message":"{{ fakeit_Sentence 8 }}"}}'
- '{"event":"ping","data":{"timestamp":"{{ fakeit_Date }}","client_id":"{{ fakeit_UUID }}"}}'
- '{"event":"status_update","data":{"status":"{{ fakeit_Word }}","user_id":"{{ fakeit_UUID }}","timestamp":"{{ fakeit_Date }}"}}'
```
### JSON Configuration
```json
{
"method": "POST",
"url": "https://api.realtime-app.com/events",
"yes": true,
"timeout": "5s",
"dodos": 15,
"requests": 1000,
"duration": "60s",
"skip_verify": false,
"headers": [
{ "Content-Type": "application/json" },
{ "X-Event-Type": "{{ fakeit_LetterNN 4 12 }}" },
{ "Connection": "keep-alive" },
{ "Cache-Control": "no-cache" }
],
"cookies": [
{ "connection_id": "{{ fakeit_UUID }}" },
{ "session_token": "{{ fakeit_UUID }}" }
],
"params": [
{ "channel": ["general", "notifications", "alerts", "updates"] },
{ "version": ["v1", "v2"] }
],
"body": [
"{\"event\":\"{{ fakeit_Word }}\",\"data\":{\"timestamp\":\"{{ fakeit_Date }}\",\"user_id\":\"{{ fakeit_UUID }}\",\"message\":\"{{ fakeit_Sentence 8 }}\"}}",
"{\"event\":\"ping\",\"data\":{\"timestamp\":\"{{ fakeit_Date }}\",\"client_id\":\"{{ fakeit_UUID }}\"}}",
"{\"event\":\"status_update\",\"data\":{\"status\":\"{{ fakeit_Word }}\",\"user_id\":\"{{ fakeit_UUID }}\",\"timestamp\":\"{{ fakeit_Date }}\"}}"
]
}
```
---
## 11. Multi-tenant Application Testing
Test SaaS applications with tenant-specific configurations.
### CLI Usage
```bash
dodo -u https://app.saas-platform.com/api/data \
-m GET \
-d 12 \
-r 600 \
-t 15s \
--skip-verify=false \
-H "X-Tenant-ID:{{ fakeit_UUID }}" \
-H "Authorization:Bearer {{ fakeit_LetterN 64 }}" \
-H "X-Client-Type:web" -H "X-Client-Type:mobile" -H "X-Client-Type:api" \
-H "Accept:application/json" \
-c "tenant_session={{ fakeit_UUID }}" \
-c "user_role=admin" -c "user_role=user" -c "user_role=viewer" \
-c "subscription_tier=free" -c "subscription_tier=pro" -c "subscription_tier=enterprise" \
-p "page={{ fakeit_IntRange 1 10 }}" \
-p "limit={{ fakeit_IntRange 10 100 }}" \
-p "sort=created_at" -p "sort=updated_at" -p "sort=name" \
-p "order=asc" -p "order=desc" \
-p "filter_by=active" -p "filter_by=inactive" -p "filter_by=pending" \
-y
```
### YAML Configuration
```yaml
method: "GET"
url: "https://app.saas-platform.com/api/data"
yes: true
timeout: "15s"
dodos: 12
requests: 600
skip_verify: false
headers:
- X-Tenant-ID: "{{ fakeit_UUID }}"
- Authorization: "Bearer {{ fakeit_LetterN 64 }}"
- X-Client-Type: ["web", "mobile", "api"]
- Accept: "application/json"
cookies:
- tenant_session: "{{ fakeit_UUID }}"
- user_role: ["admin", "user", "viewer"]
- subscription_tier: ["free", "pro", "enterprise"]
params:
- page: "{{ fakeit_IntRange 1 10 }}"
- limit: "{{ fakeit_IntRange 10 100 }}"
- sort: ["created_at", "updated_at", "name"]
- order: ["asc", "desc"]
- filter_by: ["active", "inactive", "pending"]
```
### JSON Configuration
```json
{
"method": "GET",
"url": "https://app.saas-platform.com/api/data",
"yes": true,
"timeout": "15s",
"dodos": 12,
"requests": 600,
"skip_verify": false,
"headers": [
{ "X-Tenant-ID": "{{ fakeit_UUID }}" },
{ "Authorization": "Bearer {{ fakeit_LetterN 64 }}" },
{ "X-Client-Type": ["web", "mobile", "api"] },
{ "Accept": "application/json" }
],
"cookies": [
{ "tenant_session": "{{ fakeit_UUID }}" },
{ "user_role": ["admin", "user", "viewer"] },
{ "subscription_tier": ["free", "pro", "enterprise"] }
],
"params": [
{ "page": "{{ fakeit_IntRange 1 10 }}" },
{ "limit": "{{ fakeit_IntRange 10 100 }}" },
{ "sort": ["created_at", "updated_at", "name"] },
{ "order": ["asc", "desc"] },
{ "filter_by": ["active", "inactive", "pending"] }
]
}
```
---
## 12. Rate Limiting Testing
Test API rate limits and throttling mechanisms.
### CLI Usage
```bash
dodo -u https://api.rate-limited.com/endpoint \
-m GET \
-d 20 \
-r 2000 \
-t 3s \
-o 120s \
--skip-verify=false \
-H "X-API-Key:{{ fakeit_UUID }}" \
-H "X-Client-ID:{{ fakeit_UUID }}" \
-H "X-Rate-Limit-Test:true" \
-H "User-Agent:{{ fakeit_UserAgent }}" \
-c "rate_limit_bucket={{ fakeit_UUID }}" \
-c "client_tier=tier1" -c "client_tier=tier2" -c "client_tier=tier3" \
-p "burst_test=true" \
-p "client_type=premium" -p "client_type=standard" -p "client_type=free" \
-p "request_id={{ fakeit_UUID }}" \
-y
```
### YAML Configuration
```yaml
method: "GET"
url: "https://api.rate-limited.com/endpoint"
yes: true
timeout: "3s"
dodos: 20
requests: 2000
duration: "120s"
skip_verify: false
headers:
- X-API-Key: "{{ fakeit_UUID }}"
- X-Client-ID: "{{ fakeit_UUID }}"
- X-Rate-Limit-Test: "true"
- User-Agent: "{{ fakeit_UserAgent }}"
params:
- burst_test: "true"
- client_type: ["premium", "standard", "free"]
- request_id: "{{ fakeit_UUID }}"
cookies:
- rate_limit_bucket: "{{ fakeit_UUID }}"
- client_tier: ["tier1", "tier2", "tier3"]
```
### JSON Configuration
```json
{
"method": "GET",
"url": "https://api.rate-limited.com/endpoint",
"yes": true,
"timeout": "3s",
"dodos": 20,
"requests": 2000,
"duration": "120s",
"skip_verify": false,
"headers": [
{ "X-API-Key": "{{ fakeit_UUID }}" },
{ "X-Client-ID": "{{ fakeit_UUID }}" },
{ "X-Rate-Limit-Test": "true" },
{ "User-Agent": "{{ fakeit_UserAgent }}" }
],
"params": [
{ "burst_test": "true" },
{ "client_type": ["premium", "standard", "free"] },
{ "request_id": "{{ fakeit_UUID }}" }
],
"cookies": [
{ "rate_limit_bucket": "{{ fakeit_UUID }}" },
{ "client_tier": ["tier1", "tier2", "tier3"] }
]
}
```
---
## Notes
- All examples use template functions for dynamic data generation
- Adjust `dodos`, `requests`, `duration`, and `timeout` values based on your testing requirements
- Use `skip_verify: true` for testing with self-signed certificates
- Set `yes: true` to skip confirmation prompts in automated testing
- Template functions like `{{ fakeit_* }}` generate random realistic data for each request
- Multiple values in arrays (e.g., `["value1", "value2"]`) will be randomly selected per request
- Use the `body_FormData` function for multipart form uploads
- Proxy configurations support HTTP, SOCKS5, and SOCKS5H protocols
For more template functions and advanced configuration options, refer to the main documentation and `utils/templates.go`.

View File

@ -3,6 +3,26 @@
<img width="30%" height="30%" src="https://ftp.aykhans.me/web/client/pubshares/VzPtSHS7yPQT7ngoZzZSNU/browse?path=%2Fdodo.png"> <img width="30%" height="30%" src="https://ftp.aykhans.me/web/client/pubshares/VzPtSHS7yPQT7ngoZzZSNU/browse?path=%2Fdodo.png">
</p> </p>
<div align="center">
<h4>
<a href="./EXAMPLES.md">
Examples
</a>
<span> | </span>
<a href="#installation">
Install
</a>
<span> | </span>
<a href="https://hub.docker.com/r/aykhans/dodo">
Docker
</a>
</h4>
<br>
<a href="https://coff.ee/aykhan">
<img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 40px !important;width: 150px !important;">
</a>
</div>
## Table of Contents ## Table of Contents
- [Installation](#installation) - [Installation](#installation)
@ -245,6 +265,8 @@ With Docker:
docker run --rm -i -v /path/to/config.json:/config.json aykhans/dodo -f /config.json -u https://example.com -m GET -d 10 -r 1000 -o 1m -t 5s docker run --rm -i -v /path/to/config.json:/config.json aykhans/dodo -f /config.json -u https://example.com -m GET -d 10 -r 1000 -o 1m -t 5s
``` ```
You can find more usage examples in the [EXAMPLES.md](./EXAMPLES.md) file.
## Config Parameters Reference ## Config Parameters Reference
If `Headers`, `Params`, `Cookies`, `Body`, or `Proxy` fields have multiple values, each request will choose a random value from the list. If `Headers`, `Params`, `Cookies`, `Body`, or `Proxy` fields have multiple values, each request will choose a random value from the list.

View File

@ -18,7 +18,7 @@ import (
) )
const ( const (
VERSION string = "0.7.0" VERSION string = "0.7.1"
DefaultUserAgent string = "Dodo/" + VERSION DefaultUserAgent string = "Dodo/" + VERSION
DefaultMethod string = "GET" DefaultMethod string = "GET"
DefaultTimeout time.Duration = time.Second * 10 DefaultTimeout time.Duration = time.Second * 10
@ -159,9 +159,6 @@ func (config *Config) Validate() []error {
if utils.IsNilOrZero(config.URL) { if utils.IsNilOrZero(config.URL) {
errs = append(errs, errors.New("request URL is required")) errs = append(errs, errors.New("request URL is required"))
} else { } else {
if config.URL.Scheme == "" {
config.URL.Scheme = "http"
}
if config.URL.Scheme != "http" && config.URL.Scheme != "https" { if config.URL.Scheme != "http" && config.URL.Scheme != "https" {
errs = append(errs, errors.New("request URL scheme must be http or https")) errs = append(errs, errors.New("request URL scheme must be http or https"))
} }

2
go.mod
View File

@ -3,7 +3,7 @@ module github.com/aykhans/dodo
go 1.24.2 go 1.24.2
require ( require (
github.com/brianvoe/gofakeit/v7 v7.2.1 github.com/brianvoe/gofakeit/v7 v7.3.0
github.com/jedib0t/go-pretty/v6 v6.6.7 github.com/jedib0t/go-pretty/v6 v6.6.7
github.com/valyala/fasthttp v1.62.0 github.com/valyala/fasthttp v1.62.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1

4
go.sum
View File

@ -1,7 +1,7 @@
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/brianvoe/gofakeit/v7 v7.2.1 h1:AGojgaaCdgq4Adzrd2uWdbGNDyX6MWNhHdQBraNfOHI= github.com/brianvoe/gofakeit/v7 v7.3.0 h1:TWStf7/lLpAjKw+bqwzeORo9jvrxToWEwp9b1J2vApQ=
github.com/brianvoe/gofakeit/v7 v7.2.1/go.mod h1:QXuPeBw164PJCzCUZVmgpgHJ3Llj49jSLVkKPMtxtxA= github.com/brianvoe/gofakeit/v7 v7.3.0/go.mod h1:QXuPeBw164PJCzCUZVmgpgHJ3Llj49jSLVkKPMtxtxA=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/jedib0t/go-pretty/v6 v6.6.7 h1:m+LbHpm0aIAPLzLbMfn8dc3Ht8MW7lsSO4MPItz/Uuo= github.com/jedib0t/go-pretty/v6 v6.6.7 h1:m+LbHpm0aIAPLzLbMfn8dc3Ht8MW7lsSO4MPItz/Uuo=

View File

@ -81,20 +81,28 @@ func (g *FuncMapGenerator) newFuncMap() *template.FuncMap {
}, },
"strings_TrimPrefix": strings.TrimPrefix, "strings_TrimPrefix": strings.TrimPrefix,
"strings_TrimSuffix": strings.TrimSuffix, "strings_TrimSuffix": strings.TrimSuffix,
"strings_Join": func(sep string, values ...string) string {
return strings.Join(values, sep)
},
// Dict // Dict
"dict_Str": func(values ...any) map[string]string { "dict_Str": func(values ...string) map[string]string {
dict := make(map[string]string) dict := make(map[string]string)
for i := 0; i < len(values); i += 2 { for i := 0; i < len(values); i += 2 {
if i+1 < len(values) { if i+1 < len(values) {
key := values[i].(string) key := values[i]
value := values[i+1].(string) value := values[i+1]
dict[key] = value dict[key] = value
} }
} }
return dict return dict
}, },
// Slice
"slice_Str": func(values ...string) []string { return values },
"slice_Int": func(values ...int) []int { return values },
"slice_Uint": func(values ...uint) []uint { return values },
// Body // Body
"body_FormData": func(kv map[string]string) string { "body_FormData": func(kv map[string]string) string {
var data bytes.Buffer var data bytes.Buffer
@ -383,12 +391,16 @@ func (g *FuncMapGenerator) newFuncMap() *template.FuncMap {
"fakeit_Number": g.localFaker.Number, "fakeit_Number": g.localFaker.Number,
"fakeit_Int": g.localFaker.Int, "fakeit_Int": g.localFaker.Int,
"fakeit_IntN": g.localFaker.IntN, "fakeit_IntN": g.localFaker.IntN,
"fakeit_IntRange": g.localFaker.IntRange,
"fakeit_RandomInt": g.localFaker.RandomInt,
"fakeit_Int8": g.localFaker.Int8, "fakeit_Int8": g.localFaker.Int8,
"fakeit_Int16": g.localFaker.Int16, "fakeit_Int16": g.localFaker.Int16,
"fakeit_Int32": g.localFaker.Int32, "fakeit_Int32": g.localFaker.Int32,
"fakeit_Int64": g.localFaker.Int64, "fakeit_Int64": g.localFaker.Int64,
"fakeit_Uint": g.localFaker.Uint, "fakeit_Uint": g.localFaker.Uint,
"fakeit_UintN": g.localFaker.UintN, "fakeit_UintN": g.localFaker.UintN,
"fakeit_UintRange": g.localFaker.UintRange,
"fakeit_RandomUint": g.localFaker.RandomUint,
"fakeit_Uint8": g.localFaker.Uint8, "fakeit_Uint8": g.localFaker.Uint8,
"fakeit_Uint16": g.localFaker.Uint16, "fakeit_Uint16": g.localFaker.Uint16,
"fakeit_Uint32": g.localFaker.Uint32, "fakeit_Uint32": g.localFaker.Uint32,
@ -400,12 +412,18 @@ func (g *FuncMapGenerator) newFuncMap() *template.FuncMap {
"fakeit_HexUint": g.localFaker.HexUint, "fakeit_HexUint": g.localFaker.HexUint,
// FakeIt / String // FakeIt / String
"fakeit_Digit": g.localFaker.Digit, "fakeit_Digit": g.localFaker.Digit,
"fakeit_DigitN": g.localFaker.DigitN, "fakeit_DigitN": g.localFaker.DigitN,
"fakeit_Letter": g.localFaker.Letter, "fakeit_Letter": g.localFaker.Letter,
"fakeit_LetterN": g.localFaker.LetterN, "fakeit_LetterN": g.localFaker.LetterN,
"fakeit_LetterNN": func(min, max uint) string {
return g.localFaker.LetterN(g.localFaker.UintRange(min, max))
},
"fakeit_Lexify": g.localFaker.Lexify, "fakeit_Lexify": g.localFaker.Lexify,
"fakeit_Numerify": g.localFaker.Numerify, "fakeit_Numerify": g.localFaker.Numerify,
"fakeit_RandomString": func(values ...string) string {
return g.localFaker.RandomString(values)
},
// FakeIt / Celebrity // FakeIt / Celebrity
"fakeit_CelebrityActor": g.localFaker.CelebrityActor, "fakeit_CelebrityActor": g.localFaker.CelebrityActor,