部署优化
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
@@ -19,14 +17,6 @@ from tests.company_reports_acceptance_fixture import (
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
||||
BUILDER_SCRIPT = PROJECT_ROOT / "company_reports" / "xlsx" / "build_workbook.mjs"
|
||||
ARTIFACT_PACKAGE = (
|
||||
PROJECT_ROOT
|
||||
/ "company_reports"
|
||||
/ "xlsx"
|
||||
/ "node_modules"
|
||||
/ "@oai"
|
||||
/ "artifact-tool"
|
||||
)
|
||||
|
||||
|
||||
def acceptance_reports():
|
||||
@@ -38,16 +28,6 @@ def acceptance_reports():
|
||||
)
|
||||
|
||||
|
||||
def artifact_tool_available() -> bool:
|
||||
configured = os.environ.get("COMPANY_REPORT_ARTIFACT_TOOL_MODULE", "").strip()
|
||||
return ARTIFACT_PACKAGE.exists() or bool(configured and Path(configured).is_file())
|
||||
|
||||
|
||||
def node_binary() -> str:
|
||||
configured = os.environ.get("COMPANY_REPORT_NODE_BINARY", "").strip()
|
||||
return configured or shutil.which("node") or ""
|
||||
|
||||
|
||||
class CompanyReportAcceptanceCoreTests(unittest.TestCase):
|
||||
def test_all_five_companies_have_contract_complete_month_end_reports(self):
|
||||
reports = acceptance_reports()
|
||||
@@ -106,15 +86,10 @@ class CompanyReportAcceptanceCoreTests(unittest.TestCase):
|
||||
self.assertEqual(dy_row.departure.day, 20)
|
||||
|
||||
|
||||
@unittest.skipUnless(artifact_tool_available(), "artifact-tool dependency is not installed")
|
||||
class CompanyReportAcceptanceWorkbookTests(unittest.TestCase):
|
||||
def test_real_builder_exports_reopens_and_renders_all_five_workbooks(self):
|
||||
node = node_binary()
|
||||
if not node:
|
||||
self.skipTest("Node.js is unavailable")
|
||||
def test_real_builder_exports_and_reopens_all_five_workbooks(self):
|
||||
builder = ArtifactToolBuilder(
|
||||
BUILDER_SCRIPT,
|
||||
node_binary=node,
|
||||
timeout_seconds=180,
|
||||
)
|
||||
with tempfile.TemporaryDirectory(prefix="company-report-acceptance-") as temp_dir:
|
||||
@@ -125,15 +100,12 @@ class CompanyReportAcceptanceWorkbookTests(unittest.TestCase):
|
||||
self.assertTrue(built.path.is_file())
|
||||
self.assertEqual(built.path.name, report.filename)
|
||||
self.assertEqual(built.summary["formula_count"], 0)
|
||||
self.assertEqual(built.summary["preview_count"], 3)
|
||||
self.assertEqual(built.summary["preview_count"], 0)
|
||||
self.assertEqual(
|
||||
built.summary["row_counts"],
|
||||
[len(period.rows) for period in report.periods],
|
||||
)
|
||||
self.assertEqual(
|
||||
len(list((root / report.company / "previews").glob("sheet-*.png"))),
|
||||
3,
|
||||
)
|
||||
self.assertEqual(len(list((root / report.company / "previews").glob("*"))), 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -2,7 +2,6 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import unittest
|
||||
from dataclasses import replace
|
||||
@@ -22,33 +21,10 @@ from tests.test_company_reports_service import FakeRepository, synthetic_snapsho
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
||||
BUILDER_SCRIPT = PROJECT_ROOT / "company_reports" / "xlsx" / "build_workbook.mjs"
|
||||
ARTIFACT_PACKAGE = (
|
||||
PROJECT_ROOT
|
||||
/ "company_reports"
|
||||
/ "xlsx"
|
||||
/ "node_modules"
|
||||
/ "@oai"
|
||||
/ "artifact-tool"
|
||||
)
|
||||
|
||||
|
||||
def artifact_tool_available() -> bool:
|
||||
configured = os.environ.get("COMPANY_REPORT_ARTIFACT_TOOL_MODULE", "").strip()
|
||||
return ARTIFACT_PACKAGE.exists() or bool(configured and Path(configured).is_file())
|
||||
|
||||
|
||||
def node_binary() -> str:
|
||||
configured = os.environ.get("COMPANY_REPORT_NODE_BINARY", "").strip()
|
||||
return configured or shutil.which("node") or ""
|
||||
|
||||
|
||||
@unittest.skipUnless(artifact_tool_available(), "artifact-tool dependency is not installed")
|
||||
class CompanyReportIntegrationTests(unittest.TestCase):
|
||||
def test_missing_and_unmatched_group_codes_export_blank_booking_room(self):
|
||||
node = node_binary()
|
||||
if not node:
|
||||
self.skipTest("Node.js is unavailable")
|
||||
|
||||
class CapturingBuilder:
|
||||
def __init__(self, delegate):
|
||||
self.delegate = delegate
|
||||
@@ -70,7 +46,7 @@ class CompanyReportIntegrationTests(unittest.TestCase):
|
||||
)
|
||||
)
|
||||
builder = CapturingBuilder(
|
||||
ArtifactToolBuilder(BUILDER_SCRIPT, node_binary=node, timeout_seconds=90)
|
||||
ArtifactToolBuilder(BUILDER_SCRIPT, timeout_seconds=90)
|
||||
)
|
||||
publisher = AtomicReportPublisher(project_root, output_root)
|
||||
service = CompanyReportService(
|
||||
@@ -101,10 +77,6 @@ class CompanyReportIntegrationTests(unittest.TestCase):
|
||||
)
|
||||
|
||||
def test_same_snapshot_rerun_reuses_publication_when_binary_changes(self):
|
||||
node = node_binary()
|
||||
if not node:
|
||||
self.skipTest("Node.js is unavailable")
|
||||
|
||||
class StableVersionRepository(FakeRepository):
|
||||
def reserve_report(self, report):
|
||||
reservation = ReservedReport(9001, 1, report.company)
|
||||
@@ -142,7 +114,7 @@ class CompanyReportIntegrationTests(unittest.TestCase):
|
||||
project_root.mkdir(parents=True)
|
||||
repository = StableVersionRepository(synthetic_snapshot())
|
||||
builder = RebuildingBuilder(
|
||||
ArtifactToolBuilder(BUILDER_SCRIPT, node_binary=node, timeout_seconds=90)
|
||||
ArtifactToolBuilder(BUILDER_SCRIPT, timeout_seconds=90)
|
||||
)
|
||||
publisher = AtomicReportPublisher(project_root, output_root)
|
||||
service = CompanyReportService(
|
||||
|
||||
@@ -7,9 +7,19 @@ from datetime import date
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from company_reports.contracts import BatchSnapshot, ErrorCode
|
||||
from openpyxl import load_workbook
|
||||
|
||||
from company_reports.contracts import (
|
||||
BatchSnapshot,
|
||||
CompanyReport,
|
||||
DailyVersionPin,
|
||||
ErrorCode,
|
||||
PeriodReport,
|
||||
ReportRow,
|
||||
)
|
||||
from company_reports.core import build_company_report
|
||||
from company_reports.publishing import (
|
||||
ArtifactToolBuilder,
|
||||
AtomicReportPublisher,
|
||||
BuiltWorkbook,
|
||||
PublicationError,
|
||||
@@ -53,6 +63,38 @@ def empty_report():
|
||||
)
|
||||
|
||||
|
||||
def workbook_report() -> CompanyReport:
|
||||
row = ReportRow(
|
||||
arrival=date(2026, 7, 8),
|
||||
departure=date(2026, 7, 10),
|
||||
nights=2,
|
||||
block_code="=BLOCK-A",
|
||||
res_comment="SYN-GROUP-A",
|
||||
booking_room="【DBL】1",
|
||||
total_booking_price="+900",
|
||||
normalized_group_code="SYN-GROUP-A",
|
||||
record_ids=(1,),
|
||||
duplicate_group=True,
|
||||
multi_price_review=True,
|
||||
)
|
||||
return CompanyReport(
|
||||
company="QBD",
|
||||
report_year=2026,
|
||||
report_month=7,
|
||||
as_of_date=date(2026, 7, 10),
|
||||
filename="QBD-July-2026.xlsx",
|
||||
periods=(
|
||||
PeriodReport(1, 10, True, "QBD 01-10 Jul 2026", (row,)),
|
||||
PeriodReport(11, 20, False, "QBD 11-20 Jul 2026", ()),
|
||||
PeriodReport(21, 31, False, "QBD 21-31 Jul 2026", ()),
|
||||
),
|
||||
warnings=(),
|
||||
errors=(),
|
||||
daily_versions=(DailyVersionPin(date(2026, 7, 8), 100),),
|
||||
booking_versions={"SYN-GROUP-A": 700},
|
||||
)
|
||||
|
||||
|
||||
def synthetic_built_workbook(
|
||||
work_dir: Path,
|
||||
filename: str,
|
||||
@@ -70,6 +112,41 @@ def synthetic_built_workbook(
|
||||
|
||||
|
||||
class CompanyReportPublishingTests(unittest.TestCase):
|
||||
def test_builder_creates_xlsx_without_private_artifact_tool_runtime(self):
|
||||
with tempfile.TemporaryDirectory(prefix="company-report-builder-test-") as temp_dir:
|
||||
root = Path(temp_dir)
|
||||
report = workbook_report()
|
||||
builder = ArtifactToolBuilder(
|
||||
Path("/private/artifact-tool/build_workbook.mjs"),
|
||||
node_binary="/definitely/not/node",
|
||||
)
|
||||
|
||||
built = builder.build(report, root)
|
||||
|
||||
self.assertTrue(built.path.is_file())
|
||||
self.assertEqual(built.path.stat().st_mode & 0o777, 0o600)
|
||||
self.assertEqual(built.summary["status"], "success")
|
||||
self.assertEqual(built.summary["row_counts"], [1, 0, 0])
|
||||
self.assertEqual(built.summary["formula_count"], 0)
|
||||
self.assertEqual(len(built.summary["semantic_sha256"]), 64)
|
||||
workbook = load_workbook(built.path)
|
||||
self.assertEqual(workbook.sheetnames, [period.sheet_name for period in report.periods])
|
||||
sheet = workbook[report.periods[0].sheet_name]
|
||||
self.assertEqual(
|
||||
[cell.value for cell in sheet[1]],
|
||||
[
|
||||
"ARRIVAL",
|
||||
"DEPARTURE",
|
||||
"NIGHTS",
|
||||
"BLOCK_CODE",
|
||||
"RES_COMMENT",
|
||||
"Booking Room",
|
||||
"Total Booking Price",
|
||||
],
|
||||
)
|
||||
self.assertEqual(sheet["D2"].value, "'=BLOCK-A")
|
||||
self.assertEqual(sheet["G2"].value, "'+900")
|
||||
|
||||
def test_publish_archives_prior_current_and_activates_new_version(self):
|
||||
with tempfile.TemporaryDirectory(prefix="company-report-publish-test-") as temp_dir:
|
||||
project_root = Path(temp_dir) / "project"
|
||||
|
||||
@@ -33,8 +33,12 @@ class DeploymentEntrypointTests(unittest.TestCase):
|
||||
root_requirements = (PROJECT_ROOT / "requirements.txt").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
company_requirements = (
|
||||
PROJECT_ROOT / "requirements-company-reports.txt"
|
||||
).read_text(encoding="utf-8")
|
||||
|
||||
self.assertIn("--enable-processing", compose)
|
||||
self.assertIn("--enable-company-reports", compose)
|
||||
self.assertNotIn("\n mcp:", compose)
|
||||
self.assertNotIn("DEERFLOW", compose)
|
||||
self.assertNotIn("ARR_MCP", compose)
|
||||
@@ -47,6 +51,13 @@ class DeploymentEntrypointTests(unittest.TestCase):
|
||||
self.assertNotIn("basic_auth", caddy)
|
||||
self.assertNotIn("requirements-agent-integration", root_requirements)
|
||||
self.assertNotIn("requirements-arr-mcp", root_requirements)
|
||||
self.assertIn("openpyxl", company_requirements)
|
||||
company_package = json.loads(
|
||||
(PROJECT_ROOT / "company_reports" / "xlsx" / "package.json").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
)
|
||||
self.assertNotIn("@oai/artifact-tool", json.dumps(company_package))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user