Files
playbook/test/test_tsl_api_framework.py
T

904 lines
33 KiB
Python

import ast
import csv
import json
import re
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
SKILL_ROOT = ROOT / "skills" / "tsl-api-reference"
CLASS_LOOKUP = SKILL_ROOT / "scripts" / "class_lookup.py"
API_LOOKUP = SKILL_ROOT / "scripts" / "lookup.py"
FRAMEWORK_INDEX = SKILL_ROOT / "data" / "framework_index.json"
FUNCTION_INDEX = SKILL_ROOT / "data" / "function_index.tsv"
CODEGEN_ROOT = SKILL_ROOT / "references" / "codegen"
def run_script(script, *args):
return subprocess.run(
[sys.executable, str(script), *args],
cwd=ROOT,
capture_output=True,
text=True,
)
def indexed_class_count():
with FUNCTION_INDEX.open(encoding="utf-8", newline="") as handle:
return sum(
row["kind"] == "class"
for row in csv.DictReader(handle, delimiter="\t")
)
class TslApiFrameworkTests(unittest.TestCase):
def test_web_development_support_functions_are_removed(self):
removed = {
"Web_tb_arrow",
"Web_tb_bar",
"Web_tb_gradient",
"Web_tb_npbar",
"Web_tb_npgridbar",
"Web_css_bgbar",
"Web_css_bgcolor",
"Web_css_bghist",
"Web_richtext_zdbgcolor",
"Web_spark_hist",
"Web_spark_nphist",
"Web_fig_box",
"Web_fig_heatmap",
"Web_fig_histogram",
"Web_fig_radar",
"Web_fig_tree_decomp",
"Web_fig_violin",
"Demo_framestyle_constyle_dotweb",
"Demo_framestyle_constyle_net",
"Demo_framestyle_heatmaptb_net",
"Demo_framestyle_net",
"Demo_framestyle_web",
"Demo_framestyle_data",
"Kerneldensityestimator",
"Web_color_theme",
"Web_color_triscale",
"Web_dict2tabletree",
"Web_format",
"Web_getcolorscalebyrgbarr",
"Web_getdefultcolor",
"Web_getlineargradient",
"Web_gettdbar",
"Web_html_span",
"Web_html_table",
"Web_table2dicttree",
"web_metric_TranValuesWithGroup",
"tsplot_PerformanceSummary",
"tssubplot_lines",
}
with FUNCTION_INDEX.open(encoding="utf-8", newline="") as handle:
indexed = {
row["name"].casefold()
for row in csv.DictReader(handle, delimiter="\t")
}
headings = set()
for path in CODEGEN_ROOT.rglob("*.md"):
text = path.read_text(encoding="utf-8")
headings.update(
match.group(1).split("(", 1)[0].casefold()
for match in re.finditer(
r"^## `([^`]+)`", text, re.MULTILINE
)
)
self.assertTrue({name.casefold() for name in removed}.isdisjoint(indexed))
self.assertTrue({name.casefold() for name in removed}.isdisjoint(headings))
self.assertTrue(
{"barline_web", "fastline_web", "pieline_web"} <= indexed
)
def test_recent_update_source_names_do_not_leak_into_api_taxonomy(self):
fallback_pages = [
path.relative_to(CODEGEN_ROOT).as_posix()
for path in CODEGEN_ROOT.rglob("recent-updates-*.md")
]
with FUNCTION_INDEX.open(encoding="utf-8", newline="") as handle:
fallback_rows = [
row["page"]
for row in csv.DictReader(handle, delimiter="\t")
if "recent-updates-" in row["page"]
or row["module"].startswith("recent-updates-")
]
self.assertEqual([], fallback_pages)
self.assertEqual([], fallback_rows)
def test_recent_financial_functions_have_normalized_signatures_and_sources(self):
expected = {
"adjustmentFactor": (
"AdjustmentFactor",
"adjustmentFactor(rate_day)",
"dotnet/financial/market-specified_date.md",
),
"stockZtOrDtCloseOfMD": (
"StockZtOrDtCloseOfMD",
"stockZtOrDtCloseOfMD(flag)",
"dotnet/financial/market-specified_date.md",
),
"getEtfListByDate": (
"GetETFListByDate",
"getEtfListByDate(bk_name, end_t)",
"dotnet/financial/fund-etf.md",
),
"getIndexAdjustDate": (
"GetIndexAdjustDate",
"getIndexAdjustDate(index_id, beg_t, end_t)",
"dotnet/financial/index-index_info.md",
),
"fi_report_excessYields": (
"fi_report_ExcessYields",
"fi_report_excessYields(rt_data, fname_pf, fname_bm, "
"fname_date, plot_type)",
"dotnet/financial/financial_engineering-factor_research-"
"performance_analysis-return_evaluation-intermediate-"
"common_reports.md",
),
"fi_report_corrAnalysis": (
"fi_report_CorrAnalysis",
"fi_report_corrAnalysis(rt_data, fname_date)",
"dotnet/financial/financial_engineering-factor_research-"
"performance_analysis-return_evaluation-intermediate-"
"common_reports.md",
),
"fi_report_historicalPerformance": (
"fi_report_HistoricalPerformance",
"fi_report_historicalPerformance(rt_data, fname_bm, "
"fname_date, cycle, factor_roll_n_year)",
"dotnet/financial/financial_engineering-factor_research-"
"performance_analysis-return_evaluation-intermediate-"
"common_reports.md",
),
"fi_report_periodReturn": (
"fi_report_PeriodReturn",
"fi_report_periodReturn(rt_data, fname_date)",
"dotnet/financial/financial_engineering-factor_research-"
"performance_analysis-return_evaluation-intermediate-"
"common_reports.md",
),
"fi_report_returnAndRisk": (
"fi_report_ReturnAndRisk",
"fi_report_returnAndRisk(rt_data, fname_date, qk_type, cycle, "
"days_in_1_y)",
"dotnet/financial/financial_engineering-factor_research-"
"performance_analysis-return_evaluation-intermediate-"
"common_reports.md",
),
"fi_report_riskFeature": (
"fi_report_RiskFeature",
"fi_report_riskFeature(rt_data, fname_bm, fname_date, "
"days_in_1_y)",
"dotnet/financial/financial_engineering-factor_research-"
"performance_analysis-return_evaluation-intermediate-"
"common_reports.md",
),
"fi_report_rollReturn": (
"fi_report_RollReturn",
"fi_report_rollReturn(rt_data, fname_date, cycle)",
"dotnet/financial/financial_engineering-factor_research-"
"performance_analysis-return_evaluation-intermediate-"
"common_reports.md",
),
"financialItemIn12Month_HK": (
"FinancialItemIn12Month_HK",
"financialItemIn12Month_HK(r_date, info_id, "
"if_to_func_currency)",
"dotnet/financial/financial_analysis-extended_"
"fundamental_data.md",
),
"reportType_HK": (
"ReportType_HK",
"reportType_HK(r_date, r_type)",
"dotnet/financial/financial_analysis-extended_"
"fundamental_data.md",
),
"originalToFunctionalCurrency_HK": (
"OriginalToFunctionalCurrency_HK",
"originalToFunctionalCurrency_HK(end_t, original_value, "
"original_currency)",
"dotnet/financial/financial_analysis-extended_"
"fundamental_data.md",
),
}
with FUNCTION_INDEX.open(encoding="utf-8", newline="") as handle:
rows = list(csv.DictReader(handle, delimiter="\t"))
by_name = {row["name"]: row for row in rows}
self.assertEqual(set(expected), set(expected) & set(by_name))
for name, (query_name, signature, page) in expected.items():
with self.subTest(name=name):
row = by_name[name]
self.assertEqual("dotnet", row["scope"])
self.assertEqual("financial", row["module"])
self.assertEqual(signature, row["signature"])
self.assertEqual(page, row["page"])
result = run_script(API_LOOKUP, "--name", query_name)
self.assertEqual(0, result.returncode, result.stderr)
self.assertIn(f"## `{signature}`", result.stdout)
self.assertIn(f"{page}#", result.stdout)
parameter_text = signature.partition("(")[2].removesuffix(")")
for parameter in filter(None, parameter_text.split(", ")):
self.assertRegex(
parameter,
r"^[a-z][a-z0-9]*(?:_[a-z0-9]+)*$",
)
parameter_types = []
for line in result.stdout.splitlines():
match = re.fullmatch(
r"\| `[^`]+`\s+\|\s+([^|]+?)\s+\|.*",
line,
)
if match:
parameter_types.append(match.group(1).strip())
self.assertTrue(parameter_types)
self.assertTrue(
all(value == value.casefold() for value in parameter_types)
)
return_types = [
line.removeprefix("返回:").strip()
for line in result.stdout.splitlines()
if line.startswith("返回:")
]
self.assertEqual(1, len(return_types))
self.assertEqual(return_types[0], return_types[0].casefold())
for name in (
"financialItemIn12Month_HK",
"reportType_HK",
"originalToFunctionalCurrency_HK",
):
self.assertIn("_HK", by_name[name]["signature"])
def test_class_index_covers_all_indexed_class_kinds(self):
result = run_script(CLASS_LOOKUP, "--list", "--format", "json")
self.assertEqual(0, result.returncode, result.stderr)
packet = json.loads(result.stdout)
self.assertEqual(indexed_class_count(), packet["count"])
classes = {
(item["scope"], item["qualified_name"]): item
for item in packet["classes"]
}
self.assertIn(("builtin", "TStringList"), classes)
self.assertIn(("module", "tsBackTesting"), classes)
self.assertIn(("module", "opDeltaHedging"), classes)
self.assertIn(("module", "tsLargeAndSmallTurning"), classes)
self.assertIn(("module", "regime_Based"), classes)
self.assertIn(("module", "ml_Clock"), classes)
self.assertIn(("module", "gaussianNB"), classes)
self.assertIn(("module", "decisionTreeClassifier"), classes)
self.assertIn(("module", "mlpClassifier"), classes)
self.assertIn(("module", "svc"), classes)
self.assertIn(("module", "brinson_PerforAttri"), classes)
self.assertIn(("module", "cb_BinaryTree"), classes)
self.assertIn(("module", "ts_ChipDistribution"), classes)
self.assertIn(("module", "tsPositionMeasure"), classes)
self.assertIn(("module", "ts_SameTypeFundEquityPercent"), classes)
self.assertIn(("module", "ts_AIPBackTesting"), classes)
self.assertIn(("dotnet", "tsOptimizer"), classes)
self.assertTrue(classes[("module", "tsBackTesting")]["profile_available"])
self.assertTrue(
classes[("module", "ts_SameTypeFundEquityPercent")][
"profile_available"
]
)
self.assertTrue(
classes[("module", "ts_AIPBackTesting")]["profile_available"]
)
self.assertFalse(classes[("builtin", "TStringList")]["profile_available"])
def test_recovered_recent_update_classes_keep_member_ownership(self):
with FUNCTION_INDEX.open(encoding="utf-8", newline="") as handle:
rows = list(csv.DictReader(handle, delimiter="\t"))
expected_owners = {
"module/derivatives-analysis/option-delta-hedging.md": {
"opDeltaHedging"
},
"module/tinysoft-strategies/market-cap-rotation.md": {
"tsLargeAndSmallTurning"
},
"module/portfolio-optimization/investment-clock.md": {
"regime_Based",
"ml_Clock",
},
}
for page, owners in expected_owners.items():
with self.subTest(page=page):
members = [
row
for row in rows
if row["page"] == page and row["kind"] != "class"
]
self.assertTrue(members)
self.assertTrue(all(row["owner"] in owners for row in members))
flattened_names = {
"GetOptionDelta",
"GetRegularAlpha",
"Anova",
"GetState_Division",
}
self.assertFalse(
any(
row["page"] in expected_owners
and row["name"] in flattened_names
and not row["owner"]
for row in rows
)
)
def test_recovered_domain_classes_and_wrappers_keep_their_boundaries(self):
with FUNCTION_INDEX.open(encoding="utf-8", newline="") as handle:
rows = list(csv.DictReader(handle, delimiter="\t"))
owned = {
"brinson_PerforAttri.GetBenchmark": "brinson_PerforAttri",
"cb_BinaryTree.CBValuation": "cb_BinaryTree",
"ts_ChipDistribution.ChipMeasure": "ts_ChipDistribution",
"tsPositionMeasure.PortfolioOptimize": "tsPositionMeasure",
"ts_SameTypeFundEquityPercent.GetFundArr": (
"ts_SameTypeFundEquityPercent"
),
"ts_AIPBackTesting.CumReturn": "ts_AIPBackTesting",
}
for qualified_name, owner in owned.items():
with self.subTest(qualified_name=qualified_name):
matches = [
row for row in rows if row["qualified_name"] == qualified_name
]
self.assertTrue(matches)
self.assertTrue(all(row["owner"] == owner for row in matches))
wrappers = {
"brinson_perforAttri_demo",
"op_underlying_hv",
"Index_TA_ChipDistribution",
"CB_CCB",
"CB_GetRiskTarget_CCB",
"getstockVal",
}
for name in wrappers:
with self.subTest(name=name):
matches = [row for row in rows if row["name"] == name]
self.assertTrue(matches)
self.assertTrue(all(not row["owner"] for row in matches))
flattened_members = {
"GetBenchmark",
"CBValuation",
"ChipMeasure",
"PortfolioOptimize",
"GetFundArr",
"CumReturn",
}
self.assertFalse(
any(
row["scope"] == "module"
and row["name"] in flattened_members
and row["page"].startswith("module/")
and not row["owner"]
for row in rows
)
)
def test_ts_ops_indexes_runtime_class_without_example_implementation(self):
with FUNCTION_INDEX.open(encoding="utf-8", newline="") as handle:
rows = list(csv.DictReader(handle, delimiter="\t"))
page_rows = [
row for row in rows if row["page"] == "module/ts-ops.md"
]
self.assertIn(
("opsServer", "class", ""),
{
(row["qualified_name"], row["kind"], row["owner"])
for row in page_rows
},
)
expected_members = {
"opsServer.FOpsId",
"opsServer.FAsyncResults",
"opsServer.FLastId",
"opsServer.FDataUrl",
"opsServer.FOPSList",
"opsServer.FOPSName",
"opsServer.FCallBack",
"opsServer.FSubCallBack",
"opsServer.FReturnRuntimeError",
"opsServer.FDebug",
"opsServer.create",
"opsServer.CallBack",
"opsServer.WaitSubscription",
"opsServer.AsyncCall",
"opsServer.DoOnMessage",
}
self.assertTrue(
expected_members
<= {row["qualified_name"] for row in page_rows}
)
self.assertTrue(
all(
row["owner"] == "opsServer"
for row in page_rows
if row["kind"] != "class"
)
)
example_names = {
"myopsserver2",
"hahaha2",
"getmydata",
"my_multiply",
}
self.assertFalse(
any(row["name"] in example_names for row in page_rows)
)
ops_call = [
row
for row in rows
if row["name"] == "opsCall"
and row["page"] == "module/ts-opi.md"
]
self.assertTrue(ops_call)
self.assertTrue(
all(row["kind"] == "function" and not row["owner"] for row in ops_call)
)
def test_recovered_machine_learning_classes_have_core_members(self):
expected = {
"gaussianNB": {"create", "Fit", "Predict", "Predict_proba"},
"decisionTreeClassifier": {
"create",
"Fit",
"Predict",
"Predict_proba",
},
"mlpClassifier": {"create", "Fit", "Predict", "Score"},
"svc": {"create", "Fit", "Predict", "Predict_proba", "Score"},
}
for class_name, required_members in expected.items():
with self.subTest(class_name=class_name):
result = run_script(
CLASS_LOOKUP,
"--class",
class_name,
"--include-members",
"--format",
"json",
)
self.assertEqual(0, result.returncode, result.stderr)
members = {
item["name"] for item in json.loads(result.stdout)["members"]
}
self.assertTrue(required_members <= members)
def test_generic_class_uses_compact_member_view_without_fake_lifecycle(self):
result = run_script(
CLASS_LOOKUP,
"--class",
"TStringList",
"--format",
"json",
)
self.assertEqual(0, result.returncode, result.stderr)
packet = json.loads(result.stdout)
self.assertEqual("resolved", packet["status"])
self.assertEqual("not_profiled", packet["profile_status"])
self.assertIsNone(packet["profile"])
self.assertEqual({"method": 25, "property": 15}, packet["member_summary"])
def test_custom_index_without_profiles_queries_generic_class(self):
fieldnames = [
"name",
"scope",
"module",
"signature",
"page",
"anchor",
"tags",
"summary",
"kind",
"binding",
"visibility",
"owner",
"qualified_name",
]
rows = [
{
"name": "MyClass",
"scope": "project",
"module": "demo",
"signature": "MyClass",
"page": "project/demo.md",
"anchor": "myclass",
"tags": "示例 class",
"summary": "示例类。",
"kind": "class",
"binding": "",
"visibility": "",
"owner": "",
"qualified_name": "MyClass",
},
{
"name": "Run",
"scope": "project",
"module": "demo",
"signature": "Run()",
"page": "project/demo.md",
"anchor": "run",
"tags": "运行",
"summary": "运行示例。",
"kind": "method",
"binding": "instance",
"visibility": "public",
"owner": "MyClass",
"qualified_name": "MyClass.Run",
},
]
with tempfile.TemporaryDirectory() as temp_dir:
data_dir = Path(temp_dir) / "data"
data_dir.mkdir()
tsv_path = data_dir / "function_index.tsv"
with tsv_path.open("w", encoding="utf-8", newline="") as handle:
writer = csv.DictWriter(
handle,
fieldnames=fieldnames,
delimiter="\t",
lineterminator="\n",
)
writer.writeheader()
writer.writerows(rows)
result = run_script(
CLASS_LOOKUP,
"--tsv",
str(tsv_path),
"--class",
"MyClass",
"--include-members",
"--format",
"json",
)
self.assertEqual(0, result.returncode, result.stderr)
packet = json.loads(result.stdout)
self.assertEqual("resolved", packet["status"])
self.assertEqual("not_profiled", packet["profile_status"])
self.assertIsNone(packet["profile"])
self.assertEqual({"method": 1}, packet["member_summary"])
self.assertEqual("MyClass.Run", packet["members"][0]["qualified_name"])
def test_explicit_missing_framework_file_remains_a_data_error(self):
with tempfile.TemporaryDirectory() as temp_dir:
missing = Path(temp_dir) / "missing-framework-index.json"
result = run_script(
CLASS_LOOKUP,
"--profiles",
str(missing),
"--class",
"TStringList",
)
self.assertEqual(1, result.returncode)
self.assertIn("failed to load", result.stderr)
def test_configuration_on_generic_class_requires_curated_profile(self):
result = run_script(
CLASS_LOOKUP,
"--class",
"TStringList",
"--config",
"Mode=1",
"--format",
"json",
)
self.assertEqual(0, result.returncode, result.stderr)
packet = json.loads(result.stdout)
self.assertEqual("not_profiled", packet["profile_status"])
self.assertIn(
"CLASS_NOT_PROFILED",
{item["code"] for item in packet["diagnostics"]},
)
def test_profiled_class_adds_framework_lifecycle(self):
result = run_script(
CLASS_LOOKUP,
"--class",
"tsBackTesting",
"--format",
"json",
)
self.assertEqual(0, result.returncode, result.stderr)
packet = json.loads(result.stdout)
self.assertEqual("resolved", packet["profile_status"])
self.assertEqual("resolved", packet["profile"]["scaffold_status"])
self.assertEqual("incomplete", packet["profile"]["contract_status"])
self.assertEqual(
["construct", "configure", "schedule", "callback", "execute", "inspect"],
[phase["phase"] for phase in packet["profile"]["lifecycle"]],
)
def test_non_backtest_framework_archetypes_have_profiles(self):
expected = {
"tsbf_ReturnAttribution": {
"phases": ["construct", "configure", "schedule", "callback", "inspect"],
"contract_status": "incomplete",
},
"ts_EvaluationOfRotationSkill": {
"phases": ["construct", "execute", "inspect"],
"contract_status": "resolved",
},
"ts_IndustryAllocationOfFund": {
"phases": ["construct", "configure", "execute"],
"contract_status": "resolved",
},
"ts_SameTypeFundEquityPercent": {
"phases": [
"construct",
"configure",
"schedule",
"callback",
"execute",
"inspect",
],
"contract_status": "incomplete",
},
"ts_AIPBackTesting": {
"phases": [
"construct",
"configure",
"schedule",
"callback",
"execute",
"inspect",
],
"contract_status": "incomplete",
},
}
for class_name, contract in expected.items():
with self.subTest(class_name=class_name):
result = run_script(
CLASS_LOOKUP,
"--class",
class_name,
"--format",
"json",
)
self.assertEqual(0, result.returncode, result.stderr)
profile = json.loads(result.stdout)["profile"]
self.assertEqual(
contract["phases"],
[phase["phase"] for phase in profile["lifecycle"]],
)
self.assertEqual(
contract["contract_status"], profile["contract_status"]
)
def test_tsoptimizer_resolves_after_module_migration(self):
unfiltered = run_script(
CLASS_LOOKUP,
"--class",
"tsOptimizer",
"--format",
"json",
)
filtered = run_script(
CLASS_LOOKUP,
"--class",
"tsOptimizer",
"--scope",
"dotnet",
"--module",
"math",
"--format",
"json",
)
self.assertEqual(0, unfiltered.returncode, unfiltered.stderr)
unfiltered_packet = json.loads(unfiltered.stdout)
self.assertEqual("resolved", unfiltered_packet["status"])
self.assertEqual("dotnet", unfiltered_packet["class"]["scope"])
self.assertEqual("math", unfiltered_packet["class"]["module"])
self.assertEqual(0, filtered.returncode, filtered.stderr)
self.assertEqual(
"dotnet",
json.loads(filtered.stdout)["class"]["scope"],
)
self.assertEqual(
"math",
json.loads(filtered.stdout)["class"]["module"],
)
def test_class_check_validates_generic_and_profile_layers(self):
result = run_script(CLASS_LOOKUP, "--check")
self.assertEqual(0, result.returncode, result.stderr)
self.assertIn(f"OK: {indexed_class_count()} classes indexed", result.stdout)
self.assertIn("6 framework profile(s) validated", result.stdout)
def test_framework_module_has_no_independent_cli(self):
source = (SKILL_ROOT / "scripts" / "framework_lookup.py").read_text(
encoding="utf-8"
)
tree = ast.parse(source)
functions = {
node.name for node in tree.body if isinstance(node, ast.FunctionDef)
}
imported_modules = {
alias.name
for node in tree.body
if isinstance(node, ast.Import)
for alias in node.names
}
self.assertNotIn("main", functions)
self.assertNotIn("argparse", imported_modules)
self.assertNotIn('__name__ == "__main__"', source)
def test_ts_backtesting_returns_class_first_scaffold(self):
result = run_script(
CLASS_LOOKUP,
"--class",
"tsBackTesting",
"--format",
"json",
)
self.assertEqual(0, result.returncode, result.stderr)
packet = json.loads(result.stdout)
profile = packet["profile"]
self.assertEqual("resolved", profile["scaffold_status"])
self.assertEqual("incomplete", profile["contract_status"])
self.assertEqual(
["construct", "configure", "schedule", "callback", "execute", "inspect"],
[phase["phase"] for phase in profile["lifecycle"]],
)
self.assertEqual(
["tsBackTesting.GetTradeOrder"],
[hook["api"] for hook in profile["hooks"] if hook["required"]],
)
self.assertEqual(
"CONTRACT_INCOMPLETE",
profile["diagnostics"][0]["code"],
)
def test_candidate_discriminator_does_not_invent_a_mode(self):
result = run_script(
CLASS_LOOKUP,
"--class",
"tsBackTesting",
"--config",
"FGroupType=1",
"--format",
"json",
)
self.assertEqual(0, result.returncode, result.stderr)
packet = json.loads(result.stdout)
profile = packet["profile"]
self.assertEqual({"FGroupType": "1"}, profile["configured_values"])
self.assertEqual("incomplete", profile["contract_status"])
self.assertIn(
"CONTRACT_MODE_UNRESOLVED",
{item["code"] for item in profile["diagnostics"]},
)
def test_unknown_configuration_is_reported(self):
result = run_script(
CLASS_LOOKUP,
"--class",
"tsBackTesting",
"--config",
"notAField=1",
"--format",
"json",
)
self.assertEqual(0, result.returncode, result.stderr)
packet = json.loads(result.stdout)
self.assertIn(
"UNKNOWN_CONFIGURATION",
{item["code"] for item in packet["profile"]["diagnostics"]},
)
def test_unknown_class_uses_query_success_exit_code(self):
result = run_script(
CLASS_LOOKUP,
"--class",
"MissingFramework",
"--format",
"json",
)
self.assertEqual(0, result.returncode, result.stderr)
self.assertEqual("not_found", json.loads(result.stdout)["status"])
def test_check_rejects_stale_evidence(self):
data = json.loads(FRAMEWORK_INDEX.read_text(encoding="utf-8"))
data["frameworks"][0]["hooks"][0]["evidence"] = [
"module/ts-backtesting.md#missing"
]
with tempfile.TemporaryDirectory() as temp_dir:
index_path = Path(temp_dir) / "framework_index.json"
index_path.write_text(json.dumps(data), encoding="utf-8")
result = run_script(
CLASS_LOOKUP,
"--check",
"--profiles",
str(index_path),
)
self.assertEqual(1, result.returncode)
self.assertIn("does not identify an indexed overload", result.stderr)
def test_query_rejects_invalid_profile_data(self):
data = json.loads(FRAMEWORK_INDEX.read_text(encoding="utf-8"))
data["frameworks"][0]["execution"][0]["evidence"] = [
"module/ts-backtesting.md#missing"
]
with tempfile.TemporaryDirectory() as temp_dir:
index_path = Path(temp_dir) / "framework_index.json"
index_path.write_text(json.dumps(data), encoding="utf-8")
result = run_script(
CLASS_LOOKUP,
"--class",
"tsBackTesting",
"--format",
"json",
"--profiles",
str(index_path),
)
self.assertEqual(1, result.returncode)
self.assertEqual("", result.stdout)
self.assertIn("does not identify an indexed overload", result.stderr)
def test_check_rejects_profile_without_indexed_class(self):
data = json.loads(FRAMEWORK_INDEX.read_text(encoding="utf-8"))
data["frameworks"][0]["qualified_name"] = "MissingClass"
with tempfile.TemporaryDirectory() as temp_dir:
index_path = Path(temp_dir) / "framework_index.json"
index_path.write_text(json.dumps(data), encoding="utf-8")
result = run_script(
CLASS_LOOKUP,
"--check",
"--profiles",
str(index_path),
)
self.assertEqual(1, result.returncode)
self.assertIn("is not an indexed class", result.stderr)
def test_ordinary_api_lookup_remains_available(self):
result = run_script(API_LOOKUP, "--name", "tsBackTesting.BackTest")
self.assertEqual(0, result.returncode, result.stderr)
self.assertIn("### `BackTest()`", result.stdout)
self.assertIn("module/ts-backtesting.md#backtest", result.stdout)
if __name__ == "__main__":
unittest.main()