chore: remove outdated documentation and reference files
Some checks failed
AITBC CI/CD Pipeline / lint-and-test (3.11) (push) Has been cancelled
AITBC CI/CD Pipeline / lint-and-test (3.12) (push) Has been cancelled
AITBC CI/CD Pipeline / lint-and-test (3.13) (push) Has been cancelled
AITBC CI/CD Pipeline / test-cli (push) Has been cancelled
AITBC CI/CD Pipeline / test-services (push) Has been cancelled
AITBC CI/CD Pipeline / test-production-services (push) Has been cancelled
AITBC CI/CD Pipeline / security-scan (push) Has been cancelled
AITBC CI/CD Pipeline / build (push) Has been cancelled
AITBC CI/CD Pipeline / deploy-staging (push) Has been cancelled
AITBC CI/CD Pipeline / deploy-production (push) Has been cancelled
AITBC CI/CD Pipeline / performance-test (push) Has been cancelled
AITBC CI/CD Pipeline / docs (push) Has been cancelled
AITBC CI/CD Pipeline / release (push) Has been cancelled
AITBC CI/CD Pipeline / notify (push) Has been cancelled
Security Scanning / Bandit Security Scan (apps/coordinator-api/src) (push) Has been cancelled
Security Scanning / Bandit Security Scan (cli/aitbc_cli) (push) Has been cancelled
Security Scanning / Bandit Security Scan (packages/py/aitbc-core/src) (push) Has been cancelled
Security Scanning / Bandit Security Scan (packages/py/aitbc-crypto/src) (push) Has been cancelled
Security Scanning / Bandit Security Scan (packages/py/aitbc-sdk/src) (push) Has been cancelled
Security Scanning / Bandit Security Scan (tests) (push) Has been cancelled
Security Scanning / CodeQL Security Analysis (javascript) (push) Has been cancelled
Security Scanning / CodeQL Security Analysis (python) (push) Has been cancelled
Security Scanning / Dependency Security Scan (push) Has been cancelled
Security Scanning / Container Security Scan (push) Has been cancelled
Security Scanning / OSSF Scorecard (push) Has been cancelled
Security Scanning / Security Summary Report (push) Has been cancelled
AITBC CLI Level 1 Commands Test / test-cli-level1 (3.11) (push) Has been cancelled
AITBC CLI Level 1 Commands Test / test-cli-level1 (3.12) (push) Has been cancelled
AITBC CLI Level 1 Commands Test / test-cli-level1 (3.13) (push) Has been cancelled
AITBC CLI Level 1 Commands Test / test-summary (push) Has been cancelled
Some checks failed
AITBC CI/CD Pipeline / lint-and-test (3.11) (push) Has been cancelled
AITBC CI/CD Pipeline / lint-and-test (3.12) (push) Has been cancelled
AITBC CI/CD Pipeline / lint-and-test (3.13) (push) Has been cancelled
AITBC CI/CD Pipeline / test-cli (push) Has been cancelled
AITBC CI/CD Pipeline / test-services (push) Has been cancelled
AITBC CI/CD Pipeline / test-production-services (push) Has been cancelled
AITBC CI/CD Pipeline / security-scan (push) Has been cancelled
AITBC CI/CD Pipeline / build (push) Has been cancelled
AITBC CI/CD Pipeline / deploy-staging (push) Has been cancelled
AITBC CI/CD Pipeline / deploy-production (push) Has been cancelled
AITBC CI/CD Pipeline / performance-test (push) Has been cancelled
AITBC CI/CD Pipeline / docs (push) Has been cancelled
AITBC CI/CD Pipeline / release (push) Has been cancelled
AITBC CI/CD Pipeline / notify (push) Has been cancelled
Security Scanning / Bandit Security Scan (apps/coordinator-api/src) (push) Has been cancelled
Security Scanning / Bandit Security Scan (cli/aitbc_cli) (push) Has been cancelled
Security Scanning / Bandit Security Scan (packages/py/aitbc-core/src) (push) Has been cancelled
Security Scanning / Bandit Security Scan (packages/py/aitbc-crypto/src) (push) Has been cancelled
Security Scanning / Bandit Security Scan (packages/py/aitbc-sdk/src) (push) Has been cancelled
Security Scanning / Bandit Security Scan (tests) (push) Has been cancelled
Security Scanning / CodeQL Security Analysis (javascript) (push) Has been cancelled
Security Scanning / CodeQL Security Analysis (python) (push) Has been cancelled
Security Scanning / Dependency Security Scan (push) Has been cancelled
Security Scanning / Container Security Scan (push) Has been cancelled
Security Scanning / OSSF Scorecard (push) Has been cancelled
Security Scanning / Security Summary Report (push) Has been cancelled
AITBC CLI Level 1 Commands Test / test-cli-level1 (3.11) (push) Has been cancelled
AITBC CLI Level 1 Commands Test / test-cli-level1 (3.12) (push) Has been cancelled
AITBC CLI Level 1 Commands Test / test-cli-level1 (3.13) (push) Has been cancelled
AITBC CLI Level 1 Commands Test / test-summary (push) Has been cancelled
- Remove debugging service documentation (DEBUgging_SERVICES.md) - Remove development logs policy and quick reference guides - Remove E2E test creation summary - Remove gift certificate example file - Remove GitHub pull summary documentation
This commit is contained in:
307
tests/unit/AgentBounty.test.js
Normal file
307
tests/unit/AgentBounty.test.js
Normal file
@@ -0,0 +1,307 @@
|
||||
const { expect } = require("chai");
|
||||
const { ethers } = require("hardhat");
|
||||
|
||||
describe("AgentBounty System", function () {
|
||||
let agentBounty, aitbcToken, performanceVerifier;
|
||||
let owner, bountyCreator, agent, arbitrator;
|
||||
|
||||
beforeEach(async function () {
|
||||
// Get signers
|
||||
[owner, bountyCreator, agent, arbitrator] = await ethers.getSigners();
|
||||
|
||||
// Deploy mock AITBC token
|
||||
const MockERC20 = await ethers.getContractFactory("MockERC20");
|
||||
aitbcToken = await MockERC20.deploy("AITBC Token", "AITBC", ethers.utils.parseEther("1000000"));
|
||||
await aitbcToken.deployed();
|
||||
|
||||
// Deploy mock performance verifier
|
||||
const MockPerformanceVerifier = await ethers.getContractFactory("MockPerformanceVerifier");
|
||||
performanceVerifier = await MockPerformanceVerifier.deploy();
|
||||
await performanceVerifier.deployed();
|
||||
|
||||
// Deploy AgentBounty contract
|
||||
const AgentBounty = await ethers.getContractFactory("AgentBounty");
|
||||
agentBounty = await AgentBounty.deploy(
|
||||
aitbcToken.address,
|
||||
performanceVerifier.address
|
||||
);
|
||||
await agentBounty.deployed();
|
||||
|
||||
// Transfer tokens to bounty creator and agent
|
||||
await aitbcToken.transfer(bountyCreator.address, ethers.utils.parseEther("10000"));
|
||||
await aitbcToken.transfer(agent.address, ethers.utils.parseEther("10000"));
|
||||
});
|
||||
|
||||
describe("Bounty Creation", function () {
|
||||
it("Should create a bounty successfully", async function () {
|
||||
const rewardAmount = ethers.utils.parseEther("100");
|
||||
const deadline = Math.floor(Date.now() / 1000) + 86400; // 24 hours from now
|
||||
|
||||
await aitbcToken.connect(bountyCreator).approve(agentBounty.address, rewardAmount);
|
||||
|
||||
const tx = await agentBounty.connect(bountyCreator).createBounty(
|
||||
"Test Bounty",
|
||||
"A test bounty for AI agents",
|
||||
rewardAmount,
|
||||
deadline,
|
||||
90, // min_accuracy
|
||||
3600, // max_response_time
|
||||
10, // max_submissions
|
||||
false, // requires_zk_proof
|
||||
["test", "ai"],
|
||||
"BRONZE",
|
||||
"easy"
|
||||
);
|
||||
|
||||
const receipt = await tx.wait();
|
||||
const event = receipt.events.find(e => e.event === "BountyCreated");
|
||||
|
||||
expect(event.args.bountyId).to.equal(1);
|
||||
expect(event.args.creator).to.equal(bountyCreator.address);
|
||||
expect(event.args.rewardAmount).to.equal(rewardAmount);
|
||||
});
|
||||
|
||||
it("Should fail if reward amount is zero", async function () {
|
||||
const deadline = Math.floor(Date.now() / 1000) + 86400;
|
||||
|
||||
await aitbcToken.connect(bountyCreator).approve(agentBounty.address, ethers.utils.parseEther("100"));
|
||||
|
||||
await expect(
|
||||
agentBounty.connect(bountyCreator).createBounty(
|
||||
"Test Bounty",
|
||||
"Description",
|
||||
0,
|
||||
deadline,
|
||||
90,
|
||||
3600,
|
||||
10,
|
||||
false,
|
||||
["test"],
|
||||
"BRONZE",
|
||||
"easy"
|
||||
)
|
||||
).to.be.revertedWith("Reward amount must be greater than 0");
|
||||
});
|
||||
|
||||
it("Should fail if deadline is in the past", async function () {
|
||||
const pastDeadline = Math.floor(Date.now() / 1000) - 3600; // 1 hour ago
|
||||
|
||||
await aitbcToken.connect(bountyCreator).approve(agentBounty.address, ethers.utils.parseEther("100"));
|
||||
|
||||
await expect(
|
||||
agentBounty.connect(bountyCreator).createBounty(
|
||||
"Test Bounty",
|
||||
"Description",
|
||||
ethers.utils.parseEther("100"),
|
||||
pastDeadline,
|
||||
90,
|
||||
3600,
|
||||
10,
|
||||
false,
|
||||
["test"],
|
||||
"BRONZE",
|
||||
"easy"
|
||||
)
|
||||
).to.be.revertedWith("Deadline must be in the future");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Bounty Submission", function () {
|
||||
let bountyId;
|
||||
|
||||
beforeEach(async function () {
|
||||
const rewardAmount = ethers.utils.parseEther("100");
|
||||
const deadline = Math.floor(Date.now() / 1000) + 86400;
|
||||
|
||||
await aitbcToken.connect(bountyCreator).approve(agentBounty.address, rewardAmount);
|
||||
|
||||
const tx = await agentBounty.connect(bountyCreator).createBounty(
|
||||
"Test Bounty",
|
||||
"Description",
|
||||
rewardAmount,
|
||||
deadline,
|
||||
90,
|
||||
3600,
|
||||
10,
|
||||
false,
|
||||
["test"],
|
||||
"BRONZE",
|
||||
"easy"
|
||||
);
|
||||
|
||||
const receipt = await tx.wait();
|
||||
bountyId = receipt.events.find(e => e.event === "BountyCreated").args.bountyId;
|
||||
});
|
||||
|
||||
it("Should submit a bounty successfully", async function () {
|
||||
const submissionData = "test submission data";
|
||||
|
||||
const tx = await agentBounty.connect(agent).submitBounty(
|
||||
bountyId,
|
||||
submissionData,
|
||||
[]
|
||||
);
|
||||
|
||||
const receipt = await tx.wait();
|
||||
const event = receipt.events.find(e => e.event === "BountySubmitted");
|
||||
|
||||
expect(event.args.bountyId).to.equal(bountyId);
|
||||
expect(event.args.submitter).to.equal(agent.address);
|
||||
expect(event.args.submissionData).to.equal(submissionData);
|
||||
});
|
||||
|
||||
it("Should fail if bounty doesn't exist", async function () {
|
||||
await expect(
|
||||
agentBounty.connect(agent).submitBounty(
|
||||
999,
|
||||
"test data",
|
||||
[]
|
||||
)
|
||||
).to.be.revertedWith("Bounty does not exist");
|
||||
});
|
||||
|
||||
it("Should fail if bounty is expired", async function () {
|
||||
// Fast forward time past deadline
|
||||
await ethers.provider.send("evm_increaseTime", [86400 * 2]); // 2 days
|
||||
await ethers.provider.send("evm_mine");
|
||||
|
||||
await expect(
|
||||
agentBounty.connect(agent).submitBounty(
|
||||
bountyId,
|
||||
"test data",
|
||||
[]
|
||||
)
|
||||
).to.be.revertedWith("Bounty has expired");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Bounty Verification", function () {
|
||||
let bountyId, submissionId;
|
||||
|
||||
beforeEach(async function () {
|
||||
const rewardAmount = ethers.utils.parseEther("100");
|
||||
const deadline = Math.floor(Date.now() / 1000) + 86400;
|
||||
|
||||
await aitbcToken.connect(bountyCreator).approve(agentBounty.address, rewardAmount);
|
||||
|
||||
const tx = await agentBounty.connect(bountyCreator).createBounty(
|
||||
"Test Bounty",
|
||||
"Description",
|
||||
rewardAmount,
|
||||
deadline,
|
||||
90,
|
||||
3600,
|
||||
10,
|
||||
false,
|
||||
["test"],
|
||||
"BRONZE",
|
||||
"easy"
|
||||
);
|
||||
|
||||
const receipt = await tx.wait();
|
||||
bountyId = receipt.events.find(e => e.event === "BountyCreated").args.bountyId;
|
||||
|
||||
const submitTx = await agentBounty.connect(agent).submitBounty(
|
||||
bountyId,
|
||||
"test submission data",
|
||||
[]
|
||||
);
|
||||
|
||||
const submitReceipt = await submitTx.wait();
|
||||
submissionId = submitReceipt.events.find(e => e.event === "BountySubmitted").args.submissionId;
|
||||
});
|
||||
|
||||
it("Should verify a bounty successfully", async function () {
|
||||
// Mock performance verifier to return true
|
||||
await performanceVerifier.setMockResult(true);
|
||||
|
||||
const tx = await agentBounty.verifyBounty(submissionId);
|
||||
const receipt = await tx.wait();
|
||||
const event = receipt.events.find(e => e.event === "BountyVerified");
|
||||
|
||||
expect(event.args.submissionId).to.equal(submissionId);
|
||||
expect(event.args.success).to.be.true;
|
||||
});
|
||||
|
||||
it("Should distribute rewards upon successful verification", async function () {
|
||||
// Mock performance verifier to return true
|
||||
await performanceVerifier.setMockResult(true);
|
||||
|
||||
const initialBalance = await aitbcToken.balanceOf(agent.address);
|
||||
|
||||
await agentBounty.verifyBounty(submissionId);
|
||||
|
||||
const finalBalance = await aitbcToken.balanceOf(agent.address);
|
||||
expect(finalBalance).to.be.gt(initialBalance);
|
||||
});
|
||||
|
||||
it("Should handle failed verification", async function () {
|
||||
// Mock performance verifier to return false
|
||||
await performanceVerifier.setMockResult(false);
|
||||
|
||||
const tx = await agentBounty.verifyBounty(submissionId);
|
||||
const receipt = await tx.wait();
|
||||
const event = receipt.events.find(e => e.event === "BountyVerified");
|
||||
|
||||
expect(event.args.success).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe("Fee Management", function () {
|
||||
it("Should allow owner to update fees", async function () {
|
||||
const newCreationFee = 75; // 0.75%
|
||||
|
||||
await agentBounty.updateCreationFee(newCreationFee);
|
||||
|
||||
expect(await agentBounty.creationFeePercentage()).to.equal(newCreationFee);
|
||||
});
|
||||
|
||||
it("Should prevent non-owners from updating fees", async function () {
|
||||
await expect(
|
||||
agentBounty.connect(bountyCreator).updateCreationFee(75)
|
||||
).to.be.revertedWith("Ownable: caller is not the owner");
|
||||
});
|
||||
|
||||
it("Should validate fee ranges", async function () {
|
||||
// Test fee too high (over 1000 basis points = 10%)
|
||||
await expect(
|
||||
agentBounty.updateCreationFee(1001)
|
||||
).to.be.revertedWith("Fee cannot exceed 1000 basis points");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Pausability", function () {
|
||||
it("Should allow owner to pause and unpause", async function () {
|
||||
await agentBounty.pause();
|
||||
expect(await agentBounty.paused()).to.be.true;
|
||||
|
||||
await agentBounty.unpause();
|
||||
expect(await agentBounty.paused()).to.be.false;
|
||||
});
|
||||
|
||||
it("Should prevent operations when paused", async function () {
|
||||
await agentBounty.pause();
|
||||
|
||||
const rewardAmount = ethers.utils.parseEther("100");
|
||||
const deadline = Math.floor(Date.now() / 1000) + 86400;
|
||||
|
||||
await aitbcToken.connect(bountyCreator).approve(agentBounty.address, rewardAmount);
|
||||
|
||||
await expect(
|
||||
agentBounty.connect(bountyCreator).createBounty(
|
||||
"Test Bounty",
|
||||
"Description",
|
||||
rewardAmount,
|
||||
deadline,
|
||||
90,
|
||||
3600,
|
||||
10,
|
||||
false,
|
||||
["test"],
|
||||
"BRONZE",
|
||||
"easy"
|
||||
)
|
||||
).to.be.revertedWith("Pausable: paused");
|
||||
});
|
||||
});
|
||||
});
|
||||
331
tests/unit/AgentStaking.test.js
Normal file
331
tests/unit/AgentStaking.test.js
Normal file
@@ -0,0 +1,331 @@
|
||||
const { expect } = require("chai");
|
||||
const { ethers } = require("hardhat");
|
||||
|
||||
describe("AgentStaking System", function () {
|
||||
let agentStaking, aitbcToken;
|
||||
let owner, agent, staker1, staker2;
|
||||
|
||||
beforeEach(async function () {
|
||||
// Get signers
|
||||
[owner, agent, staker1, staker2] = await ethers.getSigners();
|
||||
|
||||
// Deploy mock AITBC token
|
||||
const MockERC20 = await ethers.getContractFactory("MockERC20");
|
||||
aitbcToken = await MockERC20.deploy("AITBC Token", "AITBC", ethers.utils.parseEther("1000000"));
|
||||
await aitbcToken.deployed();
|
||||
|
||||
// Deploy AgentStaking contract
|
||||
const AgentStaking = await ethers.getContractFactory("AgentStaking");
|
||||
agentStaking = await AgentStaking.deploy(aitbcToken.address);
|
||||
await agentStaking.deployed();
|
||||
|
||||
// Transfer tokens to stakers
|
||||
await aitbcToken.transfer(staker1.address, ethers.utils.parseEther("10000"));
|
||||
await aitbcToken.transfer(staker2.address, ethers.utils.parseEther("10000"));
|
||||
});
|
||||
|
||||
describe("Agent Registration", function () {
|
||||
it("Should register an agent successfully", async function () {
|
||||
const tx = await agentStaking.connect(agent).registerAgent(
|
||||
"Test Agent",
|
||||
"https://example.com/metadata",
|
||||
["AI", "ML", "NLP"]
|
||||
);
|
||||
|
||||
const receipt = await tx.wait();
|
||||
const event = receipt.events.find(e => e.event === "AgentRegistered");
|
||||
|
||||
expect(event.args.agentAddress).to.equal(agent.address);
|
||||
expect(event.args.name).to.equal("Test Agent");
|
||||
expect(event.args.metadataURI).to.equal("https://example.com/metadata");
|
||||
});
|
||||
|
||||
it("Should fail if agent is already registered", async function () {
|
||||
await agentStaking.connect(agent).registerAgent(
|
||||
"Test Agent",
|
||||
"metadata",
|
||||
["AI"]
|
||||
);
|
||||
|
||||
await expect(
|
||||
agentStaking.connect(agent).registerAgent(
|
||||
"Another Agent",
|
||||
"metadata2",
|
||||
["ML"]
|
||||
)
|
||||
).to.be.revertedWith("Agent already registered");
|
||||
});
|
||||
|
||||
it("Should update agent metadata", async function () {
|
||||
await agentStaking.connect(agent).registerAgent(
|
||||
"Test Agent",
|
||||
"metadata",
|
||||
["AI"]
|
||||
);
|
||||
|
||||
await agentStaking.connect(agent).updateAgentMetadata(
|
||||
"Updated Agent",
|
||||
"https://updated.com/metadata",
|
||||
["AI", "ML", "CV"]
|
||||
);
|
||||
|
||||
const agentInfo = await agentStaking.getAgentInfo(agent.address);
|
||||
expect(agentInfo.name).to.equal("Updated Agent");
|
||||
expect(agentInfo.metadataURI).to.equal("https://updated.com/metadata");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Staking Operations", function () {
|
||||
beforeEach(async function () {
|
||||
await agentStaking.connect(agent).registerAgent(
|
||||
"Test Agent",
|
||||
"metadata",
|
||||
["AI"]
|
||||
);
|
||||
});
|
||||
|
||||
it("Should stake tokens successfully", async function () {
|
||||
const stakeAmount = ethers.utils.parseEther("1000");
|
||||
|
||||
await aitbcToken.connect(staker1).approve(agentStaking.address, stakeAmount);
|
||||
|
||||
const tx = await agentStaking.connect(staker1).stake(agent.address, stakeAmount);
|
||||
const receipt = await tx.wait();
|
||||
const event = receipt.events.find(e => e.event === "TokensStaked");
|
||||
|
||||
expect(event.args.staker).to.equal(staker1.address);
|
||||
expect(event.args.agent).to.equal(agent.address);
|
||||
expect(event.args.amount).to.equal(stakeAmount);
|
||||
});
|
||||
|
||||
it("Should track total staked per agent", async function () {
|
||||
const stakeAmount1 = ethers.utils.parseEther("500");
|
||||
const stakeAmount2 = ethers.utils.parseEther("300");
|
||||
|
||||
await aitbcToken.connect(staker1).approve(agentStaking.address, stakeAmount1);
|
||||
await aitbcToken.connect(staker2).approve(agentStaking.address, stakeAmount2);
|
||||
|
||||
await agentStaking.connect(staker1).stake(agent.address, stakeAmount1);
|
||||
await agentStaking.connect(staker2).stake(agent.address, stakeAmount2);
|
||||
|
||||
const agentInfo = await agentStaking.getAgentInfo(agent.address);
|
||||
expect(agentInfo.totalStaked).to.equal(stakeAmount1.add(stakeAmount2));
|
||||
});
|
||||
|
||||
it("Should unstake tokens successfully", async function () {
|
||||
const stakeAmount = ethers.utils.parseEther("1000");
|
||||
|
||||
await aitbcToken.connect(staker1).approve(agentStaking.address, stakeAmount);
|
||||
await agentStaking.connect(staker1).stake(agent.address, stakeAmount);
|
||||
|
||||
// Fast forward past unstaking delay
|
||||
await ethers.provider.send("evm_increaseTime", [86400 * 7]); // 7 days
|
||||
await ethers.provider.send("evm_mine");
|
||||
|
||||
const initialBalance = await aitbcToken.balanceOf(staker1.address);
|
||||
|
||||
await agentStaking.connect(staker1).unstake(agent.address, stakeAmount);
|
||||
|
||||
const finalBalance = await aitbcToken.balanceOf(staker1.address);
|
||||
expect(finalBalance).to.equal(initialBalance.add(stakeAmount));
|
||||
});
|
||||
|
||||
it("Should fail to unstake before delay period", async function () {
|
||||
const stakeAmount = ethers.utils.parseEther("1000");
|
||||
|
||||
await aitbcToken.connect(staker1).approve(agentStaking.address, stakeAmount);
|
||||
await agentStaking.connect(staker1).stake(agent.address, stakeAmount);
|
||||
|
||||
await expect(
|
||||
agentStaking.connect(staker1).unstake(agent.address, stakeAmount)
|
||||
).to.be.revertedWith("Unstaking delay not met");
|
||||
});
|
||||
|
||||
it("Should fail to unstake more than staked", async function () {
|
||||
const stakeAmount = ethers.utils.parseEther("1000");
|
||||
const unstakeAmount = ethers.utils.parseEther("1500");
|
||||
|
||||
await aitbcToken.connect(staker1).approve(agentStaking.address, stakeAmount);
|
||||
await agentStaking.connect(staker1).stake(agent.address, stakeAmount);
|
||||
|
||||
// Fast forward past unstaking delay
|
||||
await ethers.provider.send("evm_increaseTime", [86400 * 7]);
|
||||
await ethers.provider.send("evm_mine");
|
||||
|
||||
await expect(
|
||||
agentStaking.connect(staker1).unstake(agent.address, unstakeAmount)
|
||||
).to.be.revertedWith("Insufficient staked amount");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Reward Distribution", function () {
|
||||
beforeEach(async function () {
|
||||
await agentStaking.connect(agent).registerAgent(
|
||||
"Test Agent",
|
||||
"metadata",
|
||||
["AI"]
|
||||
);
|
||||
|
||||
const stakeAmount = ethers.utils.parseEther("1000");
|
||||
await aitbcToken.connect(staker1).approve(agentStaking.address, stakeAmount);
|
||||
await agentStaking.connect(staker1).stake(agent.address, stakeAmount);
|
||||
});
|
||||
|
||||
it("Should distribute rewards proportionally", async function () {
|
||||
const rewardAmount = ethers.utils.parseEther("100");
|
||||
|
||||
await aitbcToken.transfer(agentStaking.address, rewardAmount);
|
||||
|
||||
const initialBalance = await aitbcToken.balanceOf(staker1.address);
|
||||
|
||||
await agentStaking.distributeRewards(agent.address, rewardAmount);
|
||||
|
||||
const finalBalance = await aitbcToken.balanceOf(staker1.address);
|
||||
expect(finalBalance).to.equal(initialBalance.add(rewardAmount));
|
||||
});
|
||||
|
||||
it("Should handle multiple stakers proportionally", async function () {
|
||||
// Add second staker
|
||||
const stakeAmount2 = ethers.utils.parseEther("500");
|
||||
await aitbcToken.connect(staker2).approve(agentStaking.address, stakeAmount2);
|
||||
await agentStaking.connect(staker2).stake(agent.address, stakeAmount2);
|
||||
|
||||
const rewardAmount = ethers.utils.parseEther("150");
|
||||
await aitbcToken.transfer(agentStaking.address, rewardAmount);
|
||||
|
||||
const initialBalance1 = await aitbcToken.balanceOf(staker1.address);
|
||||
const initialBalance2 = await aitbcToken.balanceOf(staker2.address);
|
||||
|
||||
await agentStaking.distributeRewards(agent.address, rewardAmount);
|
||||
|
||||
const finalBalance1 = await aitbcToken.balanceOf(staker1.address);
|
||||
const finalBalance2 = await aitbcToken.balanceOf(staker2.address);
|
||||
|
||||
// Staker1 had 1000 tokens, Staker2 had 500 tokens (2:1 ratio)
|
||||
// So rewards should be distributed 100:50
|
||||
expect(finalBalance1).to.equal(initialBalance1.add(ethers.utils.parseEther("100")));
|
||||
expect(finalBalance2).to.equal(initialBalance2.add(ethers.utils.parseEther("50")));
|
||||
});
|
||||
});
|
||||
|
||||
describe("Agent Performance Tracking", function () {
|
||||
beforeEach(async function () {
|
||||
await agentStaking.connect(agent).registerAgent(
|
||||
"Test Agent",
|
||||
"metadata",
|
||||
["AI"]
|
||||
);
|
||||
});
|
||||
|
||||
it("Should record successful performance", async function () {
|
||||
await agentStaking.recordPerformance(agent.address, true, 95);
|
||||
|
||||
const agentInfo = await agentStaking.getAgentInfo(agent.address);
|
||||
expect(agentInfo.successfulTasks).to.equal(1);
|
||||
expect(agentInfo.totalTasks).to.equal(1);
|
||||
expect(agentInfo.successRate).to.equal(10000); // 100% in basis points
|
||||
});
|
||||
|
||||
it("Should record failed performance", async function () {
|
||||
await agentStaking.recordPerformance(agent.address, false, 60);
|
||||
|
||||
const agentInfo = await agentStaking.getAgentInfo(agent.address);
|
||||
expect(agentInfo.successfulTasks).to.equal(0);
|
||||
expect(agentInfo.totalTasks).to.equal(1);
|
||||
expect(agentInfo.successRate).to.equal(0);
|
||||
});
|
||||
|
||||
it("Should calculate success rate correctly", async function () {
|
||||
// Record multiple performances
|
||||
await agentStaking.recordPerformance(agent.address, true, 90);
|
||||
await agentStaking.recordPerformance(agent.address, true, 85);
|
||||
await agentStaking.recordPerformance(agent.address, false, 70);
|
||||
await agentStaking.recordPerformance(agent.address, true, 95);
|
||||
|
||||
const agentInfo = await agentStaking.getAgentInfo(agent.address);
|
||||
expect(agentInfo.successfulTasks).to.equal(3);
|
||||
expect(agentInfo.totalTasks).to.equal(4);
|
||||
expect(agentInfo.successRate).to.equal(7500); // 75% in basis points
|
||||
});
|
||||
|
||||
it("Should update average accuracy", async function () {
|
||||
await agentStaking.recordPerformance(agent.address, true, 90);
|
||||
await agentStaking.recordPerformance(agent.address, true, 80);
|
||||
await agentStaking.recordPerformance(agent.address, true, 85);
|
||||
|
||||
const agentInfo = await agentStaking.getAgentInfo(agent.address);
|
||||
expect(agentInfo.averageAccuracy).to.equal(8500); // 85% in basis points
|
||||
});
|
||||
});
|
||||
|
||||
describe("Slashing Mechanism", function () {
|
||||
beforeEach(async function () {
|
||||
await agentStaking.connect(agent).registerAgent(
|
||||
"Test Agent",
|
||||
"metadata",
|
||||
["AI"]
|
||||
);
|
||||
|
||||
const stakeAmount = ethers.utils.parseEther("1000");
|
||||
await aitbcToken.connect(staker1).approve(agentStaking.address, stakeAmount);
|
||||
await agentStaking.connect(staker1).stake(agent.address, stakeAmount);
|
||||
});
|
||||
|
||||
it("Should slash agent stake for misconduct", async function () {
|
||||
const slashAmount = ethers.utils.parseEther("100");
|
||||
|
||||
const initialContractBalance = await aitbcToken.balanceOf(agentStaking.address);
|
||||
|
||||
await agentStaking.slashStake(agent.address, slashAmount, "Test slash reason");
|
||||
|
||||
const finalContractBalance = await aitbcToken.balanceOf(agentStaking.address);
|
||||
expect(finalContractBalance).to.equal(initialContractBalance.sub(slashAmount));
|
||||
});
|
||||
|
||||
it("Should emit slash event", async function () {
|
||||
const slashAmount = ethers.utils.parseEther("100");
|
||||
|
||||
const tx = await agentStaking.slashStake(agent.address, slashAmount, "Test reason");
|
||||
const receipt = await tx.wait();
|
||||
const event = receipt.events.find(e => e.event === "StakeSlashed");
|
||||
|
||||
expect(event.args.agent).to.equal(agent.address);
|
||||
expect(event.args.amount).to.equal(slashAmount);
|
||||
expect(event.args.reason).to.equal("Test reason");
|
||||
});
|
||||
|
||||
it("Should fail to slash more than total staked", async function () {
|
||||
const totalStaked = await agentStaking.getAgentStakedAmount(agent.address);
|
||||
const slashAmount = totalStaked.add(ethers.utils.parseEther("1"));
|
||||
|
||||
await expect(
|
||||
agentStaking.slashStake(agent.address, slashAmount, "Excessive slash")
|
||||
).to.be.revertedWith("Slash amount exceeds total staked");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Access Control", function () {
|
||||
it("Should only allow owner to set performance recorder", async function () {
|
||||
await expect(
|
||||
agentStaking.connect(staker1).setPerformanceRecorder(staker2.address)
|
||||
).to.be.revertedWith("Ownable: caller is not the owner");
|
||||
});
|
||||
|
||||
it("Should allow owner to set performance recorder", async function () {
|
||||
await agentStaking.setPerformanceRecorder(staker2.address);
|
||||
expect(await agentStaking.performanceRecorder()).to.equal(staker2.address);
|
||||
});
|
||||
|
||||
it("Should only allow performance recorder to record performance", async function () {
|
||||
await agentStaking.connect(agent).registerAgent(
|
||||
"Test Agent",
|
||||
"metadata",
|
||||
["AI"]
|
||||
);
|
||||
|
||||
await expect(
|
||||
agentStaking.connect(staker1).recordPerformance(agent.address, true, 90)
|
||||
).to.be.revertedWith("Not authorized to record performance");
|
||||
});
|
||||
});
|
||||
});
|
||||
461
tests/unit/Integration.test.js
Normal file
461
tests/unit/Integration.test.js
Normal file
@@ -0,0 +1,461 @@
|
||||
const { expect } = require("chai");
|
||||
const { ethers } = require("hardhat");
|
||||
|
||||
describe("AITBC Smart Contract Integration", function () {
|
||||
let aitbcToken, zkVerifier, groth16Verifier;
|
||||
let aiPowerRental, paymentProcessor, performanceVerifier;
|
||||
let disputeResolution, escrowService, dynamicPricing;
|
||||
let owner, provider, consumer, arbitrator, oracle;
|
||||
|
||||
beforeEach(async function () {
|
||||
// Get signers
|
||||
[owner, provider, consumer, arbitrator, oracle] = await ethers.getSigners();
|
||||
|
||||
// Deploy mock contracts for testing
|
||||
const MockERC20 = await ethers.getContractFactory("MockERC20");
|
||||
aitbcToken = await MockERC20.deploy("AITBC Token", "AITBC", ethers.utils.parseEther("1000000"));
|
||||
await aitbcToken.deployed();
|
||||
|
||||
const MockZKVerifier = await ethers.getContractFactory("MockZKVerifier");
|
||||
zkVerifier = await MockZKVerifier.deploy();
|
||||
await zkVerifier.deployed();
|
||||
|
||||
const MockGroth16Verifier = await ethers.getContractFactory("MockGroth16Verifier");
|
||||
groth16Verifier = await MockGroth16Verifier.deploy();
|
||||
await groth16Verifier.deployed();
|
||||
|
||||
// Deploy main contracts
|
||||
const AIPowerRental = await ethers.getContractFactory("AIPowerRental");
|
||||
aiPowerRental = await AIPowerRental.deploy(
|
||||
aitbcToken.address,
|
||||
zkVerifier.address,
|
||||
groth16Verifier.address
|
||||
);
|
||||
await aiPowerRental.deployed();
|
||||
|
||||
const AITBCPaymentProcessor = await ethers.getContractFactory("AITBCPaymentProcessor");
|
||||
paymentProcessor = await AITBCPaymentProcessor.deploy(
|
||||
aitbcToken.address,
|
||||
aiPowerRental.address
|
||||
);
|
||||
await paymentProcessor.deployed();
|
||||
|
||||
const PerformanceVerifier = await ethers.getContractFactory("PerformanceVerifier");
|
||||
performanceVerifier = await PerformanceVerifier.deploy(
|
||||
zkVerifier.address,
|
||||
groth16Verifier.address,
|
||||
aiPowerRental.address
|
||||
);
|
||||
await performanceVerifier.deployed();
|
||||
|
||||
const DisputeResolution = await ethers.getContractFactory("DisputeResolution");
|
||||
disputeResolution = await DisputeResolution.deploy(
|
||||
aiPowerRental.address,
|
||||
paymentProcessor.address,
|
||||
performanceVerifier.address
|
||||
);
|
||||
await disputeResolution.deployed();
|
||||
|
||||
const EscrowService = await ethers.getContractFactory("EscrowService");
|
||||
escrowService = await EscrowService.deploy(
|
||||
aitbcToken.address,
|
||||
aiPowerRental.address,
|
||||
paymentProcessor.address
|
||||
);
|
||||
await escrowService.deployed();
|
||||
|
||||
const DynamicPricing = await ethers.getContractFactory("DynamicPricing");
|
||||
dynamicPricing = await DynamicPricing.deploy(
|
||||
aiPowerRental.address,
|
||||
performanceVerifier.address,
|
||||
aitbcToken.address
|
||||
);
|
||||
await dynamicPricing.deployed();
|
||||
|
||||
// Setup authorizations
|
||||
await aiPowerRental.authorizeProvider(provider.address);
|
||||
await aiPowerRental.authorizeConsumer(consumer.address);
|
||||
await paymentProcessor.authorizePayee(provider.address);
|
||||
await paymentProcessor.authorizePayer(consumer.address);
|
||||
await performanceVerifier.authorizeOracle(oracle.address);
|
||||
await disputeResolution.authorizeArbitrator(arbitrator.address);
|
||||
await escrowService.authorizeArbiter(arbitrator.address);
|
||||
await dynamicPricing.authorizePriceOracle(oracle.address);
|
||||
|
||||
// Transfer tokens to consumer for testing
|
||||
await aitbcToken.transfer(consumer.address, ethers.utils.parseEther("1000"));
|
||||
});
|
||||
|
||||
describe("Contract Deployment", function () {
|
||||
it("Should deploy all contracts successfully", async function () {
|
||||
expect(await aiPowerRental.deployed()).to.be.true;
|
||||
expect(await paymentProcessor.deployed()).to.be.true;
|
||||
expect(await performanceVerifier.deployed()).to.be.true;
|
||||
expect(await disputeResolution.deployed()).to.be.true;
|
||||
expect(await escrowService.deployed()).to.be.true;
|
||||
expect(await dynamicPricing.deployed()).to.be.true;
|
||||
});
|
||||
|
||||
it("Should have correct contract addresses", async function () {
|
||||
expect(await aiPowerRental.aitbcToken()).to.equal(aitbcToken.address);
|
||||
expect(await aiPowerRental.zkVerifier()).to.equal(zkVerifier.address);
|
||||
expect(await aiPowerRental.groth16Verifier()).to.equal(groth16Verifier.address);
|
||||
});
|
||||
});
|
||||
|
||||
describe("AI Power Rental Integration", function () {
|
||||
it("Should create and manage rental agreements", async function () {
|
||||
const duration = 3600; // 1 hour
|
||||
const price = ethers.utils.parseEther("0.01");
|
||||
const gpuModel = "RTX 4090";
|
||||
const computeUnits = 100;
|
||||
|
||||
const tx = await aiPowerRental.connect(consumer).createRental(
|
||||
provider.address,
|
||||
duration,
|
||||
price,
|
||||
gpuModel,
|
||||
computeUnits
|
||||
);
|
||||
|
||||
const receipt = await tx.wait();
|
||||
const event = receipt.events.find(e => e.event === "AgreementCreated");
|
||||
expect(event).to.not.be.undefined;
|
||||
expect(event.args.provider).to.equal(provider.address);
|
||||
expect(event.args.consumer).to.equal(consumer.address);
|
||||
expect(event.args.price).to.equal(price);
|
||||
});
|
||||
|
||||
it("Should start rental and lock payment", async function () {
|
||||
// Create rental first
|
||||
const duration = 3600;
|
||||
const price = ethers.utils.parseEther("0.01");
|
||||
const platformFee = price.mul(250).div(10000); // 2.5%
|
||||
const totalAmount = price.add(platformFee);
|
||||
|
||||
const createTx = await aiPowerRental.connect(consumer).createRental(
|
||||
provider.address,
|
||||
duration,
|
||||
price,
|
||||
"RTX 4090",
|
||||
100
|
||||
);
|
||||
const createReceipt = await createTx.wait();
|
||||
const agreementId = createReceipt.events.find(e => e.event === "AgreementCreated").args.agreementId;
|
||||
|
||||
// Approve tokens
|
||||
await aitbcToken.connect(consumer).approve(aiPowerRental.address, totalAmount);
|
||||
|
||||
// Start rental
|
||||
const startTx = await aiPowerRental.connect(consumer).startRental(agreementId);
|
||||
const startReceipt = await startTx.wait();
|
||||
const startEvent = startReceipt.events.find(e => e.event === "AgreementStarted");
|
||||
expect(startEvent).to.not.be.undefined;
|
||||
|
||||
// Check agreement status
|
||||
const agreement = await aiPowerRental.getRentalAgreement(agreementId);
|
||||
expect(agreement.status).to.equal(1); // Active
|
||||
});
|
||||
});
|
||||
|
||||
describe("Payment Processing Integration", function () {
|
||||
it("Should create and confirm payments", async function () {
|
||||
const amount = ethers.utils.parseEther("0.01");
|
||||
const agreementId = ethers.utils.formatBytes32String("test-agreement");
|
||||
|
||||
// Approve tokens
|
||||
await aitbcToken.connect(consumer).approve(paymentProcessor.address, amount);
|
||||
|
||||
// Create payment
|
||||
const tx = await paymentProcessor.connect(consumer).createPayment(
|
||||
provider.address,
|
||||
amount,
|
||||
agreementId,
|
||||
"Test payment",
|
||||
0 // Immediate release
|
||||
);
|
||||
|
||||
const receipt = await tx.wait();
|
||||
const event = receipt.events.find(e => e.event === "PaymentCreated");
|
||||
expect(event).to.not.be.undefined;
|
||||
expect(event.args.from).to.equal(consumer.address);
|
||||
expect(event.args.to).to.equal(provider.address);
|
||||
expect(event.args.amount).to.equal(amount);
|
||||
});
|
||||
|
||||
it("Should handle escrow payments", async function () {
|
||||
const amount = ethers.utils.parseEther("0.01");
|
||||
const releaseTime = Math.floor(Date.now() / 1000) + 3600; // 1 hour from now
|
||||
|
||||
// Approve tokens
|
||||
await aitbcToken.connect(consumer).approve(escrowService.address, amount);
|
||||
|
||||
// Create escrow
|
||||
const tx = await escrowService.connect(consumer).createEscrow(
|
||||
provider.address,
|
||||
arbitrator.address,
|
||||
amount,
|
||||
0, // Standard escrow
|
||||
0, // Manual release
|
||||
releaseTime,
|
||||
"Test escrow"
|
||||
);
|
||||
|
||||
const receipt = await tx.wait();
|
||||
const event = receipt.events.find(e => e.event === "EscrowCreated");
|
||||
expect(event).to.not.be.undefined;
|
||||
expect(event.args.depositor).to.equal(consumer.address);
|
||||
expect(event.args.beneficiary).to.equal(provider.address);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Performance Verification Integration", function () {
|
||||
it("Should submit and verify performance metrics", async function () {
|
||||
const agreementId = 1;
|
||||
const responseTime = 1000; // 1 second
|
||||
const accuracy = 95;
|
||||
const availability = 99;
|
||||
const computePower = 1000;
|
||||
const throughput = 100;
|
||||
const memoryUsage = 512;
|
||||
const energyEfficiency = 85;
|
||||
|
||||
// Create mock ZK proof
|
||||
const mockZKProof = "0x" + "0".repeat(64);
|
||||
const mockGroth16Proof = "0x" + "0".repeat(64);
|
||||
|
||||
// Submit performance
|
||||
const tx = await performanceVerifier.connect(provider).submitPerformance(
|
||||
agreementId,
|
||||
responseTime,
|
||||
accuracy,
|
||||
availability,
|
||||
computePower,
|
||||
throughput,
|
||||
memoryUsage,
|
||||
energyEfficiency,
|
||||
mockZKProof,
|
||||
mockGroth16Proof
|
||||
);
|
||||
|
||||
const receipt = await tx.wait();
|
||||
const event = receipt.events.find(e => e.event === "PerformanceSubmitted");
|
||||
expect(event).to.not.be.undefined;
|
||||
expect(event.args.responseTime).to.equal(responseTime);
|
||||
expect(event.args.accuracy).to.equal(accuracy);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Dispute Resolution Integration", function () {
|
||||
it("Should file and manage disputes", async function () {
|
||||
const agreementId = 1;
|
||||
const reason = "Service quality issues";
|
||||
|
||||
// File dispute
|
||||
const tx = await disputeResolution.connect(consumer).fileDispute(
|
||||
agreementId,
|
||||
provider.address,
|
||||
0, // Performance dispute
|
||||
reason,
|
||||
ethers.utils.formatBytes32String("evidence")
|
||||
);
|
||||
|
||||
const receipt = await tx.wait();
|
||||
const event = receipt.events.find(e => e.event === "DisputeFiled");
|
||||
expect(event).to.not.be.undefined;
|
||||
expect(event.args.initiator).to.equal(consumer.address);
|
||||
expect(event.args.respondent).to.equal(provider.address);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Dynamic Pricing Integration", function () {
|
||||
it("Should update market data and calculate prices", async function () {
|
||||
const totalSupply = 10000;
|
||||
const totalDemand = 8000;
|
||||
const activeProviders = 50;
|
||||
const activeConsumers = 100;
|
||||
const totalVolume = ethers.utils.parseEther("100");
|
||||
const transactionCount = 1000;
|
||||
const averageResponseTime = 2000;
|
||||
const averageAccuracy = 96;
|
||||
const marketSentiment = 75;
|
||||
|
||||
// Update market data
|
||||
const tx = await dynamicPricing.connect(oracle).updateMarketData(
|
||||
totalSupply,
|
||||
totalDemand,
|
||||
activeProviders,
|
||||
activeConsumers,
|
||||
totalVolume,
|
||||
transactionCount,
|
||||
averageResponseTime,
|
||||
averageAccuracy,
|
||||
marketSentiment
|
||||
);
|
||||
|
||||
const receipt = await tx.wait();
|
||||
const event = receipt.events.find(e => e.event === "MarketDataUpdated");
|
||||
expect(event).to.not.be.undefined;
|
||||
expect(event.args.totalSupply).to.equal(totalSupply);
|
||||
expect(event.args.totalDemand).to.equal(totalDemand);
|
||||
|
||||
// Get market price
|
||||
const marketPrice = await dynamicPricing.getMarketPrice(address(0), "");
|
||||
expect(marketPrice).to.be.gt(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Cross-Contract Integration", function () {
|
||||
it("Should handle complete rental lifecycle", async function () {
|
||||
// 1. Create rental agreement
|
||||
const duration = 3600;
|
||||
const price = ethers.utils.parseEther("0.01");
|
||||
const platformFee = price.mul(250).div(10000);
|
||||
const totalAmount = price.add(platformFee);
|
||||
|
||||
const createTx = await aiPowerRental.connect(consumer).createRental(
|
||||
provider.address,
|
||||
duration,
|
||||
price,
|
||||
"RTX 4090",
|
||||
100
|
||||
);
|
||||
const createReceipt = await createTx.wait();
|
||||
const agreementId = createReceipt.events.find(e => e.event === "AgreementCreated").args.agreementId;
|
||||
|
||||
// 2. Approve and start rental
|
||||
await aitbcToken.connect(consumer).approve(aiPowerRental.address, totalAmount);
|
||||
await aiPowerRental.connect(consumer).startRental(agreementId);
|
||||
|
||||
// 3. Submit performance metrics
|
||||
const mockZKProof = "0x" + "0".repeat(64);
|
||||
const mockGroth16Proof = "0x" + "0".repeat(64);
|
||||
|
||||
await performanceVerifier.connect(provider).submitPerformance(
|
||||
agreementId,
|
||||
1000, // responseTime
|
||||
95, // accuracy
|
||||
99, // availability
|
||||
1000, // computePower
|
||||
100, // throughput
|
||||
512, // memoryUsage
|
||||
85, // energyEfficiency
|
||||
mockZKProof,
|
||||
mockGroth16Proof
|
||||
);
|
||||
|
||||
// 4. Complete rental
|
||||
await aiPowerRental.connect(provider).completeRental(agreementId);
|
||||
|
||||
// 5. Verify final state
|
||||
const agreement = await aiPowerRental.getRentalAgreement(agreementId);
|
||||
expect(agreement.status).to.equal(2); // Completed
|
||||
});
|
||||
});
|
||||
|
||||
describe("Security Tests", function () {
|
||||
it("Should prevent unauthorized access", async function () {
|
||||
// Try to create rental without authorization
|
||||
await expect(
|
||||
aiPowerRental.connect(arbitrator).createRental(
|
||||
provider.address,
|
||||
3600,
|
||||
ethers.utils.parseEther("0.01"),
|
||||
"RTX 4090",
|
||||
100
|
||||
)
|
||||
).to.be.revertedWith("Not authorized consumer");
|
||||
});
|
||||
|
||||
it("Should handle emergency pause", async function () {
|
||||
// Pause contracts
|
||||
await aiPowerRental.pause();
|
||||
await paymentProcessor.pause();
|
||||
await performanceVerifier.pause();
|
||||
await disputeResolution.pause();
|
||||
await escrowService.pause();
|
||||
await dynamicPricing.pause();
|
||||
|
||||
// Try to perform operations while paused
|
||||
await expect(
|
||||
aiPowerRental.connect(consumer).createRental(
|
||||
provider.address,
|
||||
3600,
|
||||
ethers.utils.parseEther("0.01"),
|
||||
"RTX 4090",
|
||||
100
|
||||
)
|
||||
).to.be.revertedWith("Pausable: paused");
|
||||
|
||||
// Unpause
|
||||
await aiPowerRental.unpause();
|
||||
await paymentProcessor.unpause();
|
||||
await performanceVerifier.unpause();
|
||||
await disputeResolution.unpause();
|
||||
await escrowService.unpause();
|
||||
await dynamicPricing.unpause();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Gas Optimization Tests", function () {
|
||||
it("Should track gas usage for major operations", async function () {
|
||||
// Create rental
|
||||
const tx = await aiPowerRental.connect(consumer).createRental(
|
||||
provider.address,
|
||||
3600,
|
||||
ethers.utils.parseEther("0.01"),
|
||||
"RTX 4090",
|
||||
100
|
||||
);
|
||||
const receipt = await tx.wait();
|
||||
|
||||
console.log(`Gas used for createRental: ${receipt.gasUsed.toString()}`);
|
||||
|
||||
// Should be reasonable gas usage
|
||||
expect(receipt.gasUsed).to.be.lt(500000); // Less than 500k gas
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Mock contracts for testing
|
||||
const MockERC20Source = `
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
|
||||
contract MockERC20 is ERC20 {
|
||||
constructor(string memory name, string memory symbol, uint256 initialSupply) ERC20(name, symbol) {
|
||||
_mint(msg.sender, initialSupply);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const MockZKVerifierSource = `
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
contract MockZKVerifier {
|
||||
function verifyPerformanceProof(
|
||||
uint256,
|
||||
uint256,
|
||||
uint256,
|
||||
uint256,
|
||||
uint256,
|
||||
bytes memory
|
||||
) external pure returns (bool) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const MockGroth16VerifierSource = `
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
contract MockGroth16Verifier {
|
||||
function verifyProof(bytes memory) external pure returns (bool) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
`;
|
||||
10
tests/unit/MockERC20.sol
Normal file
10
tests/unit/MockERC20.sol
Normal file
@@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
|
||||
contract MockERC20 is ERC20 {
|
||||
constructor(string memory name, string memory symbol, uint256 initialSupply) ERC20(name, symbol) {
|
||||
_mint(msg.sender, initialSupply);
|
||||
}
|
||||
}
|
||||
8
tests/unit/MockGroth16Verifier.sol
Normal file
8
tests/unit/MockGroth16Verifier.sol
Normal file
@@ -0,0 +1,8 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
contract MockGroth16Verifier {
|
||||
function verifyProof(bytes memory) external pure returns (bool) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
15
tests/unit/MockZKVerifier.sol
Normal file
15
tests/unit/MockZKVerifier.sol
Normal file
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
contract MockZKVerifier {
|
||||
function verifyPerformanceProof(
|
||||
uint256,
|
||||
uint256,
|
||||
uint256,
|
||||
uint256,
|
||||
uint256,
|
||||
bytes memory
|
||||
) external pure returns (bool) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
666
tests/unit/locustfile.py
Executable file
666
tests/unit/locustfile.py
Executable file
@@ -0,0 +1,666 @@
|
||||
"""
|
||||
Load tests for AITBC Marketplace using Locust
|
||||
"""
|
||||
|
||||
from locust import HttpUser, task, between, events
|
||||
from locust.env import Environment
|
||||
from locust.stats import stats_printer, stats_history
|
||||
import json
|
||||
import random
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
import gevent
|
||||
from gevent.pool import Pool
|
||||
|
||||
|
||||
class MarketplaceUser(HttpUser):
|
||||
"""Simulated marketplace user behavior"""
|
||||
|
||||
wait_time = between(1, 3)
|
||||
weight = 10
|
||||
|
||||
def on_start(self):
|
||||
"""Called when a user starts"""
|
||||
# Initialize user session
|
||||
self.user_id = f"user_{random.randint(1000, 9999)}"
|
||||
self.tenant_id = f"tenant_{random.randint(100, 999)}"
|
||||
self.auth_headers = {
|
||||
"X-Tenant-ID": self.tenant_id,
|
||||
"Authorization": f"Bearer token_{self.user_id}",
|
||||
}
|
||||
|
||||
# Create user wallet
|
||||
self.create_wallet()
|
||||
|
||||
# Track user state
|
||||
self.offers_created = []
|
||||
self.bids_placed = []
|
||||
self.balance = 10000.0 # Starting balance in USDC
|
||||
|
||||
def create_wallet(self):
|
||||
"""Create a wallet for the user"""
|
||||
wallet_data = {
|
||||
"name": f"Wallet_{self.user_id}",
|
||||
"password": f"pass_{self.user_id}",
|
||||
}
|
||||
|
||||
response = self.client.post(
|
||||
"/v1/wallets",
|
||||
json=wallet_data,
|
||||
headers=self.auth_headers
|
||||
)
|
||||
|
||||
if response.status_code == 201:
|
||||
self.wallet_id = response.json()["id"]
|
||||
else:
|
||||
self.wallet_id = f"wallet_{self.user_id}"
|
||||
|
||||
@task(3)
|
||||
def browse_offers(self):
|
||||
"""Browse marketplace offers"""
|
||||
params = {
|
||||
"limit": 20,
|
||||
"offset": random.randint(0, 100),
|
||||
"service_type": random.choice([
|
||||
"ai_inference",
|
||||
"image_generation",
|
||||
"video_processing",
|
||||
"data_analytics",
|
||||
]),
|
||||
}
|
||||
|
||||
with self.client.get(
|
||||
"/v1/marketplace/offers",
|
||||
params=params,
|
||||
headers=self.auth_headers,
|
||||
catch_response=True,
|
||||
) as response:
|
||||
if response.status_code == 200:
|
||||
data = response.json()
|
||||
offers = data.get("items", [])
|
||||
# Simulate user viewing offers
|
||||
if offers:
|
||||
self.view_offer_details(random.choice(offers)["id"])
|
||||
response.success()
|
||||
else:
|
||||
response.failure(f"Failed to browse offers: {response.status_code}")
|
||||
|
||||
def view_offer_details(self, offer_id):
|
||||
"""View detailed offer information"""
|
||||
with self.client.get(
|
||||
f"/v1/marketplace/offers/{offer_id}",
|
||||
headers=self.auth_headers,
|
||||
catch_response=True,
|
||||
) as response:
|
||||
if response.status_code == 200:
|
||||
response.success()
|
||||
else:
|
||||
response.failure(f"Failed to view offer: {response.status_code}")
|
||||
|
||||
@task(2)
|
||||
def create_offer(self):
|
||||
"""Create a new marketplace offer"""
|
||||
if self.balance < 100:
|
||||
return # Insufficient balance
|
||||
|
||||
offer_data = {
|
||||
"service_type": random.choice([
|
||||
"ai_inference",
|
||||
"image_generation",
|
||||
"video_processing",
|
||||
"data_analytics",
|
||||
"scientific_computing",
|
||||
]),
|
||||
"pricing": {
|
||||
"per_hour": round(random.uniform(0.1, 5.0), 2),
|
||||
"per_unit": round(random.uniform(0.001, 0.1), 4),
|
||||
},
|
||||
"capacity": random.randint(10, 1000),
|
||||
"requirements": {
|
||||
"gpu_memory": random.choice(["8GB", "16GB", "32GB", "64GB"]),
|
||||
"cpu_cores": random.randint(4, 32),
|
||||
"ram": random.choice(["16GB", "32GB", "64GB", "128GB"]),
|
||||
},
|
||||
"availability": {
|
||||
"start_time": (datetime.utcnow() + timedelta(hours=1)).isoformat(),
|
||||
"end_time": (datetime.utcnow() + timedelta(days=30)).isoformat(),
|
||||
},
|
||||
}
|
||||
|
||||
with self.client.post(
|
||||
"/v1/marketplace/offers",
|
||||
json=offer_data,
|
||||
headers=self.auth_headers,
|
||||
catch_response=True,
|
||||
) as response:
|
||||
if response.status_code == 201:
|
||||
offer = response.json()
|
||||
self.offers_created.append(offer["id"])
|
||||
response.success()
|
||||
else:
|
||||
response.failure(f"Failed to create offer: {response.status_code}")
|
||||
|
||||
@task(3)
|
||||
def place_bid(self):
|
||||
"""Place a bid on an existing offer"""
|
||||
# First get available offers
|
||||
with self.client.get(
|
||||
"/v1/marketplace/offers",
|
||||
params={"limit": 10, "status": "active"},
|
||||
headers=self.auth_headers,
|
||||
) as response:
|
||||
if response.status_code != 200:
|
||||
return
|
||||
|
||||
offers = response.json().get("items", [])
|
||||
if not offers:
|
||||
return
|
||||
|
||||
# Select random offer
|
||||
offer = random.choice(offers)
|
||||
|
||||
# Calculate bid amount
|
||||
max_price = offer["pricing"]["per_hour"]
|
||||
bid_price = round(max_price * random.uniform(0.8, 0.95), 2)
|
||||
|
||||
if self.balance < bid_price:
|
||||
return
|
||||
|
||||
bid_data = {
|
||||
"offer_id": offer["id"],
|
||||
"quantity": random.randint(1, min(10, offer["capacity"])),
|
||||
"max_price": bid_price,
|
||||
"duration_hours": random.randint(1, 24),
|
||||
}
|
||||
|
||||
with self.client.post(
|
||||
"/v1/marketplace/bids",
|
||||
json=bid_data,
|
||||
headers=self.auth_headers,
|
||||
catch_response=True,
|
||||
) as response:
|
||||
if response.status_code == 201:
|
||||
bid = response.json()
|
||||
self.bids_placed.append(bid["id"])
|
||||
self.balance -= bid_price * bid_data["quantity"]
|
||||
response.success()
|
||||
else:
|
||||
response.failure(f"Failed to place bid: {response.status_code}")
|
||||
|
||||
@task(2)
|
||||
def check_bids(self):
|
||||
"""Check status of placed bids"""
|
||||
if not self.bids_placed:
|
||||
return
|
||||
|
||||
bid_id = random.choice(self.bids_placed)
|
||||
|
||||
with self.client.get(
|
||||
f"/v1/marketplace/bids/{bid_id}",
|
||||
headers=self.auth_headers,
|
||||
catch_response=True,
|
||||
) as response:
|
||||
if response.status_code == 200:
|
||||
bid = response.json()
|
||||
|
||||
# If bid is accepted, create transaction
|
||||
if bid["status"] == "accepted":
|
||||
self.create_transaction(bid)
|
||||
|
||||
response.success()
|
||||
else:
|
||||
response.failure(f"Failed to check bid: {response.status_code}")
|
||||
|
||||
def create_transaction(self, bid):
|
||||
"""Create transaction for accepted bid"""
|
||||
tx_data = {
|
||||
"bid_id": bid["id"],
|
||||
"payment_method": "wallet",
|
||||
"confirmations": True,
|
||||
}
|
||||
|
||||
with self.client.post(
|
||||
"/v1/marketplace/transactions",
|
||||
json=tx_data,
|
||||
headers=self.auth_headers,
|
||||
catch_response=True,
|
||||
) as response:
|
||||
if response.status_code == 201:
|
||||
response.success()
|
||||
else:
|
||||
response.failure(f"Failed to create transaction: {response.status_code}")
|
||||
|
||||
@task(1)
|
||||
def get_marketplace_stats(self):
|
||||
"""Get marketplace statistics"""
|
||||
with self.client.get(
|
||||
"/v1/marketplace/stats",
|
||||
headers=self.auth_headers,
|
||||
catch_response=True,
|
||||
) as response:
|
||||
if response.status_code == 200:
|
||||
response.success()
|
||||
else:
|
||||
response.failure(f"Failed to get stats: {response.status_code}")
|
||||
|
||||
@task(1)
|
||||
def search_services(self):
|
||||
"""Search for specific services"""
|
||||
query = random.choice([
|
||||
"AI inference",
|
||||
"image generation",
|
||||
"video rendering",
|
||||
"data processing",
|
||||
"machine learning",
|
||||
])
|
||||
|
||||
params = {
|
||||
"q": query,
|
||||
"limit": 20,
|
||||
"min_price": random.uniform(0.1, 1.0),
|
||||
"max_price": random.uniform(5.0, 10.0),
|
||||
}
|
||||
|
||||
with self.client.get(
|
||||
"/v1/marketplace/search",
|
||||
params=params,
|
||||
headers=self.auth_headers,
|
||||
catch_response=True,
|
||||
) as response:
|
||||
if response.status_code == 200:
|
||||
response.success()
|
||||
else:
|
||||
response.failure(f"Failed to search: {response.status_code}")
|
||||
|
||||
|
||||
class MarketplaceProvider(HttpUser):
|
||||
"""Simulated service provider behavior"""
|
||||
|
||||
wait_time = between(5, 15)
|
||||
weight = 3
|
||||
|
||||
def on_start(self):
|
||||
"""Initialize provider"""
|
||||
self.provider_id = f"provider_{random.randint(100, 999)}"
|
||||
self.tenant_id = f"tenant_{random.randint(100, 999)}"
|
||||
self.auth_headers = {
|
||||
"X-Tenant-ID": self.tenant_id,
|
||||
"Authorization": f"Bearer provider_token_{self.provider_id}",
|
||||
}
|
||||
|
||||
# Register as provider
|
||||
self.register_provider()
|
||||
|
||||
# Provider services
|
||||
self.services = []
|
||||
|
||||
def register_provider(self):
|
||||
"""Register as a service provider"""
|
||||
provider_data = {
|
||||
"name": f"Provider_{self.provider_id}",
|
||||
"description": "AI/ML computing services provider",
|
||||
"endpoint": f"https://provider-{self.provider_id}.aitbc.io",
|
||||
"capabilities": [
|
||||
"ai_inference",
|
||||
"image_generation",
|
||||
"video_processing",
|
||||
],
|
||||
"infrastructure": {
|
||||
"gpu_count": random.randint(10, 100),
|
||||
"cpu_cores": random.randint(100, 1000),
|
||||
"memory_gb": random.randint(500, 5000),
|
||||
},
|
||||
}
|
||||
|
||||
self.client.post(
|
||||
"/v1/marketplace/providers/register",
|
||||
json=provider_data,
|
||||
headers=self.auth_headers
|
||||
)
|
||||
|
||||
@task(4)
|
||||
def update_service_status(self):
|
||||
"""Update status of provider services"""
|
||||
if not self.services:
|
||||
return
|
||||
|
||||
service = random.choice(self.services)
|
||||
|
||||
status_data = {
|
||||
"service_id": service["id"],
|
||||
"status": random.choice(["available", "busy", "maintenance"]),
|
||||
"utilization": random.uniform(0.1, 0.9),
|
||||
"queue_length": random.randint(0, 20),
|
||||
}
|
||||
|
||||
with self.client.patch(
|
||||
f"/v1/marketplace/services/{service['id']}/status",
|
||||
json=status_data,
|
||||
headers=self.auth_headers,
|
||||
catch_response=True,
|
||||
) as response:
|
||||
if response.status_code == 200:
|
||||
response.success()
|
||||
else:
|
||||
response.failure(f"Failed to update status: {response.status_code}")
|
||||
|
||||
@task(3)
|
||||
def create_bulk_offers(self):
|
||||
"""Create multiple offers at once"""
|
||||
offers = []
|
||||
|
||||
for _ in range(random.randint(5, 15)):
|
||||
offer_data = {
|
||||
"service_type": random.choice([
|
||||
"ai_inference",
|
||||
"image_generation",
|
||||
"video_processing",
|
||||
]),
|
||||
"pricing": {
|
||||
"per_hour": round(random.uniform(0.5, 3.0), 2),
|
||||
},
|
||||
"capacity": random.randint(50, 500),
|
||||
"requirements": {
|
||||
"gpu_memory": "16GB",
|
||||
"cpu_cores": 16,
|
||||
},
|
||||
}
|
||||
offers.append(offer_data)
|
||||
|
||||
bulk_data = {"offers": offers}
|
||||
|
||||
with self.client.post(
|
||||
"/v1/marketplace/offers/bulk",
|
||||
json=bulk_data,
|
||||
headers=self.auth_headers,
|
||||
catch_response=True,
|
||||
) as response:
|
||||
if response.status_code == 201:
|
||||
created = response.json().get("created", [])
|
||||
self.services.extend(created)
|
||||
response.success()
|
||||
else:
|
||||
response.failure(f"Failed to create bulk offers: {response.status_code}")
|
||||
|
||||
@task(2)
|
||||
def respond_to_bids(self):
|
||||
"""Respond to incoming bids"""
|
||||
with self.client.get(
|
||||
"/v1/marketplace/bids",
|
||||
params={"provider_id": self.provider_id, "status": "pending"},
|
||||
headers=self.auth_headers,
|
||||
) as response:
|
||||
if response.status_code != 200:
|
||||
return
|
||||
|
||||
bids = response.json().get("items", [])
|
||||
if not bids:
|
||||
return
|
||||
|
||||
# Respond to random bid
|
||||
bid = random.choice(bids)
|
||||
action = random.choice(["accept", "reject", "counter"])
|
||||
|
||||
response_data = {
|
||||
"bid_id": bid["id"],
|
||||
"action": action,
|
||||
}
|
||||
|
||||
if action == "counter":
|
||||
response_data["counter_price"] = round(
|
||||
bid["max_price"] * random.uniform(1.05, 1.15), 2
|
||||
)
|
||||
|
||||
with self.client.post(
|
||||
"/v1/marketplace/bids/respond",
|
||||
json=response_data,
|
||||
headers=self.auth_headers,
|
||||
catch_response=True,
|
||||
) as response:
|
||||
if response.status_code == 200:
|
||||
response.success()
|
||||
else:
|
||||
response.failure(f"Failed to respond to bid: {response.status_code}")
|
||||
|
||||
@task(1)
|
||||
def get_provider_analytics(self):
|
||||
"""Get provider analytics"""
|
||||
with self.client.get(
|
||||
f"/v1/marketplace/providers/{self.provider_id}/analytics",
|
||||
headers=self.auth_headers,
|
||||
catch_response=True,
|
||||
) as response:
|
||||
if response.status_code == 200:
|
||||
response.success()
|
||||
else:
|
||||
response.failure(f"Failed to get analytics: {response.status_code}")
|
||||
|
||||
|
||||
class MarketplaceAdmin(HttpUser):
|
||||
"""Simulated admin user behavior"""
|
||||
|
||||
wait_time = between(10, 30)
|
||||
weight = 1
|
||||
|
||||
def on_start(self):
|
||||
"""Initialize admin"""
|
||||
self.auth_headers = {
|
||||
"Authorization": "Bearer admin_token_123",
|
||||
"X-Admin-Access": "true",
|
||||
}
|
||||
|
||||
@task(3)
|
||||
def monitor_marketplace_health(self):
|
||||
"""Monitor marketplace health metrics"""
|
||||
endpoints = [
|
||||
"/v1/marketplace/health",
|
||||
"/v1/marketplace/metrics",
|
||||
"/v1/marketplace/stats",
|
||||
]
|
||||
|
||||
endpoint = random.choice(endpoints)
|
||||
|
||||
with self.client.get(
|
||||
endpoint,
|
||||
headers=self.auth_headers,
|
||||
catch_response=True,
|
||||
) as response:
|
||||
if response.status_code == 200:
|
||||
response.success()
|
||||
else:
|
||||
response.failure(f"Health check failed: {response.status_code}")
|
||||
|
||||
@task(2)
|
||||
def review_suspicious_activity(self):
|
||||
"""Review suspicious marketplace activity"""
|
||||
with self.client.get(
|
||||
"/v1/admin/marketplace/activity",
|
||||
params={
|
||||
"suspicious_only": True,
|
||||
"limit": 50,
|
||||
},
|
||||
headers=self.auth_headers,
|
||||
catch_response=True,
|
||||
) as response:
|
||||
if response.status_code == 200:
|
||||
activities = response.json().get("items", [])
|
||||
|
||||
# Take action on suspicious activities
|
||||
for activity in activities[:5]: # Limit to 5 actions
|
||||
self.take_action(activity["id"])
|
||||
|
||||
response.success()
|
||||
else:
|
||||
response.failure(f"Failed to review activity: {response.status_code}")
|
||||
|
||||
def take_action(self, activity_id):
|
||||
"""Take action on suspicious activity"""
|
||||
action = random.choice(["warn", "suspend", "investigate"])
|
||||
|
||||
with self.client.post(
|
||||
f"/v1/admin/marketplace/activity/{activity_id}/action",
|
||||
json={"action": action},
|
||||
headers=self.auth_headers,
|
||||
catch_response=True,
|
||||
) as response:
|
||||
if response.status_code in [200, 404]:
|
||||
response.success()
|
||||
else:
|
||||
response.failure(f"Failed to take action: {response.status_code}")
|
||||
|
||||
@task(1)
|
||||
def generate_reports(self):
|
||||
"""Generate marketplace reports"""
|
||||
report_types = [
|
||||
"daily_summary",
|
||||
"weekly_analytics",
|
||||
"provider_performance",
|
||||
"user_activity",
|
||||
]
|
||||
|
||||
report_type = random.choice(report_types)
|
||||
|
||||
with self.client.post(
|
||||
"/v1/admin/marketplace/reports",
|
||||
json={
|
||||
"type": report_type,
|
||||
"format": "json",
|
||||
"email": f"admin@aitbc.io",
|
||||
},
|
||||
headers=self.auth_headers,
|
||||
catch_response=True,
|
||||
) as response:
|
||||
if response.status_code == 202:
|
||||
response.success()
|
||||
else:
|
||||
response.failure(f"Failed to generate report: {response.status_code}")
|
||||
|
||||
|
||||
# Custom event handlers for monitoring
|
||||
@events.request.add_listener
|
||||
def on_request(request_type, name, response_time, response_length, exception, **kwargs):
|
||||
"""Custom request handler for additional metrics"""
|
||||
if exception:
|
||||
print(f"Request failed: {name} - {exception}")
|
||||
elif response_time > 5000: # Log slow requests
|
||||
print(f"Slow request: {name} - {response_time}ms")
|
||||
|
||||
|
||||
@events.test_start.add_listener
|
||||
def on_test_start(environment, **kwargs):
|
||||
"""Called when test starts"""
|
||||
print("Starting marketplace load test")
|
||||
print(f"Target: {environment.host}")
|
||||
|
||||
|
||||
@events.test_stop.add_listener
|
||||
def on_test_stop(environment, **kwargs):
|
||||
"""Called when test stops"""
|
||||
print("\nLoad test completed")
|
||||
|
||||
# Print summary statistics
|
||||
stats = environment.stats
|
||||
|
||||
print(f"\nTotal requests: {stats.total.num_requests}")
|
||||
print(f"Failures: {stats.total.num_failures}")
|
||||
print(f"Average response time: {stats.total.avg_response_time:.2f}ms")
|
||||
print(f"95th percentile: {stats.total.get_response_time_percentile(0.95):.2f}ms")
|
||||
print(f"Requests per second: {stats.total.current_rps:.2f}")
|
||||
|
||||
|
||||
# Custom load shapes
|
||||
class GradualLoadShape:
|
||||
"""Gradually increase load over time"""
|
||||
|
||||
def __init__(self, max_users=100, spawn_rate=10):
|
||||
self.max_users = max_users
|
||||
self.spawn_rate = spawn_rate
|
||||
|
||||
def tick(self):
|
||||
run_time = time.time() - self.start_time
|
||||
|
||||
if run_time < 60: # First minute: ramp up
|
||||
return int(self.spawn_rate * run_time / 60)
|
||||
elif run_time < 300: # Next 4 minutes: maintain
|
||||
return self.max_users
|
||||
else: # Last minute: ramp down
|
||||
remaining = 360 - run_time
|
||||
return int(self.max_users * remaining / 60)
|
||||
|
||||
|
||||
class BurstLoadShape:
|
||||
"""Burst traffic pattern"""
|
||||
|
||||
def __init__(self, burst_size=50, normal_size=10):
|
||||
self.burst_size = burst_size
|
||||
self.normal_size = normal_size
|
||||
|
||||
def tick(self):
|
||||
run_time = time.time() - self.start_time
|
||||
|
||||
# Burst every 30 seconds for 10 seconds
|
||||
if int(run_time) % 30 < 10:
|
||||
return self.burst_size
|
||||
else:
|
||||
return self.normal_size
|
||||
|
||||
|
||||
# Performance monitoring
|
||||
class PerformanceMonitor:
|
||||
"""Monitor performance during load test"""
|
||||
|
||||
def __init__(self):
|
||||
self.metrics = {
|
||||
"response_times": [],
|
||||
"error_rates": [],
|
||||
"throughput": [],
|
||||
}
|
||||
|
||||
def record_request(self, response_time, success):
|
||||
"""Record request metrics"""
|
||||
self.metrics["response_times"].append(response_time)
|
||||
self.metrics["error_rates"].append(0 if success else 1)
|
||||
|
||||
def get_summary(self):
|
||||
"""Get performance summary"""
|
||||
if not self.metrics["response_times"]:
|
||||
return {}
|
||||
|
||||
return {
|
||||
"avg_response_time": sum(self.metrics["response_times"]) / len(self.metrics["response_times"]),
|
||||
"max_response_time": max(self.metrics["response_times"]),
|
||||
"error_rate": sum(self.metrics["error_rates"]) / len(self.metrics["error_rates"]),
|
||||
"total_requests": len(self.metrics["response_times"]),
|
||||
}
|
||||
|
||||
|
||||
# Test configuration
|
||||
if __name__ == "__main__":
|
||||
# Setup environment
|
||||
env = Environment(user_classes=[MarketplaceUser, MarketplaceProvider, MarketplaceAdmin])
|
||||
|
||||
# Create performance monitor
|
||||
monitor = PerformanceMonitor()
|
||||
|
||||
# Setup host
|
||||
env.host = "http://localhost:8001"
|
||||
|
||||
# Setup load shape
|
||||
env.create_local_runner()
|
||||
|
||||
# Start web UI for monitoring
|
||||
env.create_web_ui("127.0.0.1", 8089)
|
||||
|
||||
# Start the load test
|
||||
print("Starting marketplace load test...")
|
||||
print("Web UI available at: http://127.0.0.1:8089")
|
||||
|
||||
# Run for 6 minutes
|
||||
env.runner.start(100, spawn_rate=10)
|
||||
gevent.spawn_later(360, env.runner.stop)
|
||||
|
||||
# Print stats
|
||||
gevent.spawn(stats_printer(env.stats))
|
||||
|
||||
# Wait for test to complete
|
||||
env.runner.greenlet.join()
|
||||
Reference in New Issue
Block a user