feat: complete phase 3 developer ecosystem and dao governance

This commit is contained in:
oib
2026-02-28 23:24:26 +01:00
parent 5bc18d684c
commit d8a432ce33
9 changed files with 1341 additions and 0 deletions

View File

@@ -0,0 +1,124 @@
import pytest
from datetime import datetime, timedelta
from unittest.mock import AsyncMock
from sqlmodel import Session, create_engine, SQLModel
from sqlmodel.pool import StaticPool
from fastapi import HTTPException
from app.services.dao_governance_service import DAOGovernanceService
from app.domain.dao_governance import ProposalState, ProposalType
from app.schemas.dao_governance import MemberCreate, ProposalCreate, VoteCreate
@pytest.fixture
def test_db():
engine = create_engine(
"sqlite:///:memory:",
connect_args={"check_same_thread": False},
poolclass=StaticPool,
)
SQLModel.metadata.create_all(engine)
session = Session(engine)
yield session
session.close()
@pytest.fixture
def mock_contract_service():
return AsyncMock()
@pytest.fixture
def dao_service(test_db, mock_contract_service):
return DAOGovernanceService(
session=test_db,
contract_service=mock_contract_service
)
@pytest.mark.asyncio
async def test_register_member(dao_service):
req = MemberCreate(wallet_address="0xDAO1", staked_amount=100.0)
member = await dao_service.register_member(req)
assert member.wallet_address == "0xDAO1"
assert member.staked_amount == 100.0
assert member.voting_power == 100.0
@pytest.mark.asyncio
async def test_create_proposal(dao_service):
# Register proposer
await dao_service.register_member(MemberCreate(wallet_address="0xDAO1", staked_amount=100.0))
req = ProposalCreate(
proposer_address="0xDAO1",
title="Fund new AI model",
description="Allocate 1000 AITBC to train a new model",
proposal_type=ProposalType.GRANT,
execution_payload={"amount": "1000", "recipient_address": "0xDev1"},
voting_period_days=7
)
proposal = await dao_service.create_proposal(req)
assert proposal.title == "Fund new AI model"
assert proposal.status == ProposalState.ACTIVE
assert proposal.proposal_type == ProposalType.GRANT
@pytest.mark.asyncio
async def test_cast_vote(dao_service):
await dao_service.register_member(MemberCreate(wallet_address="0xDAO1", staked_amount=100.0))
await dao_service.register_member(MemberCreate(wallet_address="0xDAO2", staked_amount=50.0))
prop_req = ProposalCreate(
proposer_address="0xDAO1",
title="Test Proposal",
description="Testing voting"
)
proposal = await dao_service.create_proposal(prop_req)
# Cast vote
vote_req = VoteCreate(
member_address="0xDAO2",
proposal_id=proposal.id,
support=True
)
vote = await dao_service.cast_vote(vote_req)
assert vote.support is True
assert vote.weight == 50.0
dao_service.session.refresh(proposal)
assert proposal.for_votes == 50.0
@pytest.mark.asyncio
async def test_execute_proposal_success(dao_service, test_db):
await dao_service.register_member(MemberCreate(wallet_address="0xDAO1", staked_amount=100.0))
prop_req = ProposalCreate(
proposer_address="0xDAO1",
title="Test Grant",
description="Testing grant execution",
proposal_type=ProposalType.GRANT,
execution_payload={"amount": "500", "recipient_address": "0xDev"}
)
proposal = await dao_service.create_proposal(prop_req)
await dao_service.cast_vote(VoteCreate(
member_address="0xDAO1",
proposal_id=proposal.id,
support=True
))
# Fast forward time to end of voting period
proposal.end_time = datetime.utcnow() - timedelta(seconds=1)
test_db.commit()
exec_proposal = await dao_service.execute_proposal(proposal.id)
assert exec_proposal.status == ProposalState.EXECUTED
# Verify treasury allocation was created
from app.domain.dao_governance import TreasuryAllocation
from sqlmodel import select
allocation = test_db.exec(select(TreasuryAllocation).where(TreasuryAllocation.proposal_id == proposal.id)).first()
assert allocation is not None
assert allocation.amount == 500.0
assert allocation.recipient_address == "0xDev"

View File

@@ -0,0 +1,110 @@
import pytest
from unittest.mock import AsyncMock
from datetime import datetime, timedelta
from sqlmodel import Session, create_engine, SQLModel
from sqlmodel.pool import StaticPool
from fastapi import HTTPException
from app.services.developer_platform_service import DeveloperPlatformService
from app.domain.developer_platform import BountyStatus, CertificationLevel
from app.schemas.developer_platform import (
DeveloperCreate, BountyCreate, BountySubmissionCreate, CertificationGrant
)
@pytest.fixture
def test_db():
engine = create_engine(
"sqlite:///:memory:",
connect_args={"check_same_thread": False},
poolclass=StaticPool,
)
SQLModel.metadata.create_all(engine)
session = Session(engine)
yield session
session.close()
@pytest.fixture
def mock_contract_service():
return AsyncMock()
@pytest.fixture
def dev_service(test_db, mock_contract_service):
return DeveloperPlatformService(
session=test_db,
contract_service=mock_contract_service
)
@pytest.mark.asyncio
async def test_register_developer(dev_service):
req = DeveloperCreate(
wallet_address="0xDev1",
github_handle="dev_one",
skills=["python", "solidity"]
)
dev = await dev_service.register_developer(req)
assert dev.wallet_address == "0xDev1"
assert dev.reputation_score == 0.0
assert "solidity" in dev.skills
@pytest.mark.asyncio
async def test_grant_certification(dev_service):
dev = await dev_service.register_developer(DeveloperCreate(wallet_address="0xDev1"))
req = CertificationGrant(
developer_id=dev.id,
certification_name="ZK-Circuit Architect",
level=CertificationLevel.ADVANCED,
issued_by="0xDAOAdmin"
)
cert = await dev_service.grant_certification(req)
assert cert.developer_id == dev.id
assert cert.level == CertificationLevel.ADVANCED
# Check reputation boost (ADVANCED = +50.0)
dev_service.session.refresh(dev)
assert dev.reputation_score == 50.0
@pytest.mark.asyncio
async def test_bounty_lifecycle(dev_service):
# 1. Register Developer
dev = await dev_service.register_developer(DeveloperCreate(wallet_address="0xDev1"))
# 2. Create Bounty
bounty_req = BountyCreate(
title="Implement Atomic Swap",
description="Write a secure HTLC contract",
reward_amount=1000.0,
creator_address="0xCreator"
)
bounty = await dev_service.create_bounty(bounty_req)
assert bounty.status == BountyStatus.OPEN
# 3. Submit Work
sub_req = BountySubmissionCreate(
developer_id=dev.id,
github_pr_url="https://github.com/aitbc/pr/1"
)
sub = await dev_service.submit_bounty(bounty.id, sub_req)
assert sub.bounty_id == bounty.id
dev_service.session.refresh(bounty)
assert bounty.status == BountyStatus.IN_REVIEW
# 4. Approve Submission
appr_sub = await dev_service.approve_submission(sub.id, reviewer_address="0xReviewer", review_notes="Looks great!")
assert appr_sub.is_approved is True
assert appr_sub.tx_hash_reward is not None
dev_service.session.refresh(bounty)
dev_service.session.refresh(dev)
assert bounty.status == BountyStatus.COMPLETED
assert bounty.assigned_developer_id == dev.id
assert dev.total_earned_aitbc == 1000.0
assert dev.reputation_score == 5.0 # Base bump for finishing a bounty