diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index ecb3ac1d..47ef1b77 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -92,3 +92,5 @@ jobs: # the glob above because it needs Go and lives in its own directory. - name: Lint-rule unit suite (tests/lint-rules/test-ux-rules.sh) run: bash tests/lint-rules/test-ux-rules.sh + - name: Lint-rule unit suite (tests/lint-rules/test-batch1-rules.sh) + run: bash tests/lint-rules/test-batch1-rules.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index 228c8a26..1e8ff641 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ Sections dated before 2026-09-19 predate the cycle and stay as they are. ## Unreleased +- process(lint): **ERR001/LOOP001 first field count recorded.** Same run as the UX001/UX002 calibration (mxcli 0.26.0, scratch copy of a 21-module Mendix 11.12 app, 876 microflows): ERR001 8 findings, LOOP001 4, all on the app's own modules, none on marketplace or System, no load errors, 1.7 s for 55 rules. Scored by reading each flagged flow: LOOP001 4 of 4 true (every retrieve sits in the loop body and filters on the iterator); ERR001 3 true (`on error continue` with nothing logged, raised or handled — one silently drops a callee's gate failure) and 5 debatable (the flow returns an error payload or an empty result as its signal, and carries an annotation saying the handling is deliberate, which the rule does not read). No false positives, so the rule text stands; whether an annotation or an explicit error-payload return should count as handling is logged as an open question in `process/lint-backlog.md`'s spirit — decide after the second project. Both rules stay at warning. — MendixMau +- new(lint): **ERR001 ErrorSwallowed and LOOP001 ExpensiveActionInLoop** — Batch 1 of `process/lint-backlog.md`. ERR001 flags a flow where an activity is set to Continue / Custom / Custom-without-rollback and nothing in the flow logs, raises an error event, or calls a handler-named flow (the "Continue and pray" pattern from `learned-microflow-patterns.md`); it is the complement of mxcli's CONV013/CONV014, which only check that handling is set. LOOP001 flags a database retrieve, delete, REST, external or Java call inside a loop body at any depth (CONV011 covers commit-in-loop; this is the rest of the list); association retrieves and sub-flow calls are left alone. Both read `error_handling_type` / `loop_depth` / `retrieve_source` as mxcli ≥ 0.25.0 projects them (values taken from the mxcli source, `sdk/microflows/error_handling.go` and the catalog builder), use `activities_for(…, nested=True)`, ship at warning severity, and emit a `_rule` blindness finding on an older binary or an empty column. Unit-tested under the real Starlark interpreter, `tests/lint-rules/test-batch1-rules.sh` (29 cases incl. the 3000-flow perf run, in CI); the test harness gained `nested=` to mirror the 0.25.0 builtin. First field count still owed before promotion — the PR stays draft until a real project reports one. — MendixMau - fix(lint): **UX001/UX002 field-calibrated on a real model; both had a vocabulary defect the unit suite could not see.** First run of the pair with mxcli 0.26.0 against a scratch copy of a 21-module Mendix 11.12 app (876 microflows, 124 pages, 1.7 s, no load errors): UX001 fired 0 times and UX002's decision leg 0 times across 728 decisions. Catalog probe showed why — the stored microflow-button action type is `Forms$MicroflowAction` (290 rows), not the `Forms$MicroflowClientAction` read from the source, so UX001 legs 2–3 could never fire for a microflow button; and `auto_generate_caption` is 0 on every `ExclusiveSplit`, a bare decision instead carries its condition expression as its caption, so UX002's test could never be true. Fixed: UX001 lists both spellings; UX002 flags a caption that is empty or equals `condition_expression` (39 of 39 custom top-level decisions on that model, while marketplace decisions read "Passwords equal?"). Fixtures now carry the probed action-type name and a new case pins the caption==condition shape (36 cases). Lesson for every rule here: a self-check that only tests "count is non-zero" passes on a wrong vocabulary — the field run is the check. — MendixMau - fix(tests): **`test-stock-hashes.sh` no longer demands a stock hash for a toolkit-authored rule.** Its coverage check walked all of `MXTK_LINT_RULES`, but `mxcli init` only seeds three of those files, so the day UX001/UX002 joined the list every version went red (`v0.17.0 is missing: ux001… ux002…`, 3 FAIL) for a hash that cannot exist. `bin/lib/install-manifest.sh` gains `MXTK_LINT_RULES_STOCK` (the three init-seeded rules); the fixture covers, lists and live-captures against that set and asserts it is a subset of `MXTK_LINT_RULES`. Verified by replaying the fixture's awk against the real list: all three versions cover all three stock rules, and a listed non-stock name still fails. Same disease in `tests/test-lint-delivery.sh`: its fresh-install case asserted a literal 4 installed files; it now derives the count from the manifest lists and checks that no `MXTK_LINT_RULES_NOINSTALL` file landed. — MendixMau - new(lint): **UX001 ConfirmBeforeDestructive and UX002 DecisionsCaptioned.** Two Starlark rules in `lint-rules/`, delivered by the existing install/refresh path: UX001 flags a button that deletes (direct delete action, which has no confirmation setting) or calls a flow that deletes or is named Approve/Reject/Submit/Archive/… without "Ask confirmation"; UX002 flags decisions left on their auto-generated caption and flows of 8+ activities with no annotation or documentation. Both need mxcli ≥ 0.25.0 (widget `action_type`/`has_confirmation`, activity `caption`) and emit a `_rule` blindness finding on anything older. Vocabulary read from the mxcli source, not from the bundled API guide; first field calibration still owed (no mxcli binary reachable in the authoring session) — the README row says so. Logic is unit-tested under the real Starlark interpreter: `tests/lint-rules/test-ux-rules.sh` (35 cases incl. a synthetic 3000-flow perf run, wired into CI) — the suite caught two defects before they shipped: start/end events inflated UX002's activity count, and its version probe only fired on flows that had a decision. Prompted by a CLAUDE.md-only enforcement of the same house rules (confirm before approve, feedback on close/commit) that no session could be held to. — MendixMau diff --git a/bin/lib/install-manifest.sh b/bin/lib/install-manifest.sh index 17b14b43..da257d86 100644 --- a/bin/lib/install-manifest.sh +++ b/bin/lib/install-manifest.sh @@ -151,7 +151,7 @@ MXTK_PROJECT_BIN_NOINSTALL="report-disposition-check.sh" # gate-check.sh runs i # project that re-runs init loses these fixes and its lint gate goes quietly blind. Re-running # sync-project.sh restores them; lint-rules/STOCK-HASHES.txt is what lets it tell a # reverted-to-stock file (safe to restore) from one a project deliberately tuned (hands off). -MXTK_LINT_RULES="conv010_act_microflow_content.star data_change_microflows.star entity_business_key.star ux001_confirm_before_destructive.star ux002_decisions_captioned.star" +MXTK_LINT_RULES="conv010_act_microflow_content.star data_change_microflows.star entity_business_key.star ux001_confirm_before_destructive.star ux002_decisions_captioned.star err001_error_swallowed.star loop001_expensive_action_in_loop.star" # MXTK_LINT_RULES_STOCK — the subset of MXTK_LINT_RULES that `mxcli init` ALSO seeds, i.e. the # only files lint-rules/STOCK-HASHES.txt can ever hold a hash for. The ux001/ux002 rules are # toolkit-authored: init never writes them, so no stock hash can exist and a coverage check diff --git a/lint-rules/README.md b/lint-rules/README.md index 48ce28e9..df0111a6 100644 --- a/lint-rules/README.md +++ b/lint-rules/README.md @@ -12,6 +12,12 @@ and configure the marked constants — **after** running `mxcli init`, never bef | `entity_business_key.star` | ARCH003 — persistent entities with no UNIQUE NOT NULL business key | no | | `ux001_confirm_before_destructive.star` | UX001 — a button that deletes, approves, rejects, submits or archives (direct delete action, or a flow that deletes / is named so) without "Ask confirmation" | `DESTRUCTIVE_NAME_PATTERN` if your verbs differ | | `ux002_decisions_captioned.star` | UX002 — decisions (`ExclusiveSplit`) with an auto-generated caption instead of the question they answer; flows of 8+ activities with neither an annotation nor a documentation text | `ANNOTATE_FROM` | +| `err001_error_swallowed.star` | ERR001 — an activity set to Continue / Custom / Custom-without-rollback in a flow that never logs, raises an error event, or calls a flow named like a handler (`SUB_LogError`, `ERR_…`): the error is swallowed | `HANDLER_NAME_PATTERN` if your handler flows are named differently | +| `loop001_expensive_action_in_loop.star` | LOOP001 — a database retrieve, delete, REST call, external call or Java action inside a loop body (any depth): one round trip per row | `EXPENSIVE` if a call type is cheap in your setup | + +`err001` and `loop001` first field count (2026-10-12, mxcli 0.26.0, 21-module Mendix 11.12 model, +876 microflows): ERR001 8 (3 true, 5 debatable — flows that return an error payload instead of +logging), LOOP001 4 (4 true). Warning severity until a second project reports. `data_change_microflows` and `entity_business_key` are **repaired copies of rules `mxcli init` already seeds**, not new ones. Both shipped comparing `entity.entity_type` to `"PERSISTENT"` diff --git a/lint-rules/err001_error_swallowed.star b/lint-rules/err001_error_swallowed.star new file mode 100644 index 00000000..5f63b31f --- /dev/null +++ b/lint-rules/err001_error_swallowed.star @@ -0,0 +1,143 @@ +# mxtk-lint-rule: err001_error_swallowed +# Shipped by mxcli-project-toolkit. Needs no per-project configuration; sync-project.sh +# refreshes it while it is unedited and reports (never overwrites) a locally edited copy. +# See lint-rules/README.md. +# --- end mxtk-lint-rule header --- +# ERR001: An error that is caught must leave a trace +# +# "Continue" on an activity means: if this fails, carry on as if it worked. "Custom" and +# "Custom without rollback" route the error to a handler branch. All three are fine when the +# flow then logs the error, raises it again, or hands it to a flow that does. All three are a +# silent swallow when nothing in the flow does any of that: the user sees a half-done action, +# the log shows nothing, and the next session's first hour goes to finding out which of forty +# flows ate the exception (the "Continue and pray" pattern, learned-microflow-patterns.md). +# +# What counts as a trace, anywhere in the SAME flow (loop bodies included): +# 1. a Log message activity (action_type LogMessageAction / log_level set) +# 2. an error end event (activity_type ErrorEvent — raise/re-throw) +# 3. a call to a flow whose name says it handles or logs errors +# (SUB_LogError, ERR_Handle, ..._HandleError — HANDLER_NAME_PATTERN, CamelCase-strict) +# +# Deliberately NOT flagged: Rollback and Abort (the platform surfaces those itself), the +# default "" (no handling set, which Mendix treats as Rollback), flows in System/Administration, +# and RULE-type flows. One finding per flow, naming the swallowing activities. +# +# What CONV013/CONV014 (mxcli stock) do NOT cover: they check that an external call HAS error +# handling. This rule checks that the handling leads somewhere. Different question. +# +# REQUIRES mxcli >= 0.25.0: error_handling_type / log_level / nested activities were added to +# the Starlark activity projection there (mendixlabs/mxcli#1266). On an older binary the rule +# does not go quiet: it emits a `_rule` finding saying it saw nothing. +# +# Vocabulary read from the mxcli source (sdk/microflows/error_handling.go: the action's stored +# ErrorHandlingType verbatim — "Rollback", "Continue", "Custom", "CustomWithoutRollBack" with +# a capital B, "Abort"; builder_microflows.go: activity_type "ErrorEvent", action_type with +# the "Microflows$" prefix stripped). Probe it on your model before trusting it: +# sqlite3 .mxcli/catalog.db "SELECT DISTINCT ErrorHandlingType FROM activities ORDER BY 1;" +# sqlite3 .mxcli/catalog.db "SELECT DISTINCT ActivityType, ActionType FROM activities WHERE LogLevel != '' OR ActivityType='ErrorEvent';" + +RULE_ID = "ERR001" +RULE_NAME = "ErrorSwallowed" +DESCRIPTION = "An activity that continues or custom-handles on error must be in a flow that logs, raises, or calls an error handler" +CATEGORY = "quality" +SEVERITY = "warning" + +REQUIRES = ["full"] + +SKIP_MODULES = ("System", "Administration") +FLOW_TYPES = ("MICROFLOW", "NANOFLOW") + +# Stored error handling values that catch the error (activities.ErrorHandlingType). +SWALLOWING = ("Continue", "Custom", "CustomWithoutRollBack") + +# Evidence the error went somewhere. +LOG_ACTION = "LogMessageAction" +RAISE_EVENT = "ErrorEvent" +HANDLER_NAME_PATTERN = "(^|_)(Log|Error|Err|Handle)([A-Z0-9_]|$)" + + +def _handler_called(qname): + """True when this flow calls a flow whose name announces error handling or logging.""" + for ref in refs_from(qname): + if ref.target_type not in ("MICROFLOW", "NANOFLOW"): + continue + if ref.target_name == qname: + continue + if matches(ref.target_name.split(".")[-1], HANDLER_NAME_PATTERN): + return True + return False + + +def check(): + violations = [] + flows_seen = 0 + activities_seen = 0 + handling_seen = 0 + api_missing = False + + for mf in microflows(): + if mf.module_name in SKIP_MODULES: + continue + if mf.microflow_type not in FLOW_TYPES: + continue + flows_seen += 1 + + swallowing = [] + traced = False + for act in activities_for(mf.qualified_name, nested=True): + activities_seen += 1 + # getattr, not act.error_handling_type: on a pre-0.25.0 binary the attribute does + # not exist and a bare access aborts the rule as a crash, which the gate would + # baseline. A missing attribute is reported as blindness instead. + eh = getattr(act, "error_handling_type", None) + if eh == None: + api_missing = True + break + if eh: + handling_seen += 1 + if eh in SWALLOWING: + swallowing.append(act.caption or act.name or act.activity_type) + if act.activity_type == RAISE_EVENT: + traced = True + elif act.action_type == LOG_ACTION or getattr(act, "log_level", ""): + traced = True + if api_missing: + break + if not swallowing or traced: + continue + if _handler_called(mf.qualified_name): + continue + + violations.append(violation( + message="{} catches errors on {} activit{} ({}) but never logs, raises, or calls an error handler: the error is swallowed.".format( + mf.qualified_name, len(swallowing), "y" if len(swallowing) == 1 else "ies", + ", ".join(["'" + s + "'" for s in swallowing[:4]]) + (", ..." if len(swallowing) > 4 else ""), + ), + location=location(module=mf.module_name, document_type="Microflow", document_name=mf.qualified_name), + suggestion="In the handler branch (or right after a 'Continue' activity) add a Log message at ERROR level with $latestError/Message, or raise the error again; or call the project's SUB_LogError. If the error truly does not matter, say so in an annotation next to the activity.", + )) + + # SELF-CHECKS. A rule that cannot see must say so, never report a clean pass. + if api_missing: + violations.append(violation( + message="ERR001 cannot run on this mxcli: activities have no error_handling_type (added in mxcli 0.25.0). This rule inspected NOTHING.", + location=location(module="_rule", document_type="Microflow", document_name="ERR001"), + suggestion="Upgrade mxcli to 0.25.0 or later, or remove this rule deliberately rather than leaving it inert.", + )) + elif flows_seen > 0 and activities_seen == 0: + violations.append(violation( + message="ERR001 saw {} flows and not one activity. activities_for() needs the FULL catalog; this rule inspected NOTHING.".format(flows_seen), + location=location(module="_rule", document_type="Microflow", document_name="ERR001"), + suggestion="Run `REFRESH CATALOG FULL` (mxcli lint should request it itself from 0.24 on) and lint again.", + )) + elif activities_seen >= 20 and handling_seen == 0: + # Every real action activity stores a handling value (Rollback at minimum). Twenty + # activities without a single one means the column is empty: a catalog from before + # the field existed, not a project that never set it. + violations.append(violation( + message="ERR001 saw {} activities and none carries an error_handling_type, so no handling was inspected. The catalog is probably from an older schema.".format(activities_seen), + location=location(module="_rule", document_type="Microflow", document_name="ERR001"), + suggestion="Run `REFRESH CATALOG FULL` on mxcli >= 0.25.0 so the activities table carries ErrorHandlingType, then lint again.", + )) + + return violations diff --git a/lint-rules/loop001_expensive_action_in_loop.star b/lint-rules/loop001_expensive_action_in_loop.star new file mode 100644 index 00000000..a5563eb9 --- /dev/null +++ b/lint-rules/loop001_expensive_action_in_loop.star @@ -0,0 +1,127 @@ +# mxtk-lint-rule: loop001_expensive_action_in_loop +# Shipped by mxcli-project-toolkit. Needs no per-project configuration; sync-project.sh +# refreshes it while it is unedited and reports (never overwrites) a locally edited copy. +# See lint-rules/README.md. +# --- end mxtk-lint-rule header --- +# LOOP001: A database, REST, or Java call inside a loop runs once per row +# +# A retrieve from database, a delete, a REST call, an external (web service / OData) call +# or a Java action in a loop body is executed once per iteration: N rows, N round trips. +# The fix is almost always the same shape — retrieve the list once before the loop and +# FIND/FILTER in memory, collect the objects and delete the list after the loop, or batch +# the calls (microflow-loop-antipatterns.md; real: a 1,200-row import at 1,200 database +# retrieves, 40 s, rewritten to one retrieve + FIND, under a second). +# +# CONV011 (mxcli stock) flags a COMMIT in a loop. This rule is the rest of that list. It +# looks INSIDE loops at any depth (activities_for(..., nested=True)), so a retrieve two +# loops down is seen. One finding per flow, naming the offending activities. +# +# Deliberately NOT flagged: retrieve over association (in-memory, already loaded), create, +# change, list operations, aggregates, calls to other microflows (the called flow is linted +# on its own), and anything in System/Administration. A retrieve whose source is unknown +# ("") is also left alone rather than guessed at. +# +# REQUIRES mxcli >= 0.25.0: nested activities with loop_depth, and retrieve_source, were +# added to the Starlark activity projection there (mendixlabs/mxcli#1266). On an older +# binary every activity reports loop_depth 0 and the loop bodies are absent, so the rule +# cannot see — it says so with a `_rule` finding instead of passing. +# +# Vocabulary read from the mxcli source (builder_microflows.go: action_type is the stored +# Mendix action type with "Microflows$" stripped; RetrieveSource is "database" or +# "association"). Probe it on your model before trusting it: +# sqlite3 .mxcli/catalog.db "SELECT DISTINCT ActionType, RetrieveSource FROM activities WHERE LoopDepth > 0;" + +RULE_ID = "LOOP001" +RULE_NAME = "ExpensiveActionInLoop" +DESCRIPTION = "Database retrieve, delete, REST, external or Java call inside a loop body" +CATEGORY = "performance" +SEVERITY = "warning" + +REQUIRES = ["full"] + +SKIP_MODULES = ("System", "Administration") +FLOW_TYPES = ("MICROFLOW", "NANOFLOW") + +# action_type values that cost a round trip per iteration. +EXPENSIVE = ("DeleteObjectAction", "RestCallAction", "CallExternalAction", "JavaActionCallAction") +RETRIEVE = "RetrieveAction" +RETRIEVE_DATABASE = "database" + +LOOP_TYPE = "LoopedActivity" + + +def _label(act): + kind = act.action_type + if kind == RETRIEVE: + kind = "database retrieve" + elif kind == "DeleteObjectAction": + kind = "delete" + elif kind == "RestCallAction": + kind = "REST call" + elif kind == "CallExternalAction": + kind = "external call" + elif kind == "JavaActionCallAction": + kind = "Java action" + return "'{}' ({})".format(act.caption or act.name or act.activity_type, kind) + + +def check(): + violations = [] + loops_seen = 0 + body_rows_seen = 0 + api_missing = False + + for mf in microflows(): + if mf.module_name in SKIP_MODULES: + continue + if mf.microflow_type not in FLOW_TYPES: + continue + + offenders = [] + for act in activities_for(mf.qualified_name, nested=True): + # getattr, not act.loop_depth: on a pre-0.25.0 binary the attribute does not + # exist and a bare access aborts the rule as a crash, which the gate would + # baseline. A missing attribute is reported as blindness instead. + depth = getattr(act, "loop_depth", None) + if depth == None: + api_missing = True + break + if act.activity_type == LOOP_TYPE: + loops_seen += 1 + continue + if depth < 1: + continue + body_rows_seen += 1 + if act.action_type in EXPENSIVE: + offenders.append(_label(act)) + elif act.action_type == RETRIEVE and getattr(act, "retrieve_source", "") == RETRIEVE_DATABASE: + offenders.append(_label(act)) + if api_missing: + break + if not offenders: + continue + + violations.append(violation( + message="{} runs {} per-row call{} inside a loop: {}.".format( + mf.qualified_name, len(offenders), "" if len(offenders) == 1 else "s", + ", ".join(offenders[:4]) + (", ..." if len(offenders) > 4 else ""), + ), + location=location(module=mf.module_name, document_type="Microflow", document_name=mf.qualified_name), + suggestion="Retrieve the list once before the loop and use FIND/FILTER on it; collect objects to delete into a list and delete it after the loop; batch REST/Java calls or move them to a sub-flow that takes the whole list.", + )) + + # SELF-CHECKS. A rule that cannot see must say so, never report a clean pass. + if api_missing: + violations.append(violation( + message="LOOP001 cannot run on this mxcli: activities have no loop_depth (added in mxcli 0.25.0). This rule inspected NOTHING.", + location=location(module="_rule", document_type="Microflow", document_name="LOOP001"), + suggestion="Upgrade mxcli to 0.25.0 or later, or remove this rule deliberately rather than leaving it inert.", + )) + elif loops_seen > 0 and body_rows_seen == 0: + violations.append(violation( + message="LOOP001 saw {} loop(s) and not one activity inside any of them, so no loop body was inspected. The catalog is probably from before loop bodies were indexed.".format(loops_seen), + location=location(module="_rule", document_type="Microflow", document_name="LOOP001"), + suggestion="Run `REFRESH CATALOG FULL` on mxcli >= 0.25.0 so the activities table carries loop bodies (LoopDepth > 0), then lint again.", + )) + + return violations diff --git a/tests/lint-rules/fixtures/err001-a-continue-nolog.json b/tests/lint-rules/fixtures/err001-a-continue-nolog.json new file mode 100644 index 00000000..81b4a547 --- /dev/null +++ b/tests/lint-rules/fixtures/err001-a-continue-nolog.json @@ -0,0 +1,36 @@ +{ + "microflows": [ + { + "id": "m-Sales.ACT_Order_Delete", + "name": "ACT_Order_Delete", + "qualified_name": "Sales.ACT_Order_Delete", + "module_name": "Sales", + "microflow_type": "MICROFLOW", + "description": "" + } + ], + "activities": { + "Sales.ACT_Order_Delete": [ + { + "id": "a1", + "name": "a1", + "caption": "Delete order", + "activity_type": "ActionActivity", + "action_type": "DeleteObjectAction", + "error_handling_type": "Continue", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a2", + "name": "a2", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "CommitAction", + "error_handling_type": "Rollback", + "loop_depth": 0, + "auto_generate_caption": false + } + ] + } +} diff --git a/tests/lint-rules/fixtures/err001-b-continue-log.json b/tests/lint-rules/fixtures/err001-b-continue-log.json new file mode 100644 index 00000000..3d877f82 --- /dev/null +++ b/tests/lint-rules/fixtures/err001-b-continue-log.json @@ -0,0 +1,37 @@ +{ + "microflows": [ + { + "id": "m-Sales.ACT_Order_Delete", + "name": "ACT_Order_Delete", + "qualified_name": "Sales.ACT_Order_Delete", + "module_name": "Sales", + "microflow_type": "MICROFLOW", + "description": "" + } + ], + "activities": { + "Sales.ACT_Order_Delete": [ + { + "id": "a1", + "name": "a1", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "DeleteObjectAction", + "error_handling_type": "Continue", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a2", + "name": "a2", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "LogMessageAction", + "error_handling_type": "Rollback", + "loop_depth": 0, + "auto_generate_caption": false, + "log_level": "Error" + } + ] + } +} diff --git a/tests/lint-rules/fixtures/err001-c-custom-raise.json b/tests/lint-rules/fixtures/err001-c-custom-raise.json new file mode 100644 index 00000000..60bdf4ef --- /dev/null +++ b/tests/lint-rules/fixtures/err001-c-custom-raise.json @@ -0,0 +1,36 @@ +{ + "microflows": [ + { + "id": "m-Sales.ACT_Sync", + "name": "ACT_Sync", + "qualified_name": "Sales.ACT_Sync", + "module_name": "Sales", + "microflow_type": "MICROFLOW", + "description": "" + } + ], + "activities": { + "Sales.ACT_Sync": [ + { + "id": "a1", + "name": "a1", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "RestCallAction", + "error_handling_type": "Custom", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a2", + "name": "a2", + "caption": "Step", + "activity_type": "ErrorEvent", + "action_type": "", + "error_handling_type": "", + "loop_depth": 0, + "auto_generate_caption": false + } + ] + } +} diff --git a/tests/lint-rules/fixtures/err001-d-handler-call.json b/tests/lint-rules/fixtures/err001-d-handler-call.json new file mode 100644 index 00000000..7c387030 --- /dev/null +++ b/tests/lint-rules/fixtures/err001-d-handler-call.json @@ -0,0 +1,69 @@ +{ + "microflows": [ + { + "id": "m-Sales.ACT_Sync", + "name": "ACT_Sync", + "qualified_name": "Sales.ACT_Sync", + "module_name": "Sales", + "microflow_type": "MICROFLOW", + "description": "" + }, + { + "id": "m-Sales.SUB_LogError", + "name": "SUB_LogError", + "qualified_name": "Sales.SUB_LogError", + "module_name": "Sales", + "microflow_type": "MICROFLOW", + "description": "" + } + ], + "activities": { + "Sales.ACT_Sync": [ + { + "id": "a1", + "name": "a1", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "RestCallAction", + "error_handling_type": "CustomWithoutRollBack", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a2", + "name": "a2", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "MicroflowCallAction", + "error_handling_type": "Rollback", + "loop_depth": 0, + "auto_generate_caption": false + } + ], + "Sales.SUB_LogError": [ + { + "id": "a1", + "name": "a1", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "LogMessageAction", + "error_handling_type": "Rollback", + "loop_depth": 0, + "auto_generate_caption": false, + "log_level": "Error" + } + ] + }, + "refs_from": { + "Sales.ACT_Sync": [ + { + "source_type": "MICROFLOW", + "source_name": "Sales.ACT_Sync", + "target_type": "MICROFLOW", + "target_name": "Sales.SUB_LogError", + "ref_kind": "CALL", + "module_name": "Sales" + } + ] + } +} diff --git a/tests/lint-rules/fixtures/err001-e-rollback-abort.json b/tests/lint-rules/fixtures/err001-e-rollback-abort.json new file mode 100644 index 00000000..33b9da23 --- /dev/null +++ b/tests/lint-rules/fixtures/err001-e-rollback-abort.json @@ -0,0 +1,46 @@ +{ + "microflows": [ + { + "id": "m-Sales.ACT_Sync", + "name": "ACT_Sync", + "qualified_name": "Sales.ACT_Sync", + "module_name": "Sales", + "microflow_type": "MICROFLOW", + "description": "" + } + ], + "activities": { + "Sales.ACT_Sync": [ + { + "id": "a1", + "name": "a1", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "RestCallAction", + "error_handling_type": "Rollback", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a2", + "name": "a2", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "CommitAction", + "error_handling_type": "Abort", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a3", + "name": "a3", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "CreateObjectAction", + "error_handling_type": "", + "loop_depth": 0, + "auto_generate_caption": false + } + ] + } +} diff --git a/tests/lint-rules/fixtures/err001-f-system.json b/tests/lint-rules/fixtures/err001-f-system.json new file mode 100644 index 00000000..b44a1503 --- /dev/null +++ b/tests/lint-rules/fixtures/err001-f-system.json @@ -0,0 +1,46 @@ +{ + "microflows": [ + { + "id": "m-System.ACT_Cleanup", + "name": "ACT_Cleanup", + "qualified_name": "System.ACT_Cleanup", + "module_name": "System", + "microflow_type": "MICROFLOW", + "description": "" + }, + { + "id": "m-Administration.ACT_Reset", + "name": "ACT_Reset", + "qualified_name": "Administration.ACT_Reset", + "module_name": "Administration", + "microflow_type": "MICROFLOW", + "description": "" + } + ], + "activities": { + "System.ACT_Cleanup": [ + { + "id": "a1", + "name": "a1", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "DeleteObjectAction", + "error_handling_type": "Continue", + "loop_depth": 0, + "auto_generate_caption": false + } + ], + "Administration.ACT_Reset": [ + { + "id": "a1", + "name": "a1", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "DeleteObjectAction", + "error_handling_type": "Continue", + "loop_depth": 0, + "auto_generate_caption": false + } + ] + } +} diff --git a/tests/lint-rules/fixtures/err001-g-rule-type.json b/tests/lint-rules/fixtures/err001-g-rule-type.json new file mode 100644 index 00000000..14459c03 --- /dev/null +++ b/tests/lint-rules/fixtures/err001-g-rule-type.json @@ -0,0 +1,26 @@ +{ + "microflows": [ + { + "id": "m-Sales.RULE_CanShip", + "name": "RULE_CanShip", + "qualified_name": "Sales.RULE_CanShip", + "module_name": "Sales", + "microflow_type": "RULE", + "description": "" + } + ], + "activities": { + "Sales.RULE_CanShip": [ + { + "id": "a1", + "name": "a1", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "RetrieveAction", + "error_handling_type": "Continue", + "loop_depth": 0, + "auto_generate_caption": false + } + ] + } +} diff --git a/tests/lint-rules/fixtures/err001-i-no-activities.json b/tests/lint-rules/fixtures/err001-i-no-activities.json new file mode 100644 index 00000000..d595c780 --- /dev/null +++ b/tests/lint-rules/fixtures/err001-i-no-activities.json @@ -0,0 +1,21 @@ +{ + "microflows": [ + { + "id": "m-Sales.ACT_One", + "name": "ACT_One", + "qualified_name": "Sales.ACT_One", + "module_name": "Sales", + "microflow_type": "MICROFLOW", + "description": "" + }, + { + "id": "m-Sales.ACT_Two", + "name": "ACT_Two", + "qualified_name": "Sales.ACT_Two", + "module_name": "Sales", + "microflow_type": "MICROFLOW", + "description": "" + } + ], + "activities": {} +} diff --git a/tests/lint-rules/fixtures/err001-j-twenty-empty.json b/tests/lint-rules/fixtures/err001-j-twenty-empty.json new file mode 100644 index 00000000..1acf5359 --- /dev/null +++ b/tests/lint-rules/fixtures/err001-j-twenty-empty.json @@ -0,0 +1,216 @@ +{ + "microflows": [ + { + "id": "m-Sales.ACT_Big", + "name": "ACT_Big", + "qualified_name": "Sales.ACT_Big", + "module_name": "Sales", + "microflow_type": "MICROFLOW", + "description": "" + } + ], + "activities": { + "Sales.ACT_Big": [ + { + "id": "a0", + "name": "a0", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "ChangeObjectAction", + "error_handling_type": "", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a1", + "name": "a1", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "ChangeObjectAction", + "error_handling_type": "", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a2", + "name": "a2", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "ChangeObjectAction", + "error_handling_type": "", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a3", + "name": "a3", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "ChangeObjectAction", + "error_handling_type": "", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a4", + "name": "a4", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "ChangeObjectAction", + "error_handling_type": "", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a5", + "name": "a5", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "ChangeObjectAction", + "error_handling_type": "", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a6", + "name": "a6", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "ChangeObjectAction", + "error_handling_type": "", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a7", + "name": "a7", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "ChangeObjectAction", + "error_handling_type": "", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a8", + "name": "a8", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "ChangeObjectAction", + "error_handling_type": "", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a9", + "name": "a9", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "ChangeObjectAction", + "error_handling_type": "", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a10", + "name": "a10", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "ChangeObjectAction", + "error_handling_type": "", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a11", + "name": "a11", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "ChangeObjectAction", + "error_handling_type": "", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a12", + "name": "a12", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "ChangeObjectAction", + "error_handling_type": "", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a13", + "name": "a13", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "ChangeObjectAction", + "error_handling_type": "", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a14", + "name": "a14", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "ChangeObjectAction", + "error_handling_type": "", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a15", + "name": "a15", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "ChangeObjectAction", + "error_handling_type": "", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a16", + "name": "a16", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "ChangeObjectAction", + "error_handling_type": "", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a17", + "name": "a17", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "ChangeObjectAction", + "error_handling_type": "", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a18", + "name": "a18", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "ChangeObjectAction", + "error_handling_type": "", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a19", + "name": "a19", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "ChangeObjectAction", + "error_handling_type": "", + "loop_depth": 0, + "auto_generate_caption": false + } + ] + } +} diff --git a/tests/lint-rules/fixtures/err001-k-nested.json b/tests/lint-rules/fixtures/err001-k-nested.json new file mode 100644 index 00000000..75328c6f --- /dev/null +++ b/tests/lint-rules/fixtures/err001-k-nested.json @@ -0,0 +1,80 @@ +{ + "microflows": [ + { + "id": "m-Sales.ACT_LoopSwallow", + "name": "ACT_LoopSwallow", + "qualified_name": "Sales.ACT_LoopSwallow", + "module_name": "Sales", + "microflow_type": "MICROFLOW", + "description": "" + }, + { + "id": "m-Sales.ACT_LoopLogged", + "name": "ACT_LoopLogged", + "qualified_name": "Sales.ACT_LoopLogged", + "module_name": "Sales", + "microflow_type": "MICROFLOW", + "description": "" + } + ], + "activities": { + "Sales.ACT_LoopSwallow": [ + { + "id": "a1", + "name": "a1", + "caption": "Step", + "activity_type": "LoopedActivity", + "action_type": "", + "error_handling_type": "Rollback", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a2", + "name": "a2", + "caption": "Push row", + "activity_type": "ActionActivity", + "action_type": "RestCallAction", + "error_handling_type": "Continue", + "loop_depth": 1, + "auto_generate_caption": false, + "parent_loop_id": "a1" + } + ], + "Sales.ACT_LoopLogged": [ + { + "id": "a1", + "name": "a1", + "caption": "Step", + "activity_type": "LoopedActivity", + "action_type": "", + "error_handling_type": "Rollback", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a2", + "name": "a2", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "RestCallAction", + "error_handling_type": "Continue", + "loop_depth": 1, + "auto_generate_caption": false, + "parent_loop_id": "a1" + }, + { + "id": "a3", + "name": "a3", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "LogMessageAction", + "error_handling_type": "Rollback", + "loop_depth": 1, + "auto_generate_caption": false, + "parent_loop_id": "a1", + "log_level": "Warning" + } + ] + } +} diff --git a/tests/lint-rules/fixtures/err001-l-two-swallowing.json b/tests/lint-rules/fixtures/err001-l-two-swallowing.json new file mode 100644 index 00000000..23119878 --- /dev/null +++ b/tests/lint-rules/fixtures/err001-l-two-swallowing.json @@ -0,0 +1,46 @@ +{ + "microflows": [ + { + "id": "m-Sales.NF_Save", + "name": "NF_Save", + "qualified_name": "Sales.NF_Save", + "module_name": "Sales", + "microflow_type": "NANOFLOW", + "description": "" + } + ], + "activities": { + "Sales.NF_Save": [ + { + "id": "a1", + "name": "a1", + "caption": "Commit", + "activity_type": "ActionActivity", + "action_type": "CommitAction", + "error_handling_type": "Continue", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a2", + "name": "a2", + "caption": "Call sync", + "activity_type": "ActionActivity", + "action_type": "MicroflowCallAction", + "error_handling_type": "CustomWithoutRollBack", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a3", + "name": "a3", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "ShowPageAction", + "error_handling_type": "Rollback", + "loop_depth": 0, + "auto_generate_caption": false + } + ] + } +} diff --git a/tests/lint-rules/fixtures/err001-m-handler-name-mismatch.json b/tests/lint-rules/fixtures/err001-m-handler-name-mismatch.json new file mode 100644 index 00000000..70c9117f --- /dev/null +++ b/tests/lint-rules/fixtures/err001-m-handler-name-mismatch.json @@ -0,0 +1,66 @@ +{ + "microflows": [ + { + "id": "m-Sales.ACT_Sync", + "name": "ACT_Sync", + "qualified_name": "Sales.ACT_Sync", + "module_name": "Sales", + "microflow_type": "MICROFLOW", + "description": "" + }, + { + "id": "m-Sales.SUB_Logistics", + "name": "SUB_Logistics", + "qualified_name": "Sales.SUB_Logistics", + "module_name": "Sales", + "microflow_type": "MICROFLOW", + "description": "" + } + ], + "activities": { + "Sales.ACT_Sync": [ + { + "id": "a1", + "name": "a1", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "RestCallAction", + "error_handling_type": "Continue", + "loop_depth": 0, + "auto_generate_caption": false + } + ], + "Sales.SUB_Logistics": [ + { + "id": "a1", + "name": "a1", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "ChangeObjectAction", + "error_handling_type": "Rollback", + "loop_depth": 0, + "auto_generate_caption": false + } + ] + }, + "refs_from": { + "Sales.ACT_Sync": [ + { + "source_type": "MICROFLOW", + "source_name": "Sales.ACT_Sync", + "target_type": "MICROFLOW", + "target_name": "Sales.SUB_Logistics", + "ref_kind": "CALL", + "module_name": "Sales" + }, + { + "source_type": "MICROFLOW", + "source_name": "Sales.ACT_Sync", + "target_type": "ENTITY", + "target_name": "Sales.Order", + "ref_kind": "CALL", + "module_name": "Sales" + } + ] + } +} diff --git a/tests/lint-rules/fixtures/err001-n-self-ref-handler.json b/tests/lint-rules/fixtures/err001-n-self-ref-handler.json new file mode 100644 index 00000000..9ae5d25e --- /dev/null +++ b/tests/lint-rules/fixtures/err001-n-self-ref-handler.json @@ -0,0 +1,38 @@ +{ + "microflows": [ + { + "id": "m-Sales.SUB_HandleError", + "name": "SUB_HandleError", + "qualified_name": "Sales.SUB_HandleError", + "module_name": "Sales", + "microflow_type": "MICROFLOW", + "description": "" + } + ], + "activities": { + "Sales.SUB_HandleError": [ + { + "id": "a1", + "name": "a1", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "CommitAction", + "error_handling_type": "Continue", + "loop_depth": 0, + "auto_generate_caption": false + } + ] + }, + "refs_from": { + "Sales.SUB_HandleError": [ + { + "source_type": "MICROFLOW", + "source_name": "Sales.SUB_HandleError", + "target_type": "MICROFLOW", + "target_name": "Sales.SUB_HandleError", + "ref_kind": "CALL", + "module_name": "Sales" + } + ] + } +} diff --git a/tests/lint-rules/fixtures/loop001-a-db-retrieve.json b/tests/lint-rules/fixtures/loop001-a-db-retrieve.json new file mode 100644 index 00000000..884b6467 --- /dev/null +++ b/tests/lint-rules/fixtures/loop001-a-db-retrieve.json @@ -0,0 +1,38 @@ +{ + "microflows": [ + { + "id": "m-Sales.ACT_Import", + "name": "ACT_Import", + "qualified_name": "Sales.ACT_Import", + "module_name": "Sales", + "microflow_type": "MICROFLOW", + "description": "" + } + ], + "activities": { + "Sales.ACT_Import": [ + { + "id": "a1", + "name": "a1", + "caption": "Loop", + "activity_type": "LoopedActivity", + "action_type": "", + "error_handling_type": "Rollback", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a2", + "name": "a2", + "caption": "Find customer", + "activity_type": "ActionActivity", + "action_type": "RetrieveAction", + "error_handling_type": "", + "loop_depth": 1, + "auto_generate_caption": false, + "parent_loop_id": "a1", + "retrieve_source": "database" + } + ] + } +} diff --git a/tests/lint-rules/fixtures/loop001-b-assoc-retrieve.json b/tests/lint-rules/fixtures/loop001-b-assoc-retrieve.json new file mode 100644 index 00000000..0404e448 --- /dev/null +++ b/tests/lint-rules/fixtures/loop001-b-assoc-retrieve.json @@ -0,0 +1,38 @@ +{ + "microflows": [ + { + "id": "m-Sales.ACT_Import", + "name": "ACT_Import", + "qualified_name": "Sales.ACT_Import", + "module_name": "Sales", + "microflow_type": "MICROFLOW", + "description": "" + } + ], + "activities": { + "Sales.ACT_Import": [ + { + "id": "a1", + "name": "a1", + "caption": "Loop", + "activity_type": "LoopedActivity", + "action_type": "", + "error_handling_type": "Rollback", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a2", + "name": "a2", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "RetrieveAction", + "error_handling_type": "", + "loop_depth": 1, + "auto_generate_caption": false, + "parent_loop_id": "a1", + "retrieve_source": "association" + } + ] + } +} diff --git a/tests/lint-rules/fixtures/loop001-c-delete-depth2.json b/tests/lint-rules/fixtures/loop001-c-delete-depth2.json new file mode 100644 index 00000000..9c3083b5 --- /dev/null +++ b/tests/lint-rules/fixtures/loop001-c-delete-depth2.json @@ -0,0 +1,47 @@ +{ + "microflows": [ + { + "id": "m-Sales.ACT_Purge", + "name": "ACT_Purge", + "qualified_name": "Sales.ACT_Purge", + "module_name": "Sales", + "microflow_type": "MICROFLOW", + "description": "" + } + ], + "activities": { + "Sales.ACT_Purge": [ + { + "id": "a1", + "name": "a1", + "caption": "Loop", + "activity_type": "LoopedActivity", + "action_type": "", + "error_handling_type": "Rollback", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a2", + "name": "a2", + "caption": "Loop", + "activity_type": "LoopedActivity", + "action_type": "", + "error_handling_type": "Rollback", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a3", + "name": "a3", + "caption": "Delete line", + "activity_type": "ActionActivity", + "action_type": "DeleteObjectAction", + "error_handling_type": "", + "loop_depth": 2, + "auto_generate_caption": false, + "parent_loop_id": "a2" + } + ] + } +} diff --git a/tests/lint-rules/fixtures/loop001-d-top-level-retrieve.json b/tests/lint-rules/fixtures/loop001-d-top-level-retrieve.json new file mode 100644 index 00000000..30fbf557 --- /dev/null +++ b/tests/lint-rules/fixtures/loop001-d-top-level-retrieve.json @@ -0,0 +1,48 @@ +{ + "microflows": [ + { + "id": "m-Sales.ACT_Import", + "name": "ACT_Import", + "qualified_name": "Sales.ACT_Import", + "module_name": "Sales", + "microflow_type": "MICROFLOW", + "description": "" + } + ], + "activities": { + "Sales.ACT_Import": [ + { + "id": "a1", + "name": "a1", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "RetrieveAction", + "error_handling_type": "", + "loop_depth": 0, + "auto_generate_caption": false, + "retrieve_source": "database" + }, + { + "id": "a2", + "name": "a2", + "caption": "Loop", + "activity_type": "LoopedActivity", + "action_type": "", + "error_handling_type": "Rollback", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a3", + "name": "a3", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "ChangeObjectAction", + "error_handling_type": "", + "loop_depth": 1, + "auto_generate_caption": false, + "parent_loop_id": "a2" + } + ] + } +} diff --git a/tests/lint-rules/fixtures/loop001-e-rest-java-external.json b/tests/lint-rules/fixtures/loop001-e-rest-java-external.json new file mode 100644 index 00000000..fad86ff8 --- /dev/null +++ b/tests/lint-rules/fixtures/loop001-e-rest-java-external.json @@ -0,0 +1,59 @@ +{ + "microflows": [ + { + "id": "m-Sales.ACT_Push", + "name": "ACT_Push", + "qualified_name": "Sales.ACT_Push", + "module_name": "Sales", + "microflow_type": "MICROFLOW", + "description": "" + } + ], + "activities": { + "Sales.ACT_Push": [ + { + "id": "a1", + "name": "a1", + "caption": "Loop", + "activity_type": "LoopedActivity", + "action_type": "", + "error_handling_type": "Rollback", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a2", + "name": "a2", + "caption": "POST row", + "activity_type": "ActionActivity", + "action_type": "RestCallAction", + "error_handling_type": "", + "loop_depth": 1, + "auto_generate_caption": false, + "parent_loop_id": "a1" + }, + { + "id": "a3", + "name": "a3", + "caption": "Hash", + "activity_type": "ActionActivity", + "action_type": "JavaActionCallAction", + "error_handling_type": "", + "loop_depth": 1, + "auto_generate_caption": false, + "parent_loop_id": "a1" + }, + { + "id": "a4", + "name": "a4", + "caption": "OData get", + "activity_type": "ActionActivity", + "action_type": "CallExternalAction", + "error_handling_type": "", + "loop_depth": 1, + "auto_generate_caption": false, + "parent_loop_id": "a1" + } + ] + } +} diff --git a/tests/lint-rules/fixtures/loop001-f-cheap-in-loop.json b/tests/lint-rules/fixtures/loop001-f-cheap-in-loop.json new file mode 100644 index 00000000..9841d3d5 --- /dev/null +++ b/tests/lint-rules/fixtures/loop001-f-cheap-in-loop.json @@ -0,0 +1,81 @@ +{ + "microflows": [ + { + "id": "m-Sales.ACT_Calc", + "name": "ACT_Calc", + "qualified_name": "Sales.ACT_Calc", + "module_name": "Sales", + "microflow_type": "MICROFLOW", + "description": "" + } + ], + "activities": { + "Sales.ACT_Calc": [ + { + "id": "a1", + "name": "a1", + "caption": "Loop", + "activity_type": "LoopedActivity", + "action_type": "", + "error_handling_type": "Rollback", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a2", + "name": "a2", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "MicroflowCallAction", + "error_handling_type": "", + "loop_depth": 1, + "auto_generate_caption": false, + "parent_loop_id": "a1" + }, + { + "id": "a3", + "name": "a3", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "CommitAction", + "error_handling_type": "", + "loop_depth": 1, + "auto_generate_caption": false, + "parent_loop_id": "a1" + }, + { + "id": "a4", + "name": "a4", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "CreateObjectAction", + "error_handling_type": "", + "loop_depth": 1, + "auto_generate_caption": false, + "parent_loop_id": "a1" + }, + { + "id": "a5", + "name": "a5", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "ChangeObjectAction", + "error_handling_type": "", + "loop_depth": 1, + "auto_generate_caption": false, + "parent_loop_id": "a1" + }, + { + "id": "a6", + "name": "a6", + "caption": "Step", + "activity_type": "ExclusiveSplit", + "action_type": "", + "error_handling_type": "", + "loop_depth": 1, + "auto_generate_caption": false, + "parent_loop_id": "a1" + } + ] + } +} diff --git a/tests/lint-rules/fixtures/loop001-g-loops-no-body.json b/tests/lint-rules/fixtures/loop001-g-loops-no-body.json new file mode 100644 index 00000000..b042f7e3 --- /dev/null +++ b/tests/lint-rules/fixtures/loop001-g-loops-no-body.json @@ -0,0 +1,57 @@ +{ + "microflows": [ + { + "id": "m-Sales.ACT_A", + "name": "ACT_A", + "qualified_name": "Sales.ACT_A", + "module_name": "Sales", + "microflow_type": "MICROFLOW", + "description": "" + }, + { + "id": "m-Sales.ACT_B", + "name": "ACT_B", + "qualified_name": "Sales.ACT_B", + "module_name": "Sales", + "microflow_type": "MICROFLOW", + "description": "" + } + ], + "activities": { + "Sales.ACT_A": [ + { + "id": "a1", + "name": "a1", + "caption": "Loop", + "activity_type": "LoopedActivity", + "action_type": "", + "error_handling_type": "Rollback", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a2", + "name": "a2", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "RetrieveAction", + "error_handling_type": "", + "loop_depth": 0, + "auto_generate_caption": false, + "retrieve_source": "database" + } + ], + "Sales.ACT_B": [ + { + "id": "a1", + "name": "a1", + "caption": "Loop", + "activity_type": "LoopedActivity", + "action_type": "", + "error_handling_type": "Rollback", + "loop_depth": 0, + "auto_generate_caption": false + } + ] + } +} diff --git a/tests/lint-rules/fixtures/loop001-i-no-loops.json b/tests/lint-rules/fixtures/loop001-i-no-loops.json new file mode 100644 index 00000000..1e62f532 --- /dev/null +++ b/tests/lint-rules/fixtures/loop001-i-no-loops.json @@ -0,0 +1,47 @@ +{ + "microflows": [ + { + "id": "m-Sales.ACT_Import", + "name": "ACT_Import", + "qualified_name": "Sales.ACT_Import", + "module_name": "Sales", + "microflow_type": "MICROFLOW", + "description": "" + } + ], + "activities": { + "Sales.ACT_Import": [ + { + "id": "a1", + "name": "a1", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "RetrieveAction", + "error_handling_type": "", + "loop_depth": 0, + "auto_generate_caption": false, + "retrieve_source": "database" + }, + { + "id": "a2", + "name": "a2", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "DeleteObjectAction", + "error_handling_type": "", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a3", + "name": "a3", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "RestCallAction", + "error_handling_type": "", + "loop_depth": 0, + "auto_generate_caption": false + } + ] + } +} diff --git a/tests/lint-rules/fixtures/loop001-j-system.json b/tests/lint-rules/fixtures/loop001-j-system.json new file mode 100644 index 00000000..2b23b983 --- /dev/null +++ b/tests/lint-rules/fixtures/loop001-j-system.json @@ -0,0 +1,68 @@ +{ + "microflows": [ + { + "id": "m-System.ACT_Cleanup", + "name": "ACT_Cleanup", + "qualified_name": "System.ACT_Cleanup", + "module_name": "System", + "microflow_type": "MICROFLOW", + "description": "" + }, + { + "id": "m-Sales.RULE_X", + "name": "RULE_X", + "qualified_name": "Sales.RULE_X", + "module_name": "Sales", + "microflow_type": "RULE", + "description": "" + } + ], + "activities": { + "System.ACT_Cleanup": [ + { + "id": "a1", + "name": "a1", + "caption": "Loop", + "activity_type": "LoopedActivity", + "action_type": "", + "error_handling_type": "Rollback", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a2", + "name": "a2", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "DeleteObjectAction", + "error_handling_type": "", + "loop_depth": 1, + "auto_generate_caption": false, + "parent_loop_id": "a1" + } + ], + "Sales.RULE_X": [ + { + "id": "a1", + "name": "a1", + "caption": "Loop", + "activity_type": "LoopedActivity", + "action_type": "", + "error_handling_type": "Rollback", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a2", + "name": "a2", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "DeleteObjectAction", + "error_handling_type": "", + "loop_depth": 1, + "auto_generate_caption": false, + "parent_loop_id": "a1" + } + ] + } +} diff --git a/tests/lint-rules/fixtures/loop001-k-unknown-source.json b/tests/lint-rules/fixtures/loop001-k-unknown-source.json new file mode 100644 index 00000000..244ffb70 --- /dev/null +++ b/tests/lint-rules/fixtures/loop001-k-unknown-source.json @@ -0,0 +1,38 @@ +{ + "microflows": [ + { + "id": "m-Sales.ACT_Import", + "name": "ACT_Import", + "qualified_name": "Sales.ACT_Import", + "module_name": "Sales", + "microflow_type": "MICROFLOW", + "description": "" + } + ], + "activities": { + "Sales.ACT_Import": [ + { + "id": "a1", + "name": "a1", + "caption": "Loop", + "activity_type": "LoopedActivity", + "action_type": "", + "error_handling_type": "Rollback", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a2", + "name": "a2", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "RetrieveAction", + "error_handling_type": "", + "loop_depth": 1, + "auto_generate_caption": false, + "parent_loop_id": "a1", + "retrieve_source": "" + } + ] + } +} diff --git a/tests/lint-rules/fixtures/loop001-l-locations.json b/tests/lint-rules/fixtures/loop001-l-locations.json new file mode 100644 index 00000000..975a3424 --- /dev/null +++ b/tests/lint-rules/fixtures/loop001-l-locations.json @@ -0,0 +1,100 @@ +{ + "microflows": [ + { + "id": "m-Sales.ACT_Import", + "name": "ACT_Import", + "qualified_name": "Sales.ACT_Import", + "module_name": "Sales", + "microflow_type": "MICROFLOW", + "description": "" + }, + { + "id": "m-Billing.NF_Post", + "name": "NF_Post", + "qualified_name": "Billing.NF_Post", + "module_name": "Billing", + "microflow_type": "NANOFLOW", + "description": "" + }, + { + "id": "m-Sales.ACT_Clean", + "name": "ACT_Clean", + "qualified_name": "Sales.ACT_Clean", + "module_name": "Sales", + "microflow_type": "MICROFLOW", + "description": "" + } + ], + "activities": { + "Sales.ACT_Import": [ + { + "id": "a1", + "name": "a1", + "caption": "Loop", + "activity_type": "LoopedActivity", + "action_type": "", + "error_handling_type": "Rollback", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a2", + "name": "a2", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "RetrieveAction", + "error_handling_type": "", + "loop_depth": 1, + "auto_generate_caption": false, + "parent_loop_id": "a1", + "retrieve_source": "database" + } + ], + "Billing.NF_Post": [ + { + "id": "a1", + "name": "a1", + "caption": "Loop", + "activity_type": "LoopedActivity", + "action_type": "", + "error_handling_type": "Rollback", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a2", + "name": "a2", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "RestCallAction", + "error_handling_type": "", + "loop_depth": 1, + "auto_generate_caption": false, + "parent_loop_id": "a1" + } + ], + "Sales.ACT_Clean": [ + { + "id": "a1", + "name": "a1", + "caption": "Loop", + "activity_type": "LoopedActivity", + "action_type": "", + "error_handling_type": "Rollback", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a2", + "name": "a2", + "caption": "Step", + "activity_type": "ActionActivity", + "action_type": "ChangeObjectAction", + "error_handling_type": "", + "loop_depth": 1, + "auto_generate_caption": false, + "parent_loop_id": "a1" + } + ] + } +} diff --git a/tests/lint-rules/fixtures/loop001-m-five-offenders.json b/tests/lint-rules/fixtures/loop001-m-five-offenders.json new file mode 100644 index 00000000..98cf636b --- /dev/null +++ b/tests/lint-rules/fixtures/loop001-m-five-offenders.json @@ -0,0 +1,81 @@ +{ + "microflows": [ + { + "id": "m-Sales.ACT_Many", + "name": "ACT_Many", + "qualified_name": "Sales.ACT_Many", + "module_name": "Sales", + "microflow_type": "MICROFLOW", + "description": "" + } + ], + "activities": { + "Sales.ACT_Many": [ + { + "id": "a1", + "name": "a1", + "caption": "Loop", + "activity_type": "LoopedActivity", + "action_type": "", + "error_handling_type": "Rollback", + "loop_depth": 0, + "auto_generate_caption": false + }, + { + "id": "a2", + "name": "a2", + "caption": "D2", + "activity_type": "ActionActivity", + "action_type": "DeleteObjectAction", + "error_handling_type": "", + "loop_depth": 1, + "auto_generate_caption": false, + "parent_loop_id": "a1" + }, + { + "id": "a3", + "name": "a3", + "caption": "D3", + "activity_type": "ActionActivity", + "action_type": "DeleteObjectAction", + "error_handling_type": "", + "loop_depth": 1, + "auto_generate_caption": false, + "parent_loop_id": "a1" + }, + { + "id": "a4", + "name": "a4", + "caption": "D4", + "activity_type": "ActionActivity", + "action_type": "DeleteObjectAction", + "error_handling_type": "", + "loop_depth": 1, + "auto_generate_caption": false, + "parent_loop_id": "a1" + }, + { + "id": "a5", + "name": "a5", + "caption": "D5", + "activity_type": "ActionActivity", + "action_type": "DeleteObjectAction", + "error_handling_type": "", + "loop_depth": 1, + "auto_generate_caption": false, + "parent_loop_id": "a1" + }, + { + "id": "a6", + "name": "a6", + "caption": "D6", + "activity_type": "ActionActivity", + "action_type": "DeleteObjectAction", + "error_handling_type": "", + "loop_depth": 1, + "auto_generate_caption": false, + "parent_loop_id": "a1" + } + ] + } +} diff --git a/tests/lint-rules/starrun/main.go b/tests/lint-rules/starrun/main.go index 1ae7ac88..ac0bc6fb 100644 --- a/tests/lint-rules/starrun/main.go +++ b/tests/lint-rules/starrun/main.go @@ -181,12 +181,28 @@ func main() { "widgets": noArgs("widgets", list("widget", widgetFields, fx.Widgets)), "microflows": noArgs("microflows", list("microflow", microflowFields, fx.Microflows)), "pages": noArgs("pages", list("page", pageFields, fx.Pages)), - "activities_for": starlark.NewBuiltin("activities_for", func(t *starlark.Thread, b *starlark.Builtin, a starlark.Tuple, k []starlark.Tuple) (starlark.Value, error) { - q, err := oneStr("activities_for")(t, b, a, k) - if err != nil { + // activities_for(q, nested=False) mirrors mxcli 0.25.0: the fixture lists every + // object of a flow, loop bodies included (with parent_loop_id / loop_depth), and the + // default call returns only the rows whose loop_depth is 0 or absent, exactly as the + // real builtin leaves loop bodies out unless nested=True (mendixlabs/mxcli#1266). + "activities_for": starlark.NewBuiltin("activities_for", func(_ *starlark.Thread, _ *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { + var q string + var nested bool + if err := starlark.UnpackArgs("activities_for", args, kwargs, "qualified_name", &q, "nested?", &nested); err != nil { return nil, err } - return list("activity", activityFields, fx.Activities[q]), nil + rows := fx.Activities[q] + if !nested { + top := make([]map[string]any, 0, len(rows)) + for _, r := range rows { + if d, ok := r["loop_depth"].(float64); ok && d > 0 { + continue + } + top = append(top, r) + } + rows = top + } + return list("activity", activityFields, rows), nil }), "refs_from": starlark.NewBuiltin("refs_from", func(t *starlark.Thread, b *starlark.Builtin, a starlark.Tuple, k []starlark.Tuple) (starlark.Value, error) { q, err := oneStr("refs_from")(t, b, a, k) diff --git a/tests/lint-rules/test-batch1-rules.sh b/tests/lint-rules/test-batch1-rules.sh new file mode 100755 index 00000000..c04b557d --- /dev/null +++ b/tests/lint-rules/test-batch1-rules.sh @@ -0,0 +1,148 @@ +#!/usr/bin/env bash +# test-batch1-rules.sh -- unit tests for lint-rules/err001_error_swallowed.star and +# lint-rules/loop001_expensive_action_in_loop.star, run under the real Go Starlark +# interpreter with the mxcli builtins mocked from JSON (tests/lint-rules/starrun). +# +# IMPORTANT: every fixture in tests/lint-rules/fixtures/ is a HAND-WRITTEN unit fixture that +# encodes the catalog projection AS READ FROM THE MXCLI SOURCE (error_handling_type values +# from sdk/microflows/error_handling.go, loop_depth / retrieve_source from the 0.25.0 +# catalog builder). It is not captured tool output. These tests prove the rule logic and the +# self-checks; they do NOT prove the vocabulary matches a real model. That is what the field +# probe in each rule's header is for. +# +# Needs `go` on PATH. A missing toolchain is a FAILURE (exit 1), never a quiet skip. +# Keep bash-3.2 compatible. +# Usage: tests/lint-rules/test-batch1-rules.sh Exit 0 only if every assertion holds. + +set -uo pipefail +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TK="$(cd "$HERE/../.." && pwd)" +FX="$HERE/fixtures" +R1="$TK/lint-rules/err001_error_swallowed.star" +R2="$TK/lint-rules/loop001_expensive_action_in_loop.star" + +if ! command -v go >/dev/null 2>&1; then + echo "FAIL go toolchain not found; these tests cannot run (NOT a pass)." + echo " Install Go (https://go.dev/dl/, or: brew install go / apt install golang) and re-run." + exit 1 +fi +# shellcheck source=../../bin/lib/portable.sh +. "$TK/bin/lib/portable.sh" +require_py # sets $PY or exits 2 with an actionable message + +W=$(mktemp -d "${TMPDIR:-/tmp}/b1rules.XXXXXX") +trap 'rm -rf "$W"' EXIT +PASS=0; FAILED=0 +ok(){ printf ' PASS %s\n' "$1"; PASS=$((PASS+1)); } +no(){ printf ' FAIL %s\n' "$1"; FAILED=$((FAILED+1)); } + +cd "$TK" || exit 1 +OUT="$W/out.json"; ERR="$W/err.txt"; RC=0 + +BIN="$W/starrun" +if ! (cd "$HERE/starrun" && GOFLAGS=-mod=mod go build -o "$BIN" . 2>"$ERR"); then + echo "FAIL could not build tests/lint-rules/starrun: $(head -c 400 "$ERR")"; exit 1 +fi + +# run RULE FIXTURE [STRIP] -> OUT holds stdout, RC the exit code +run(){ + local rule="$1" fix="$2" strip="${3:-}" + if [ -n "$strip" ]; then + "$BIN" -rule "$rule" -fixture "$FX/$fix.json" -strip-fields "$strip" >"$OUT" 2>"$ERR" + else + "$BIN" -rule "$rule" -fixture "$FX/$fix.json" >"$OUT" 2>"$ERR" + fi + RC=$? +} + +# check LABEL PY-EXPR : v is the parsed violation list; the expression must be truthy. +check(){ + local label="$1" expr="$2" + if [ "$RC" -ne 0 ]; then no "$label (harness exit $RC: $(head -c 300 "$ERR"))"; return; fi + if "$PY" - "$OUT" "$expr" <<'PY' +import json, sys +v = json.load(open(sys.argv[1])) +sys.exit(0 if eval(sys.argv[2]) else 1) +PY + then ok "$label"; else no "$label (got: $(head -c 400 "$OUT"))"; fi +} + +echo "== ERR001 ==" +run "$R1" err001-a-continue-nolog +check "a Continue, no log -> 1 finding naming the activity" 'len(v)==1 and "1 activity (" in v[0]["message"] and "Delete order" in v[0]["message"] and "swallowed" in v[0]["message"]' +run "$R1" err001-b-continue-log +check "b Continue + Log message -> 0 findings" 'len(v)==0' +run "$R1" err001-c-custom-raise +check "c Custom + error end event -> 0 findings" 'len(v)==0' +run "$R1" err001-d-handler-call +check "d CustomWithoutRollBack + call to SUB_LogError -> 0 findings" 'len(v)==0' +run "$R1" err001-e-rollback-abort +check "e Rollback / Abort / unset only -> 0 findings" 'len(v)==0' +run "$R1" err001-f-system +check "f System + Administration swallow -> 0 findings, no self-check" 'len(v)==0' +run "$R1" err001-g-rule-type +check "g RULE-type flow swallows -> 0 findings" 'len(v)==0' +run "$R1" err001-a-continue-nolog "error_handling_type" +check "h stripped error_handling_type -> 1 _rule finding mentioning 0.25.0" 'len(v)==1 and v[0]["module"]=="_rule" and "0.25.0" in v[0]["message"]' +run "$R1" err001-i-no-activities +check "i 2 flows, no activities -> 1 _rule finding 'not one activity'" 'len(v)==1 and v[0]["module"]=="_rule" and "not one activity" in v[0]["message"]' +run "$R1" err001-j-twenty-empty +check "j 20 activities, all handling empty -> 1 _rule finding 'none carries'" 'len(v)==1 and v[0]["module"]=="_rule" and "none carries" in v[0]["message"]' +run "$R1" err001-k-nested +check "k Continue inside a loop: logged -> clean, unlogged -> 1 finding at the flow" 'len(v)==1 and v[0]["document_name"]=="Sales.ACT_LoopSwallow" and v[0]["document_type"]=="Microflow" and v[0]["module"]=="Sales"' +run "$R1" err001-l-two-swallowing +check "l nanoflow, Continue + CustomWithoutRollBack -> '2 activities' naming both" 'len(v)==1 and "2 activities" in v[0]["message"] and "Commit" in v[0]["message"] and "Call sync" in v[0]["message"]' +run "$R1" err001-m-handler-name-mismatch +check "m call to SUB_Logistics is not a handler -> 1 finding" 'len(v)==1 and v[0]["document_name"]=="Sales.ACT_Sync"' +run "$R1" err001-n-self-ref-handler +check "n a handler-named flow calling itself does not vouch for itself -> 1 finding" 'len(v)==1' + +echo "== LOOP001 ==" +run "$R2" loop001-a-db-retrieve +check "a database retrieve at depth 1 -> 1 finding '(database retrieve)'" 'len(v)==1 and "1 per-row call inside" in v[0]["message"] and "Find customer" in v[0]["message"] and "(database retrieve)" in v[0]["message"]' +run "$R2" loop001-b-assoc-retrieve +check "b association retrieve in loop -> 0 findings" 'len(v)==0' +run "$R2" loop001-c-delete-depth2 +check "c delete two loops down -> 1 finding '(delete)'" 'len(v)==1 and "(delete)" in v[0]["message"]' +run "$R2" loop001-d-top-level-retrieve +check "d database retrieve before the loop -> 0 findings" 'len(v)==0' +run "$R2" loop001-e-rest-java-external +check "e REST + Java + external in loop -> '3 per-row calls' with all three labels" 'len(v)==1 and "3 per-row calls" in v[0]["message"] and "(REST call)" in v[0]["message"] and "(Java action)" in v[0]["message"] and "(external call)" in v[0]["message"]' +run "$R2" loop001-f-cheap-in-loop +check "f microflow call / commit / create / change / split in loop -> 0 findings" 'len(v)==0' +run "$R2" loop001-g-loops-no-body +check "g 2 loops, no body rows -> 1 _rule finding 'not one activity inside'" 'len(v)==1 and v[0]["module"]=="_rule" and "not one activity inside" in v[0]["message"]' +run "$R2" loop001-a-db-retrieve "loop_depth" +check "h stripped loop_depth -> 1 _rule finding mentioning 0.25.0" 'len(v)==1 and v[0]["module"]=="_rule" and "0.25.0" in v[0]["message"]' +run "$R2" loop001-i-no-loops +check "i expensive actions but no loop -> 0 findings, no self-check" 'len(v)==0' +run "$R2" loop001-j-system +check "j System module + RULE-type loops -> 0 findings" 'len(v)==0' +run "$R2" loop001-k-unknown-source +check "k retrieve with unknown source in loop -> 0 findings (not guessed)" 'len(v)==0' +run "$R2" loop001-l-locations +check "l findings located at each offending flow, clean flow skipped" ' +len(v)==2 and sorted((x["module"],x["document_name"],x["document_type"]) for x in v)==[ + ("Billing","Billing.NF_Post","Microflow"),("Sales","Sales.ACT_Import","Microflow")]' +run "$R2" loop001-m-five-offenders +check "m 5 deletes in one loop -> one finding, first 4 named then '...'" 'len(v)==1 and "5 per-row calls" in v[0]["message"] and "D5" in v[0]["message"] and "D6" not in v[0]["message"] and ", ..." in v[0]["message"]' + +echo "== performance (synthetic, 3000 flows / 20000 widgets) ==" +PF="$W/perf.json" +"$PY" "$HERE/gen-perf-fixture.py" "$PF" >/dev/null || { no "perf fixture generation"; } +TIMEFORMAT='%R' +for pair in "ERR001:$R1" "LOOP001:$R2"; do + nm="${pair%%:*}"; rule="${pair#*:}" + { time "$BIN" -rule "$rule" -fixture "$PF" >"$W/perf-out.json" 2>"$W/perf-err.txt"; } 2>"$W/time.txt" + rc=$? + secs=$(tail -n 1 "$W/time.txt") + if [ "$rc" -eq 0 ] && awk -v t="$secs" 'BEGIN{exit !(t+0 < 60)}'; then + ok "$nm perf run exit 0 in ${secs}s (limit 60s)" + else + no "$nm perf run rc=$rc in ${secs}s (limit 60s): $(head -c 300 "$W/perf-err.txt")" + fi +done + +echo +echo "passed: $PASS failed: $FAILED" +[ "$FAILED" -eq 0 ] diff --git a/tests/test-lint-delivery.sh b/tests/test-lint-delivery.sh old mode 100755 new mode 100644 index 6f1d57a9..d4ff956e --- a/tests/test-lint-delivery.sh +++ b/tests/test-lint-delivery.sh @@ -110,7 +110,7 @@ echo "== 10 caller forgot the manifest: the lib sources it itself ==" P4="$W/proj4"; mkdir -p "$P4/.claude"; echo x > "$P4/PROJECT.md" bash -c "set -u; . '$W/tk/bin/lib/install-lint-rules.sh'; \ mxtk_install_lint_rules '$W/tk' '$P4' 0 ''" >/dev/null 2>&1 -[ "$(ls "$P4/.claude/lint-rules" 2>/dev/null | wc -l | tr -d ' ')" = 6 ] \ +[ "$(ls "$P4/.claude/lint-rules" 2>/dev/null | wc -l | tr -d ' ')" = "$want" ] \ && ok "rules landed anyway" || no "silent no-op" echo "== 11 manifest genuinely unavailable: warn, never pretend success =="