✅ MyZubster End-to-End Testing Guide
Complete testing strategy – from unit tests to full E2E with Monero simulation
📌 What is This Guide?
This guide covers everything you need to know about testing MyZubster:
✅ Unit Testing – Test individual components
✅ Integration Testing – Test API and database interactions
✅ E2E Testing – Test complete user flows
✅ Mocking – Simulate Monero and external services
✅ CI/CD Pipeline – Automated testing in GitHub Actions
✅ Best Practices – Writing maintainable tests
🧩 Testing Architecture Overview
text
┌─────────────────────────────────────────────────────────────────────────────┐
│ Testing Pyramid │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ End-to-End Tests │ │
│ │ (Cypress / Playwright) │ │
│ │ ──────────────────── │ │
│ │ Few, but critical flows │ │
│ │ ~10% of tests │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Integration Tests │ │
│ │ (Jest + Supertest) │ │
│ │ ──────────────────── │ │
│ │ API + Database interactions │ │
│ │ ~30% of tests │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ Unit Tests │ │
│ │ (Jest) │ │
│ │ ───────── │ │
│ │ Individual functions and modules │ │
│ │ ~60% of tests │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
📂 Test File Structure
text
myzubster/
├── gateway/
│ ├── tests/
│ │ ├── unit/
│ │ │ ├── monero.test.js
│ │ │ ├── payment.test.js
│ │ │ └── webhook.test.js
│ │ ├── integration/
│ │ │ ├── api.test.js
│ │ │ └── database.test.js
│ │ └── e2e/
│ │ └── payment-flow.test.js
│ └── jest.config.js
├── marketplace/
│ ├── tests/
│ │ ├── unit/
│ │ │ ├── auth.test.js
│ │ │ ├── skills.test.js
│ │ │ ├── orders.test.js
│ │ │ └── escrow.test.js
│ │ ├── integration/
│ │ │ ├── api.test.js
│ │ │ └── database.test.js
│ │ └── e2e/
│ │ ├── order-flow.test.js
│ │ └── dispute-flow.test.js
│ └── jest.config.js
└── tests/
├── e2e/
│ ├── full-flow.test.js
│ └── payment-flow.test.js
└── playwright.config.js
🔧 Step 1: Jest Configuration
1.1 Jest Config for Marketplace
File: marketplace/jest.config.js
javascript
module.exports = {
testEnvironment: 'node',
testMatch: ['/tests//.test.js'],
collectCoverageFrom: [
'routes//.js',
'services//*.js',
'models//.js',
'middleware//.js',
'!/node_modules/',
'!/tests/',
],
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
},
setupFilesAfterEnv: ['./tests/setup.js'],
testTimeout: 30000,
verbose: true,
testEnvironmentOptions: {
NODE_ENV: 'test',
},
};
1.2 Jest Config for Gateway
File: gateway/jest.config.js
javascript
module.exports = {
testEnvironment: 'node',
testMatch: ['/tests//.test.js'],
collectCoverageFrom: [
'services//.js',
'models//*.js',
'routes//.js',
'!/node_modules/',
'!/tests/*',
],
coverageThreshold: {
global: {
branches: 75,
functions: 75,
lines: 75,
statements: 75,
},
},
setupFilesAfterEnv: ['./tests/setup.js'],
testTimeout: 30000,
verbose: true,
};
1.3 Test Setup
File: marketplace/tests/setup.js
javascript
const { sequelize } = require('../models');
// Global test setup
beforeAll(async () => {
// Sync database in test mode
await sequelize.sync({ force: true });
});
afterAll(async () => {
// Cleanup
await sequelize.close();
});
// Mock external services
jest.mock('axios');
// Global test utilities
global.createTestUser = async (email = 'test@test.com') => {
// Helper to create test user
};
global.generateTestToken = (user) => {
// Helper to generate JWT for tests
};
🧪 Step 2: Unit Tests
2.1 Auth Unit Tests
File: marketplace/tests/unit/auth.test.js
javascript
const { User } = require('../../models');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
describe('Auth Unit Tests', () => {
describe('Password Hashing', () => {
test('should hash password correctly', async () => {
const password = 'test123';
const hashedPassword = await bcrypt.hash(password, 10);
expect(hashedPassword).not.toBe(password);
expect(await bcrypt.compare(password, hashedPassword)).toBe(true);
});
test('should not match wrong password', async () => {
const password = 'test123';
const wrongPassword = 'wrong456';
const hashedPassword = await bcrypt.hash(password, 10);
expect(await bcrypt.compare(wrongPassword, hashedPassword)).toBe(false);
});
});
describe('JWT Token', () => {
test('should generate valid JWT token', () => {
const user = { id: 1, email: 'test@test.com', role: 'user' };
const token = jwt.sign(user, 'test_secret', { expiresIn: '7d' });
const decoded = jwt.verify(token, 'test_secret');
expect(decoded.id).toBe(1);
expect(decoded.email).toBe('test@test.com');
expect(decoded.role).toBe('user');
});
test('should reject expired token', () => {
const user = { id: 1, email: 'test@test.com', role: 'user' };
const token = jwt.sign(user, 'test_secret', { expiresIn: '1ms' });
// Wait for expiry
setTimeout(() => {
expect(() => jwt.verify(token, 'test_secret')).toThrow();
}, 10);
});
});
});
2.2 Skills Unit Tests
File: marketplace/tests/unit/skills.test.js
javascript
const { Skill } = require('../../models');
describe('Skills Unit Tests', () => {
const validSkillData = {
name: 'Test Skill',
description: 'Test Description',
price: 100,
category: 'development',
sellerId: 1,
};
test('should validate skill data', async () => {
const skill = Skill.build(validSkillData);
const validation = await skill.validate();
expect(validation).toBeUndefined();
});
test('should reject invalid price', async () => {
const invalidData = { ...validSkillData, price: -10 };
const skill = Skill.build(invalidData);
await expect(skill.validate()).rejects.toThrow();
});
test('should reject missing name', async () => {
const invalidData = { ...validSkillData, name: null };
const skill = Skill.build(invalidData);
await expect(skill.validate()).rejects.toThrow();
});
test('should reject missing description', async () => {
const invalidData = { ...validSkillData, description: null };
const skill = Skill.build(invalidData);
await expect(skill.validate()).rejects.toThrow();
});
});
2.3 Escrow Unit Tests
File: marketplace/tests/unit/escrow.test.js
javascript
const { Escrow } = require('../../models');
describe('Escrow Unit Tests', () => {
const validEscrowData = {
orderId: 1,
amount: 150,
currency: 'USD',
moneroAmount: 0.0125,
moneroAddress: '8A1B2C3D4E5F6G7H8I9J0K',
status: 'pending',
};
test('should create escrow with pending status', async () => {
const escrow = Escrow.build(validEscrowData);
expect(escrow.status).toBe('pending');
});
test('should validate amount is positive', async () => {
const invalidData = { ...validEscrowData, amount: -50 };
const escrow = Escrow.build(invalidData);
await expect(escrow.validate()).rejects.toThrow();
});
test('should validate status enum', async () => {
const invalidData = { ...validEscrowData, status: 'invalid_status' };
const escrow = Escrow.build(invalidData);
await expect(escrow.validate()).rejects.toThrow();
});
});
🔗 Step 3: Integration Tests
3.1 Auth Integration Tests
File: marketplace/tests/integration/auth.test.js
javascript
const request = require('supertest');
const app = require('../../server');
const { User } = require('../../models');
describe('Auth Integration Tests', () => {
const testUser = {
email: 'test@test.com',
password: 'test123',
username: 'testuser',
name: 'Test User',
};
describe('Registration', () => {
test('should register a new user', async () => {
const response = await request(app)
.post('/api/auth/register')
.send(testUser)
.expect(201);
expect(response.body).toHaveProperty('token');
expect(response.body.user).toHaveProperty('id');
expect(response.body.user.email).toBe(testUser.email);
// Verify user in database
const user = await User.findOne({ where: { email: testUser.email } });
expect(user).not.toBeNull();
});
test('should reject duplicate email', async () => {
await request(app)
.post('/api/auth/register')
.send(testUser)
.expect(400);
});
test('should reject missing fields', async () => {
const invalidData = { email: 'test@test.com' };
await request(app)
.post('/api/auth/register')
.send(invalidData)
.expect(400);
});
});
describe('Login', () => {
test('should login successfully', async () => {
const response = await request(app)
.post('/api/auth/login')
.send({
email: testUser.email,
password: testUser.password,
})
.expect(200);
expect(response.body).toHaveProperty('token');
expect(response.body.user).toHaveProperty('id');
});
test('should reject wrong password', async () => {
await request(app)
.post('/api/auth/login')
.send({
email: testUser.email,
password: 'wrong',
})
.expect(401);
});
test('should reject non-existent email', async () => {
await request(app)
.post('/api/auth/login')
.send({
email: 'nonexistent@test.com',
password: 'test123',
})
.expect(401);
});
});
});
3.2 Skills Integration Tests
File: marketplace/tests/integration/skills.test.js
javascript
const request = require('supertest');
const app = require('../../server');
const { User, Skill } = require('../../models');
describe('Skills Integration Tests', () => {
let authToken;
let sellerToken;
let testSkillId;
beforeAll(async () => {
// Create test users
const buyer = await User.create({
email: 'buyer@test.com',
password: 'hashed',
username: 'buyer',
name: 'Buyer Test',
});
const seller = await User.create({
email: 'seller@test.com',
password: 'hashed',
username: 'seller',
name: 'Seller Test',
role: 'seller',
});
// Get tokens
// In real tests, login to get tokens
authToken = 'mock_token';
sellerToken = 'mock_seller_token';
});
describe('POST /api/skills', () => {
test('should create a skill as seller', async () => {
const response = await request(app)
.post('/api/skills')
.set('Authorization', Bearer ${sellerToken})
.send({
name: 'Test Skill',
description: 'Test Description',
price: 100,
category: 'development',
})
.expect(201);
testSkillId = response.body.id;
expect(response.body.name).toBe('Test Skill');
expect(response.body.price).toBe(100);
});
test('should reject creation without auth', async () => {
await request(app)
.post('/api/skills')
.send({
name: 'Test Skill',
description: 'Test Description',
price: 100,
category: 'development',
})
.expect(401);
});
test('should reject missing fields', async () => {
await request(app)
.post('/api/skills')
.set('Authorization', `Bearer ${sellerToken}`)
.send({
name: 'Test Skill',
price: 100,
})
.expect(400);
});
});
describe('GET /api/skills', () => {
test('should list all skills', async () => {
const response = await request(app)
.get('/api/skills')
.expect(200);
expect(Array.isArray(response.body.skills)).toBe(true);
expect(response.body.skills.length).toBeGreaterThan(0);
});
test('should filter by category', async () => {
const response = await request(app)
.get('/api/skills?category=development')
.expect(200);
expect(response.body.skills[0].category).toBe('development');
});
});
describe('GET /api/skills/:id', () => {
test('should get skill by id', async () => {
const response = await request(app)
.get(/api/skills/${testSkillId})
.expect(200);
expect(response.body.id).toBe(testSkillId);
});
test('should return 404 for non-existent skill', async () => {
await request(app)
.get('/api/skills/99999')
.expect(404);
});
});
});
3.3 Orders Integration Tests
File: marketplace/tests/integration/orders.test.js
javascript
const request = require('supertest');
const app = require('../../server');
const { Order, User, Skill } = require('../../models');
describe('Orders Integration Tests', () => {
let buyerToken;
let sellerToken;
let testSkillId;
beforeAll(async () => {
// Create test data
const buyer = await User.create({
email: 'buyer@test.com',
password: 'hashed',
username: 'buyer',
name: 'Buyer Test',
});
const seller = await User.create({
email: 'seller@test.com',
password: 'hashed',
username: 'seller',
name: 'Seller Test',
role: 'seller',
});
const skill = await Skill.create({
name: 'Test Skill',
description: 'Test Description',
price: 100,
category: 'development',
sellerId: seller.id,
});
testSkillId = skill.id;
// Tokens would be obtained via login in real tests
buyerToken = 'mock_buyer_token';
sellerToken = 'mock_seller_token';
});
describe('POST /api/orders', () => {
test('should create order as buyer', async () => {
const response = await request(app)
.post('/api/orders')
.set('Authorization', Bearer ${buyerToken})
.send({ skillId: testSkillId })
.expect(201);
expect(response.body).toHaveProperty('id');
expect(response.body.status).toBe('pending');
expect(response.body.amount).toBe(100);
});
test('should reject self-order', async () => {
const response = await request(app)
.post('/api/orders')
.set('Authorization', `Bearer ${sellerToken}`)
.send({ skillId: testSkillId })
.expect(400);
expect(response.body.error).toBe('Non puoi acquistare la tua stessa competenza');
});
test('should reject non-existent skill', async () => {
await request(app)
.post('/api/orders')
.set('Authorization', `Bearer ${buyerToken}`)
.send({ skillId: 99999 })
.expect(404);
});
});
describe('GET /api/orders/my', () => {
test('should get user orders', async () => {
const response = await request(app)
.get('/api/orders/my')
.set('Authorization', Bearer ${buyerToken})
.expect(200);
expect(Array.isArray(response.body.orders)).toBe(true);
});
});
});
🎭 Step 4: Mocking External Services
4.1 Monero Mock
File: gateway/tests/mocks/monero.mock.js
javascript
// Mock Monero client
const mockMoneroClient = {
getWallet: jest.fn().mockResolvedValue({
getBalance: jest.fn().mockResolvedValue({ getBalance: () => 100000 }),
createSubaddress: jest.fn().mockResolvedValue({
getAddress: () => '8A1B2C3D4E5F6G7H8I9J0K',
getIndex: () => 1,
}),
getTransfers: jest.fn().mockResolvedValue([
{
getTxHash: () => 'tx_123456',
getAmount: () => 0.0125,
getNumConfirmations: () => 10,
isConfirmed: () => true,
getTimestamp: () => Date.now(),
},
]),
send: jest.fn().mockResolvedValue({
getTxHash: () => 'tx_789012',
getAmount: () => 0.0125,
getFee: () => 0.0001,
}),
}),
};
jest.mock('monero-javascript', () => ({
createWalletRpc: jest.fn().mockResolvedValue(mockMoneroClient),
}));
module.exports = mockMoneroClient;
4.2 Webhook Mock
File: marketplace/tests/mocks/webhook.mock.js
javascript
// Mock webhook sender
const mockWebhook = {
sendWebhook: jest.fn().mockResolvedValue({
success: true,
order: { id: 1, status: 'paid' },
}),
};
jest.mock('../../services/webhookSender', () => mockWebhook);
module.exports = mockWebhook;
🎬 Step 5: End-to-End Tests
5.1 Full Flow E2E Test
File: marketplace/tests/e2e/full-flow.test.js
javascript
const request = require('supertest');
const app = require('../../server');
const { sequelize } = require('../../models');
describe('End-to-End Flow Tests', () => {
let buyerToken;
let sellerToken;
let skillId;
let orderId;
// Reset database before each test
beforeEach(async () => {
await sequelize.sync({ force: true });
});
test('Complete flow: Register → Login → Create Skill → Place Order → Pay → Complete', async () => {
// 1. Register buyer
const buyerReg = await request(app)
.post('/api/auth/register')
.send({
email: 'buyer@test.com',
password: 'test123',
username: 'buyer',
name: 'Buyer Test',
})
.expect(201);
// 2. Register seller
const sellerReg = await request(app)
.post('/api/auth/register')
.send({
email: 'seller@test.com',
password: 'test123',
username: 'seller',
name: 'Seller Test',
})
.expect(201);
// 3. Login buyer
const buyerLogin = await request(app)
.post('/api/auth/login')
.send({
email: 'buyer@test.com',
password: 'test123',
})
.expect(200);
buyerToken = buyerLogin.body.token;
// 4. Login seller
const sellerLogin = await request(app)
.post('/api/auth/login')
.send({
email: 'seller@test.com',
password: 'test123',
})
.expect(200);
sellerToken = sellerLogin.body.token;
// 5. Promote seller to seller role (in test, skip this step or use admin)
// In real test, we'd update the role in database
// 6. Create skill
const skillRes = await request(app)
.post('/api/skills')
.set('Authorization', `Bearer ${sellerToken}`)
.send({
name: 'E2E Test Skill',
description: 'E2E Test Description',
price: 150,
category: 'development',
})
.expect(201);
skillId = skillRes.body.id;
// 7. Place order
const orderRes = await request(app)
.post('/api/orders')
.set('Authorization', `Bearer ${buyerToken}`)
.send({ skillId })
.expect(201);
orderId = orderRes.body.id;
expect(orderRes.body.status).toBe('pending');
// 8. Simulate payment (mock Monero)
// In real test, we would trigger the webhook
const webhookRes = await request(app)
.post('/webhook/order-update')
.send({
orderId,
status: 'confirmed',
txHash: 'mock_tx_hash',
amount: 150,
})
.expect(200);
// 9. Check order status updated to paid
const orderCheck = await request(app)
.get(`/api/orders/${orderId}`)
.set('Authorization', `Bearer ${buyerToken}`)
.expect(200);
expect(orderCheck.body.status).toBe('paid');
// 10. Update order to in_progress
await request(app)
.patch(`/api/orders/${orderId}/status`)
.set('Authorization', `Bearer ${sellerToken}`)
.send({ status: 'in_progress' })
.expect(200);
// 11. Complete order
await request(app)
.patch(`/api/orders/${orderId}/status`)
.set('Authorization', `Bearer ${sellerToken}`)
.send({ status: 'completed' })
.expect(200);
// 12. Verify order completed
const finalOrder = await request(app)
.get(`/api/orders/${orderId}`)
.set('Authorization', `Bearer ${buyerToken}`)
.expect(200);
expect(finalOrder.body.status).toBe('completed');
}, 60000);
test('Dispute flow: Order → Dispute → Resolve', async () => {
// Similar flow for dispute testing
// ... setup code ...
// 1. Create order (using helpers from previous test)
// 2. Open dispute
// 3. Resolve dispute
// 4. Verify resolution
});
});
5.2 Playwright E2E Tests (Frontend)
File: tests/e2e/playwright.config.js
javascript
const { defineConfig } = require('@playwright/test');
module.exports = defineConfig({
testDir: './tests/e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: 'http://localhost:4000',
trace: 'on-first-retry',
},
projects: [
{
name: 'chromium',
use: { browserName: 'chromium' },
},
{
name: 'firefox',
use: { browserName: 'firefox' },
},
{
name: 'webkit',
use: { browserName: 'webkit' },
},
],
});
File: tests/e2e/auth-flow.spec.js
javascript
const { test, expect } = require('@playwright/test');
test.describe('Authentication Flow', () => {
test('should register and login', async ({ page }) => {
await page.goto('/login');
// Go to registration page
await page.click('text=Sign Up');
await expect(page).toHaveURL('/register');
// Fill registration form
await page.fill('input[name="email"]', 'playwright@test.com');
await page.fill('input[name="password"]', 'test123');
await page.fill('input[name="username"]', 'playwright');
await page.fill('input[name="name"]', 'Playwright Test');
// Submit
await page.click('button[type="submit"]');
await expect(page).toHaveURL('/dashboard');
// Verify user is logged in
await expect(page.locator('text=Playwright Test')).toBeVisible();
});
});
🔄 Step 6: CI/CD Pipeline
6.1 GitHub Actions Workflow
File: .github/workflows/test.yml
yaml
name: Test Suite
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
NODE_VERSION: 18
jobs:
test-marketplace:
name: Test Marketplace
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: test
POSTGRES_DB: test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
cache-dependency-path: marketplace/package-lock.json
- name: Install dependencies
working-directory: ./marketplace
run: npm ci
- name: Run linter
working-directory: ./marketplace
run: npm run lint
- name: Run unit tests
working-directory: ./marketplace
run: npm test -- --testPathPattern=unit
- name: Run integration tests
working-directory: ./marketplace
run: npm test -- --testPathPattern=integration
env:
DATABASE_URL: postgresql://postgres:test@localhost:5432/test
JWT_SECRET: test_secret
NODE_ENV: test
- name: Upload coverage report
uses: codecov/codecov-action@v3
with:
directory: ./marketplace/coverage
flags: marketplace
name: marketplace
test-gateway:
name: Test Gateway
runs-on: ubuntu-latest
services:
mongodb:
image: mongo:6
ports:
- 27017:27017
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
cache-dependency-path: gateway/package-lock.json
- name: Install dependencies
working-directory: ./gateway
run: npm ci
- name: Run tests
working-directory: ./gateway
run: npm test
env:
MONGODB_URI: mongodb://localhost:27017/test
JWT_SECRET: test_secret
NODE_ENV: test
- name: Upload coverage report
uses: codecov/codecov-action@v3
with:
directory: ./gateway/coverage
flags: gateway
name: gateway
test-e2e:
name: End-to-End Tests
runs-on: ubuntu-latest
needs: [test-marketplace, test-gateway]
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install dependencies
working-directory: ./marketplace
run: npm ci
- name: Start services
run: |
docker-compose -f docker-compose.test.yml up -d
- name: Wait for services
run: |
sleep 10
curl -f http://localhost:4000/health
- name: Run E2E tests
working-directory: ./marketplace
run: npm test -- --testPathPattern=e2e
env:
DATABASE_URL: postgresql://postgres:test@localhost:5432/test
JWT_SECRET: test_secret
NODE_ENV: test
- name: Shutdown services
if: always()
run: docker-compose -f docker-compose.test.yml down
📊 Step 7: Test Coverage
7.1 Coverage Report
bash
Generate coverage report
npm test -- --coverage
View coverage report
open coverage/lcov-report/index.html
7.2 Coverage Thresholds
Metric Gateway Marketplace
Statements 75% 80%
Branches 70% 75%
Functions 75% 80%
Lines 75% 80%
📝 Step 8: Test Scripts
File: package.json (Add to scripts)
json
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"test:ci": "jest --coverage --ci",
"test:unit": "jest --testPathPattern=unit",
"test:integration": "jest --testPathPattern=integration",
"test:e2e": "jest --testPathPattern=e2e --runInBand",
"test:debug": "node --inspect-brk node_modules/.bin/jest --runInBand"
}
}
🐛 Troubleshooting
"Tests failing in CI"
bash
Run tests in CI environment
NODE_ENV=ci npm test
Debug with verbose output
npm test -- --verbose
Run specific failing test
npm test -- --testNamePattern="test name"
"Database connection timeout"
bash
Increase timeout in jest config
testTimeout: 60000
Check database is running
docker ps | grep postgres
"Mock not working"
bash
Clear Jest cache
jest --clearCache
Run tests with no cache
npm test -- --no-cache
📚 Resources
Jest Documentation: jestjs.io
Supertest: npmjs.com/package/supertest
Playwright: playwright.dev
GitHub Actions: docs.github.com/actions
🌐 Connect with Me
📖 Blog & Articles: DEV.to - Daniel Ioni
🐦 X (Twitter): @myzubster
💼 LinkedIn: Daniel Ioni
🐙 GitHub: DanielIoni-creator
🎵 TikTok: @h4x0r_23
⭐ Star the project on GitHub – it helps a lot!
Built with ❤️ for privacy, freedom, and decentralization.
Happy testing! 🚀
✅ Instructions for Publishing
Copy all the content above
Go to dev.to/new
Top comments (0)