824 lines
35 KiB
Python
824 lines
35 KiB
Python
import unittest
|
|
from datetime import datetime, timezone
|
|
|
|
from fastapi import HTTPException
|
|
|
|
from app.services.template_service import DEFAULT_TEMPLATES, TemplateService
|
|
|
|
|
|
class StubRepository:
|
|
def __init__(self):
|
|
self.items = {}
|
|
|
|
def create(self, contract_template):
|
|
if contract_template.created_at is None:
|
|
contract_template.created_at = datetime.now(timezone.utc)
|
|
if contract_template.updated_at is None:
|
|
contract_template.updated_at = datetime.now(timezone.utc)
|
|
self.items[contract_template.id] = contract_template
|
|
return contract_template
|
|
|
|
def update(self, contract_template):
|
|
contract_template.updated_at = datetime.now(timezone.utc)
|
|
self.items[contract_template.id] = contract_template
|
|
return contract_template
|
|
|
|
def delete(self, contract_template):
|
|
self.items.pop(contract_template.id, None)
|
|
|
|
def get_by_id(self, template_id):
|
|
return self.items.get(template_id)
|
|
|
|
def get_by_name(self, tenant_id, name):
|
|
normalized_name = name.strip().lower()
|
|
for item in self.items.values():
|
|
if item.tenant_id == tenant_id and item.name.strip().lower() == normalized_name:
|
|
return item
|
|
return None
|
|
|
|
def get_by_template_code(self, tenant_id, template_code):
|
|
normalized_code = template_code.strip().lower()
|
|
for item in self.items.values():
|
|
if item.tenant_id == tenant_id and (item.template_code or "").strip().lower() == normalized_code:
|
|
return item
|
|
return None
|
|
|
|
def list_paginated(self, tenant_id, page, page_size, contract_type=None, document_type_code=None, template_category=None, industry_category=None):
|
|
items = [item for item in self.items.values() if item.tenant_id == tenant_id]
|
|
if contract_type:
|
|
items = [item for item in items if item.contract_type == contract_type]
|
|
if document_type_code:
|
|
items = [item for item in items if item.document_type_code == document_type_code]
|
|
if template_category:
|
|
items = [item for item in items if item.template_category == template_category]
|
|
if industry_category:
|
|
items = [item for item in items if item.industry_category == industry_category]
|
|
items.sort(key=lambda item: (item.updated_at, item.created_at), reverse=True)
|
|
start = (page - 1) * page_size
|
|
return items[start : start + page_size]
|
|
|
|
def count(self, tenant_id, contract_type=None, document_type_code=None, template_category=None, industry_category=None):
|
|
return len(self.list_paginated(tenant_id, 1, 10000, contract_type, document_type_code, template_category, industry_category))
|
|
|
|
|
|
class StubVersionRepository:
|
|
def __init__(self):
|
|
self.items = []
|
|
|
|
def create(self, version):
|
|
self.items.append(version)
|
|
return version
|
|
|
|
def list_by_template_id(self, tenant_id, template_id):
|
|
versions = [
|
|
item
|
|
for item in self.items
|
|
if item.tenant_id == tenant_id and item.template_id == template_id
|
|
]
|
|
versions.sort(key=lambda item: item.created_at, reverse=True)
|
|
return versions
|
|
|
|
def clear_current(self, tenant_id, template_id):
|
|
for item in self.items:
|
|
if item.tenant_id == tenant_id and item.template_id == template_id:
|
|
item.is_current = False
|
|
|
|
def delete_by_template_id(self, tenant_id, template_id):
|
|
self.items = [
|
|
item
|
|
for item in self.items
|
|
if not (item.tenant_id == tenant_id and item.template_id == template_id)
|
|
]
|
|
|
|
|
|
class StubGovernanceAsset:
|
|
def __init__(self, asset_id: str, production_status: str = "ready"):
|
|
self.id = f"govast-{asset_id.lower()}"
|
|
self.asset_id = asset_id
|
|
self.production_status = production_status
|
|
|
|
|
|
class StubGovernanceAssetRepository:
|
|
def __init__(self):
|
|
self.items = {}
|
|
|
|
def add(self, asset_type, asset_id, production_status="ready"):
|
|
self.items[(asset_type, asset_id)] = StubGovernanceAsset(asset_id, production_status)
|
|
|
|
def get_by_identity(self, tenant_id, asset_type, source_asset_id):
|
|
return self.items.get((asset_type, source_asset_id))
|
|
|
|
|
|
class TemplateServiceTestCase(unittest.TestCase):
|
|
def setUp(self):
|
|
self.service = TemplateService(db=None)
|
|
self.service.repository = StubRepository()
|
|
self.service.version_repository = StubVersionRepository()
|
|
self.service.governance_asset_repository = StubGovernanceAssetRepository()
|
|
self.current_user = {
|
|
"id": "user-admin",
|
|
"tenant_id": "tenant-demo-001",
|
|
"role": "admin",
|
|
"display_name": "Admin",
|
|
}
|
|
self.other_user = {"id": "user-other", "tenant_id": "tenant-other", "role": "admin"}
|
|
self.member_user = {"id": "user-member", "tenant_id": "tenant-demo-001", "role": "member"}
|
|
|
|
def test_admin_can_create_and_list_templates(self):
|
|
created = self.service.create_template(
|
|
self.current_user,
|
|
name=" Employment Template ",
|
|
contract_type=" labor ",
|
|
description=" Standard employment template ",
|
|
content=" Clause one ",
|
|
variables_json='[{"name": "employee_name", "label": "Employee Name"}]',
|
|
enabled=True,
|
|
change_summary="Initial import",
|
|
)
|
|
|
|
listed = self.service.list_templates(self.current_user, page=0, page_size=200, contract_type=" labor ")
|
|
|
|
self.assertEqual(created["name"], "Employment Template")
|
|
self.assertEqual(created["contract_type"], "labor")
|
|
self.assertEqual(created["description"], "Standard employment template")
|
|
self.assertEqual(created["content"], "Clause one")
|
|
self.assertEqual(created["variables"][0]["name"], "employee_name")
|
|
self.assertEqual(created["template_code"], "TPL-LABOR-CONTRACT-001")
|
|
self.assertEqual(created["document_type_code"], "labor_contract")
|
|
self.assertEqual(created["template_category"], "employment-hr")
|
|
self.assertEqual(created["industry_category"], "general-enterprise")
|
|
self.assertEqual(created["current_version_label"], "v1.0")
|
|
self.assertEqual(created["version_count"], 1)
|
|
self.assertEqual(listed["page"], 1)
|
|
self.assertEqual(listed["page_size"], 100)
|
|
self.assertEqual(listed["total"], 1)
|
|
self.assertEqual(listed["items"][0]["id"], created["id"])
|
|
|
|
def test_update_template_requires_same_tenant(self):
|
|
created = self.service.create_template(
|
|
self.current_user,
|
|
name="Procurement Template",
|
|
contract_type="procurement",
|
|
description=None,
|
|
content="Payment clause",
|
|
variables_json=None,
|
|
enabled=True,
|
|
)
|
|
|
|
with self.assertRaises(HTTPException) as context:
|
|
self.service.update_template(
|
|
self.other_user,
|
|
created["id"],
|
|
name="Procurement Template Updated",
|
|
contract_type="procurement",
|
|
description="Cross-tenant",
|
|
content="Updated payment clause",
|
|
variables_json=None,
|
|
enabled=False,
|
|
)
|
|
|
|
self.assertEqual(context.exception.status_code, 404)
|
|
self.assertEqual(context.exception.detail, "template not found")
|
|
|
|
def test_labor_service_template_defaults_to_boundary_metadata(self):
|
|
created = self.service.create_template(
|
|
self.current_user,
|
|
name="Labor Service Agreement",
|
|
contract_type=" labor-service ",
|
|
description="Independent labor service boundary template",
|
|
content="Boundary clause",
|
|
variables_json=None,
|
|
enabled=True,
|
|
)
|
|
|
|
self.assertEqual(created["contract_type"], "labor-service")
|
|
self.assertEqual(created["template_code"], "TPL-LABOR-SERVICE-001")
|
|
self.assertEqual(created["document_type_code"], "labor_service_agreement")
|
|
self.assertEqual(created["template_category"], "employment-hr")
|
|
self.assertEqual(created["industry_category"], "general-enterprise")
|
|
|
|
def test_service_template_defaults_to_boundary_metadata(self):
|
|
created = self.service.create_template(
|
|
self.current_user,
|
|
name="通用服务合同模板",
|
|
contract_type=" service ",
|
|
description="General service template",
|
|
content="Service clause",
|
|
variables_json=None,
|
|
enabled=True,
|
|
)
|
|
|
|
self.assertEqual(created["contract_type"], "service")
|
|
self.assertEqual(created["template_code"], "TPL-SERVICE-GENERAL-001")
|
|
self.assertEqual(created["document_type_code"], "service_general")
|
|
self.assertEqual(created["template_category"], "service-delivery")
|
|
self.assertEqual(created["industry_category"], "professional-services")
|
|
|
|
def test_technical_service_template_defaults_to_boundary_metadata(self):
|
|
created = self.service.create_template(
|
|
self.current_user,
|
|
name="技术服务合同模板",
|
|
contract_type=" technical-service ",
|
|
description="Technical service template",
|
|
content="Boundary clause",
|
|
variables_json=None,
|
|
enabled=True,
|
|
)
|
|
|
|
self.assertEqual(created["contract_type"], "technical-service")
|
|
self.assertEqual(created["template_code"], "TPL-TECHNICAL-SERVICE-001")
|
|
self.assertEqual(created["document_type_code"], "technical_service")
|
|
self.assertEqual(created["template_category"], "service-delivery")
|
|
self.assertEqual(created["industry_category"], "technology")
|
|
|
|
def test_software_development_template_defaults_to_boundary_metadata(self):
|
|
created = self.service.create_template(
|
|
self.current_user,
|
|
name="软件技术开发合同模板",
|
|
contract_type=" software-development ",
|
|
description="Software development template",
|
|
content="Boundary clause",
|
|
variables_json=None,
|
|
enabled=True,
|
|
)
|
|
|
|
self.assertEqual(created["contract_type"], "software-development")
|
|
self.assertEqual(created["template_code"], "TPL-TECH-DEVELOPMENT-001")
|
|
self.assertEqual(created["document_type_code"], "software_development")
|
|
self.assertEqual(created["template_category"], "service-delivery")
|
|
self.assertEqual(created["industry_category"], "technology")
|
|
|
|
def test_lease_house_template_defaults_to_boundary_metadata(self):
|
|
created = self.service.create_template(
|
|
self.current_user,
|
|
name="房屋租赁合同模板",
|
|
contract_type=" lease-house ",
|
|
description="House lease template",
|
|
content="Boundary clause",
|
|
variables_json=None,
|
|
enabled=True,
|
|
)
|
|
|
|
self.assertEqual(created["contract_type"], "lease-house")
|
|
self.assertEqual(created["template_code"], "TPL-LEASE-HOUSE-001")
|
|
self.assertEqual(created["document_type_code"], "lease_house")
|
|
self.assertEqual(created["template_category"], "real-estate")
|
|
self.assertEqual(created["industry_category"], "real-estate")
|
|
|
|
def test_cooperation_framework_template_defaults_to_boundary_metadata(self):
|
|
created = self.service.create_template(
|
|
self.current_user,
|
|
name="合作框架协议模板",
|
|
contract_type=" cooperation-framework ",
|
|
description="Cooperation framework template",
|
|
content="Boundary clause",
|
|
variables_json=None,
|
|
enabled=True,
|
|
)
|
|
|
|
self.assertEqual(created["contract_type"], "cooperation-framework")
|
|
self.assertEqual(created["template_code"], "TPL-COOPERATION-FRAMEWORK-001")
|
|
self.assertEqual(created["document_type_code"], "cooperation_framework")
|
|
self.assertEqual(created["template_category"], "commercial-cooperation")
|
|
self.assertEqual(created["industry_category"], "general-enterprise")
|
|
|
|
def test_equity_transfer_template_defaults_to_boundary_metadata(self):
|
|
created = self.service.create_template(
|
|
self.current_user,
|
|
name="股权转让协议模板",
|
|
contract_type=" equity-transfer ",
|
|
description="Equity transfer boundary template",
|
|
content="Boundary clause",
|
|
variables_json=None,
|
|
enabled=True,
|
|
)
|
|
|
|
self.assertEqual(created["contract_type"], "equity-transfer")
|
|
self.assertEqual(created["template_code"], "TPL-EQUITY-TRANSFER-001")
|
|
self.assertEqual(created["document_type_code"], "equity_transfer")
|
|
self.assertEqual(created["template_category"], "investment-finance")
|
|
self.assertEqual(created["industry_category"], "general-enterprise")
|
|
|
|
def test_demand_letter_template_defaults_to_boundary_metadata(self):
|
|
created = self.service.create_template(
|
|
self.current_user,
|
|
name="催告函模板",
|
|
contract_type=" demand-letter ",
|
|
description="Pre-litigation demand letter template",
|
|
content="Boundary clause",
|
|
variables_json=None,
|
|
enabled=True,
|
|
)
|
|
|
|
self.assertEqual(created["contract_type"], "demand-letter")
|
|
self.assertEqual(created["template_code"], "TPL-DEMAND-LETTER-001")
|
|
self.assertEqual(created["document_type_code"], "demand_letter")
|
|
self.assertEqual(created["template_category"], "notices-letters")
|
|
self.assertEqual(created["industry_category"], "general-enterprise")
|
|
|
|
def test_rescission_notice_template_defaults_to_boundary_metadata(self):
|
|
created = self.service.create_template(
|
|
self.current_user,
|
|
name="解除合同通知书模板",
|
|
contract_type=" rescission-notice ",
|
|
description="Contract termination notice template",
|
|
content="Boundary clause",
|
|
variables_json=None,
|
|
enabled=True,
|
|
)
|
|
|
|
self.assertEqual(created["contract_type"], "rescission-notice")
|
|
self.assertEqual(created["template_code"], "TPL-CONTRACT-TERMINATION-NOTICE-001")
|
|
self.assertEqual(created["document_type_code"], "rescission_notice")
|
|
self.assertEqual(created["template_category"], "notices-letters")
|
|
self.assertEqual(created["industry_category"], "general-enterprise")
|
|
|
|
def test_agency_distribution_template_defaults_to_boundary_metadata(self):
|
|
created = self.service.create_template(
|
|
self.current_user,
|
|
name="代理/经销协议模板",
|
|
contract_type=" agency-distribution ",
|
|
description="Channel distribution template",
|
|
content="Boundary clause",
|
|
variables_json=None,
|
|
enabled=True,
|
|
)
|
|
|
|
self.assertEqual(created["contract_type"], "agency-distribution")
|
|
self.assertEqual(created["template_code"], "TPL-AGENCY-DISTRIBUTION-001")
|
|
self.assertEqual(created["document_type_code"], "agency_distribution_agreement")
|
|
self.assertEqual(created["template_category"], "commercial-cooperation")
|
|
self.assertEqual(created["industry_category"], "general-enterprise")
|
|
|
|
def test_franchise_template_defaults_to_boundary_metadata(self):
|
|
created = self.service.create_template(
|
|
self.current_user,
|
|
name="加盟合同模板",
|
|
contract_type=" franchise ",
|
|
description="Franchise agreement template",
|
|
content="Boundary clause",
|
|
variables_json=None,
|
|
enabled=True,
|
|
)
|
|
|
|
self.assertEqual(created["contract_type"], "franchise")
|
|
self.assertEqual(created["template_code"], "TPL-FRANCHISE-001")
|
|
self.assertEqual(created["document_type_code"], "franchise_agreement")
|
|
self.assertEqual(created["template_category"], "commercial-cooperation")
|
|
self.assertEqual(created["industry_category"], "general-enterprise")
|
|
|
|
def test_intercompany_loan_template_defaults_to_boundary_metadata(self):
|
|
created = self.service.create_template(
|
|
self.current_user,
|
|
name="企业间借款合同模板",
|
|
contract_type=" intercompany-loan ",
|
|
description="Intercompany loan template",
|
|
content="Boundary clause",
|
|
variables_json=None,
|
|
enabled=True,
|
|
)
|
|
|
|
self.assertEqual(created["contract_type"], "intercompany-loan")
|
|
self.assertEqual(created["template_code"], "TPL-INTERCOMPANY-LOAN-001")
|
|
self.assertEqual(created["document_type_code"], "intercompany_loan_agreement")
|
|
self.assertEqual(created["template_category"], "investment-finance")
|
|
self.assertEqual(created["industry_category"], "general-enterprise")
|
|
|
|
def test_investment_template_defaults_to_boundary_metadata(self):
|
|
created = self.service.create_template(
|
|
self.current_user,
|
|
name="投资协议模板",
|
|
contract_type=" investment_agreement ",
|
|
description="Investment agreement template",
|
|
content="Boundary clause",
|
|
variables_json=None,
|
|
enabled=True,
|
|
)
|
|
|
|
self.assertEqual(created["contract_type"], "investment_agreement")
|
|
self.assertEqual(created["template_code"], "TPL-INVESTMENT-AGREEMENT-001")
|
|
self.assertEqual(created["document_type_code"], "investment_agreement")
|
|
self.assertEqual(created["template_category"], "investment-finance")
|
|
self.assertEqual(created["industry_category"], "general-enterprise")
|
|
|
|
def test_valuation_adjustment_template_defaults_to_boundary_metadata(self):
|
|
created = self.service.create_template(
|
|
self.current_user,
|
|
name="对赌协议模板",
|
|
contract_type=" valuation-adjustment ",
|
|
description="Valuation adjustment template",
|
|
content="Boundary clause",
|
|
variables_json=None,
|
|
enabled=True,
|
|
)
|
|
|
|
self.assertEqual(created["contract_type"], "valuation-adjustment")
|
|
self.assertEqual(created["template_code"], "TPL-VALUATION-ADJUSTMENT-001")
|
|
self.assertEqual(created["document_type_code"], "valuation_adjustment_agreement")
|
|
self.assertEqual(created["template_category"], "investment-finance")
|
|
self.assertEqual(created["industry_category"], "general-enterprise")
|
|
|
|
def test_shareholder_resolution_template_defaults_to_boundary_metadata(self):
|
|
created = self.service.create_template(
|
|
self.current_user,
|
|
name="股东会决议模板",
|
|
contract_type=" shareholder-resolution ",
|
|
description="Shareholder resolution template",
|
|
content="Boundary clause",
|
|
variables_json=None,
|
|
enabled=True,
|
|
)
|
|
|
|
self.assertEqual(created["contract_type"], "shareholder-resolution")
|
|
self.assertEqual(created["template_code"], "TPL-SHAREHOLDER-RESOLUTION-001")
|
|
self.assertEqual(created["document_type_code"], "shareholder_resolution")
|
|
self.assertEqual(created["template_category"], "corporate-governance")
|
|
self.assertEqual(created["industry_category"], "general-enterprise")
|
|
|
|
def test_power_of_attorney_template_defaults_to_boundary_metadata(self):
|
|
created = self.service.create_template(
|
|
self.current_user,
|
|
name="授权委托书模板",
|
|
contract_type=" power-of-attorney ",
|
|
description="Power of attorney template",
|
|
content="Boundary clause",
|
|
variables_json=None,
|
|
enabled=True,
|
|
)
|
|
|
|
self.assertEqual(created["contract_type"], "power-of-attorney")
|
|
self.assertEqual(created["template_code"], "TPL-POWER-OF-ATTORNEY-001")
|
|
self.assertEqual(created["document_type_code"], "power_of_attorney")
|
|
self.assertEqual(created["template_category"], "corporate-governance")
|
|
self.assertEqual(created["industry_category"], "general-enterprise")
|
|
|
|
def test_legal_opinion_template_defaults_to_boundary_metadata(self):
|
|
created = self.service.create_template(
|
|
self.current_user,
|
|
name="法律意见书模板",
|
|
contract_type=" legal-opinion ",
|
|
description="Legal opinion template",
|
|
content="Boundary clause",
|
|
variables_json=None,
|
|
enabled=True,
|
|
)
|
|
|
|
self.assertEqual(created["contract_type"], "legal-opinion")
|
|
self.assertEqual(created["template_code"], "TPL-LEGAL-OPINION-001")
|
|
self.assertEqual(created["document_type_code"], "legal_opinion")
|
|
self.assertEqual(created["template_category"], "commercial-compliance")
|
|
self.assertEqual(created["industry_category"], "general-enterprise")
|
|
|
|
def test_nda_template_defaults_to_boundary_metadata(self):
|
|
created = self.service.create_template(
|
|
self.current_user,
|
|
name="商业保密协议模板",
|
|
contract_type=" nda ",
|
|
description="Commercial NDA template",
|
|
content="Boundary clause",
|
|
variables_json=None,
|
|
enabled=True,
|
|
)
|
|
|
|
self.assertEqual(created["contract_type"], "nda")
|
|
self.assertEqual(created["template_code"], "TPL-NDA-COMMERCIAL-001")
|
|
self.assertEqual(created["document_type_code"], "nda_commercial")
|
|
self.assertEqual(created["template_category"], "commercial-compliance")
|
|
self.assertEqual(created["industry_category"], "general-enterprise")
|
|
|
|
def test_property_service_template_defaults_to_boundary_metadata(self):
|
|
created = self.service.create_template(
|
|
self.current_user,
|
|
name="物业服务合同模板",
|
|
contract_type=" property-service ",
|
|
description="Property service template",
|
|
content="Boundary clause",
|
|
variables_json=None,
|
|
enabled=True,
|
|
)
|
|
|
|
self.assertEqual(created["contract_type"], "property-service")
|
|
self.assertEqual(created["template_code"], "TPL-PROPERTY-SERVICE-001")
|
|
self.assertEqual(created["document_type_code"], "property_service")
|
|
self.assertEqual(created["template_category"], "real-estate")
|
|
self.assertEqual(created["industry_category"], "real-estate")
|
|
|
|
def test_seed_default_templates_reuses_purchase_template_by_template_code(self):
|
|
self.service.create_template(
|
|
self.current_user,
|
|
name="采购合同标准模板",
|
|
contract_type="procurement",
|
|
description="Legacy purchase template",
|
|
content="Legacy content",
|
|
variables_json='[{"name": "buyer", "label": "Buyer"}]',
|
|
enabled=True,
|
|
)
|
|
|
|
seeded = self.service.seed_default_templates(self.current_user)
|
|
|
|
self.assertEqual(seeded["existing_count"], 1)
|
|
self.assertEqual(seeded["created_count"], len(DEFAULT_TEMPLATES) - 1)
|
|
self.assertTrue(any(item["template_code"] == "TPL-PURCHASE-GOODS-001" for item in seeded["existing"]))
|
|
self.assertTrue(any(item["template_code"] == "TPL-SERVICE-GENERAL-001" for item in seeded["created"]))
|
|
self.assertEqual(
|
|
self.service.repository.get_by_name(self.current_user["tenant_id"], "采购合同标准模板").template_code,
|
|
"TPL-PURCHASE-GOODS-001",
|
|
)
|
|
self.assertEqual(
|
|
self.service.repository.get_by_name(self.current_user["tenant_id"], "通用服务合同模板").template_code,
|
|
"TPL-SERVICE-GENERAL-001",
|
|
)
|
|
|
|
def test_delete_template_removes_item(self):
|
|
created = self.service.create_template(
|
|
self.current_user,
|
|
name="Service Template",
|
|
contract_type="service",
|
|
description=None,
|
|
content="Service scope",
|
|
variables_json=None,
|
|
enabled=True,
|
|
)
|
|
|
|
self.service.delete_template(self.current_user, created["id"])
|
|
listed = self.service.list_templates(self.current_user)
|
|
|
|
self.assertEqual(listed["total"], 0)
|
|
self.assertEqual(listed["items"], [])
|
|
self.assertEqual(self.service.version_repository.list_by_template_id(self.current_user["tenant_id"], created["id"]), [])
|
|
|
|
def test_member_cannot_manage_templates(self):
|
|
with self.assertRaises(HTTPException) as context:
|
|
self.service.list_templates(self.member_user)
|
|
|
|
self.assertEqual(context.exception.status_code, 403)
|
|
self.assertEqual(context.exception.detail, "forbidden")
|
|
|
|
def test_admin_can_update_template_with_multiple_variables_and_version_bump(self):
|
|
created = self.service.create_template(
|
|
self.current_user,
|
|
name="Procurement Template",
|
|
contract_type="procurement",
|
|
description="Original description",
|
|
content="Original content",
|
|
variables_json='[{"name": "supplier_name", "label": "Supplier Name"}]',
|
|
enabled=True,
|
|
)
|
|
|
|
updated = self.service.update_template(
|
|
self.current_user,
|
|
created["id"],
|
|
name="Procurement Template Updated",
|
|
contract_type="procurement",
|
|
description="Updated description",
|
|
content="Updated content",
|
|
variables_json='[{"name": "supplier_name", "label": "Supplier Name"}, {"name": "contract_amount", "label": "Contract Amount"}]',
|
|
enabled=False,
|
|
change_summary="Add amount variable",
|
|
)
|
|
|
|
self.assertEqual(updated["name"], "Procurement Template Updated")
|
|
self.assertFalse(updated["enabled"])
|
|
self.assertEqual(
|
|
updated["variables"],
|
|
[
|
|
{"name": "supplier_name", "label": "Supplier Name"},
|
|
{"name": "contract_amount", "label": "Contract Amount"},
|
|
],
|
|
)
|
|
self.assertEqual(updated["current_version_label"], "v1.1")
|
|
self.assertEqual(updated["latest_change_summary"], "Add amount variable")
|
|
self.assertEqual(updated["version_count"], 2)
|
|
self.assertEqual(updated["template_code"], "TPL-PURCHASE-GOODS-001")
|
|
self.assertEqual(updated["document_type_code"], "procurement_goods")
|
|
self.assertEqual(updated["template_category"], "procurement-supply")
|
|
self.assertEqual(updated["industry_category"], "general-enterprise")
|
|
|
|
def test_create_and_update_template_accept_explicit_governance_code(self):
|
|
created = self.service.create_template(
|
|
self.current_user,
|
|
name="Phase 13 Purchase Template",
|
|
contract_type="purchase",
|
|
description="Imported from Phase 13",
|
|
content="Template body",
|
|
variables_json=None,
|
|
enabled=True,
|
|
template_code="TPL-PHASE13-PURCHASE-001",
|
|
)
|
|
|
|
updated = self.service.update_template(
|
|
self.current_user,
|
|
created["id"],
|
|
name="Phase 13 Purchase Template",
|
|
contract_type="purchase",
|
|
description="Imported from Phase 13",
|
|
content="Updated template body",
|
|
variables_json=None,
|
|
enabled=True,
|
|
template_code="TPL-PURCHASE-GOODS-001",
|
|
)
|
|
|
|
self.assertEqual(created["template_code"], "TPL-PHASE13-PURCHASE-001")
|
|
self.assertEqual(updated["template_code"], "TPL-PURCHASE-GOODS-001")
|
|
|
|
def test_get_template_versions_returns_current_and_history(self):
|
|
created = self.service.create_template(
|
|
self.current_user,
|
|
name="Procurement Template",
|
|
contract_type="procurement",
|
|
description="Original description",
|
|
content="Original content",
|
|
variables_json='[{"name": "supplier_name", "label": "Supplier Name"}]',
|
|
enabled=True,
|
|
change_summary="Initial version",
|
|
)
|
|
|
|
self.service.update_template(
|
|
self.current_user,
|
|
created["id"],
|
|
name="Procurement Template Updated",
|
|
contract_type="procurement",
|
|
description="Updated description",
|
|
content="Updated content",
|
|
variables_json='[{"name": "supplier_name", "label": "Supplier Name"}]',
|
|
enabled=True,
|
|
change_summary="Add payment clause",
|
|
)
|
|
|
|
versions = self.service.get_template_versions(self.current_user, created["id"])
|
|
|
|
self.assertEqual(versions["current_version_label"], "v1.1")
|
|
self.assertEqual(versions["version_count"], 2)
|
|
self.assertEqual(len(versions["items"]), 2)
|
|
self.assertEqual(versions["items"][0]["version_label"], "v1.1")
|
|
self.assertEqual(versions["items"][0]["change_summary"], "Add payment clause")
|
|
self.assertEqual(versions["items"][0]["template_category"], "procurement-supply")
|
|
self.assertEqual(versions["items"][0]["document_type_code"], "procurement_goods")
|
|
self.assertTrue(versions["items"][0]["is_current"])
|
|
self.assertEqual(versions["items"][1]["version_label"], "v1.0")
|
|
|
|
def test_list_templates_supports_template_and_industry_filters(self):
|
|
self.service.create_template(
|
|
self.current_user,
|
|
name="Employment Template",
|
|
contract_type="labor",
|
|
description=None,
|
|
content="Employment clause",
|
|
variables_json=None,
|
|
enabled=True,
|
|
)
|
|
self.service.create_template(
|
|
self.current_user,
|
|
name="Service Template",
|
|
contract_type="service",
|
|
description=None,
|
|
content="Service clause",
|
|
variables_json=None,
|
|
enabled=True,
|
|
template_category="service-delivery",
|
|
industry_category="professional-services",
|
|
)
|
|
|
|
filtered = self.service.list_templates(
|
|
self.current_user,
|
|
contract_type=None,
|
|
page=1,
|
|
page_size=20,
|
|
template_category="service-delivery",
|
|
industry_category="professional-services",
|
|
)
|
|
|
|
self.assertEqual(filtered["total"], 1)
|
|
self.assertEqual(filtered["items"][0]["name"], "Service Template")
|
|
|
|
document_type_filtered = self.service.list_templates(
|
|
self.current_user,
|
|
document_type_code="service_general",
|
|
)
|
|
|
|
self.assertEqual(document_type_filtered["total"], 1)
|
|
self.assertEqual(document_type_filtered["items"][0]["document_type_code"], "service_general")
|
|
|
|
def test_publish_and_unpublish_template_require_governance_asset(self):
|
|
created = self.service.create_template(
|
|
self.current_user,
|
|
name="Procurement Template",
|
|
contract_type="procurement",
|
|
description="Ready for publish",
|
|
content="Template body",
|
|
variables_json=None,
|
|
enabled=False,
|
|
)
|
|
self.service.governance_asset_repository.add("contract_template", "TPL-PURCHASE-GOODS-001", production_status="ready")
|
|
|
|
published = self.service.publish_template(self.current_user, created["id"])
|
|
self.assertEqual(published["published_by"], self.current_user["id"])
|
|
self.assertIsNotNone(published["published_at"])
|
|
self.assertEqual(published["latest_governance_asset_id"], "govast-tpl-purchase-goods-001")
|
|
self.assertTrue(published["enabled"])
|
|
|
|
unpublished = self.service.unpublish_template(self.current_user, created["id"])
|
|
self.assertIsNone(unpublished["published_at"])
|
|
self.assertIsNone(unpublished["published_by"])
|
|
self.assertIsNone(unpublished["latest_governance_asset_id"])
|
|
self.assertFalse(unpublished["enabled"])
|
|
|
|
def test_publish_template_blocks_without_ready_governance_asset(self):
|
|
created = self.service.create_template(
|
|
self.current_user,
|
|
name="Procurement Template",
|
|
contract_type="procurement",
|
|
description="Ready for publish",
|
|
content="Template body",
|
|
variables_json=None,
|
|
enabled=False,
|
|
)
|
|
with self.assertRaises(HTTPException) as context:
|
|
self.service.publish_template(self.current_user, created["id"])
|
|
self.assertEqual(context.exception.status_code, 409)
|
|
|
|
def test_rollback_template_creates_new_version_and_unpublishes(self):
|
|
created = self.service.create_template(
|
|
self.current_user,
|
|
name="Procurement Template",
|
|
contract_type="procurement",
|
|
description="Original",
|
|
content="Original content",
|
|
variables_json=None,
|
|
enabled=True,
|
|
change_summary="Initial version",
|
|
)
|
|
self.service.update_template(
|
|
self.current_user,
|
|
created["id"],
|
|
name="Procurement Template Updated",
|
|
contract_type="procurement",
|
|
description="Updated",
|
|
content="Updated content",
|
|
variables_json=None,
|
|
enabled=True,
|
|
change_summary="Second version",
|
|
)
|
|
rolled_back = self.service.rollback_template(self.current_user, created["id"], "v1.0")
|
|
self.assertEqual(rolled_back["content"], "Original content")
|
|
self.assertEqual(rolled_back["current_version_label"], "v1.2")
|
|
self.assertFalse(rolled_back["enabled"])
|
|
self.assertIsNone(rolled_back["published_at"])
|
|
|
|
def test_variables_json_must_be_valid_object_array(self):
|
|
with self.assertRaises(HTTPException) as invalid_json_context:
|
|
self.service.create_template(
|
|
self.current_user,
|
|
name="Template",
|
|
contract_type=None,
|
|
description=None,
|
|
content="Content",
|
|
variables_json="not-json",
|
|
enabled=True,
|
|
)
|
|
self.assertEqual(invalid_json_context.exception.status_code, 400)
|
|
self.assertEqual(invalid_json_context.exception.detail, "variables_json must be valid JSON")
|
|
|
|
with self.assertRaises(HTTPException) as invalid_type_context:
|
|
self.service.create_template(
|
|
self.current_user,
|
|
name="Template",
|
|
contract_type=None,
|
|
description=None,
|
|
content="Content",
|
|
variables_json='{"name": "employee_name"}',
|
|
enabled=True,
|
|
)
|
|
self.assertEqual(invalid_type_context.exception.status_code, 400)
|
|
self.assertEqual(invalid_type_context.exception.detail, "variables_json must be a JSON array")
|
|
|
|
with self.assertRaises(HTTPException) as invalid_item_context:
|
|
self.service.create_template(
|
|
self.current_user,
|
|
name="Template",
|
|
contract_type=None,
|
|
description=None,
|
|
content="Content",
|
|
variables_json='["employee_name"]',
|
|
enabled=True,
|
|
)
|
|
self.assertEqual(invalid_item_context.exception.status_code, 400)
|
|
self.assertEqual(invalid_item_context.exception.detail, "variables_json items must be objects")
|
|
|
|
with self.assertRaises(HTTPException) as invalid_nested_item_context:
|
|
self.service.create_template(
|
|
self.current_user,
|
|
name="Template",
|
|
contract_type=None,
|
|
description=None,
|
|
content="Content",
|
|
variables_json='[{"name": "employee_name"}, ["invalid"]]',
|
|
enabled=True,
|
|
)
|
|
self.assertEqual(invalid_nested_item_context.exception.status_code, 400)
|
|
self.assertEqual(invalid_nested_item_context.exception.detail, "variables_json items must be objects")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|