Files
ai-contract-platform/backend/tests/test_template_service.py
T

333 lines
13 KiB
Python

import unittest
from datetime import datetime, timezone
from fastapi import HTTPException
from app.services.template_service import 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 list_paginated(self, tenant_id, page, page_size, contract_type=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 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, template_category=None, industry_category=None):
return len(self.list_paginated(tenant_id, 1, 10000, contract_type, 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
class TemplateServiceTestCase(unittest.TestCase):
def setUp(self):
self.service = TemplateService(db=None)
self.service.repository = StubRepository()
self.service.version_repository = StubVersionRepository()
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_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_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"], [])
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_category"], "procurement-supply")
self.assertEqual(updated["industry_category"], "general-enterprise")
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.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")
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()