ApiLogicServer 15.2.0__py3-none-any.whl → 15.2.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. api_logic_server_cli/api_logic_server.py +2 -2
  2. api_logic_server_cli/prototypes/base/.github/.copilot-instructions.md +22 -0
  3. api_logic_server_cli/prototypes/base/docs/training/testing.md +21 -9
  4. api_logic_server_cli/prototypes/base/test/api_logic_server_behave/behave_logic_report.py +55 -29
  5. api_logic_server_cli/prototypes/base/test/api_logic_server_behave/behave_logic_report.py.bak +285 -0
  6. api_logic_server_cli/prototypes/basic_demo/customizations/test/api_logic_server_behave/reports/Behave Logic Report Intro micro.md +35 -0
  7. api_logic_server_cli/prototypes/basic_demo/customizations/test/api_logic_server_behave/reports/Behave Logic Report Intro.md +35 -0
  8. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/docs/training/testing.md +210 -12
  9. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/behave_logic_report.py +13 -84
  10. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/behave_logic_report.py.bak +282 -0
  11. api_logic_server_cli/prototypes/manager/system/ApiLogicServer-Internal-Dev/copilot-dev-context.md +39 -4
  12. api_logic_server_cli/prototypes/manager/system/app_model_editor/test/api_logic_server_behave/behave_logic_report.py +13 -75
  13. api_logic_server_cli/prototypes/manager/system/app_model_editor/test/api_logic_server_behave/behave_logic_report.py.bak +256 -0
  14. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_docs_logic/test/api_logic_server_behave/behave_logic_report.py +13 -75
  15. api_logic_server_cli/prototypes/manager/system/genai/examples/genai_demo/genai_demo_docs_logic/test/api_logic_server_behave/behave_logic_report.py.bak +256 -0
  16. api_logic_server_cli/prototypes/nw/test/api_logic_server_behave/reports/Behave Logic Report Intro micro.md +17 -0
  17. api_logic_server_cli/prototypes/nw/test/api_logic_server_behave/reports/Behave Logic Report Intro.md +17 -0
  18. {apilogicserver-15.2.0.dist-info → apilogicserver-15.2.3.dist-info}/METADATA +79 -8
  19. {apilogicserver-15.2.0.dist-info → apilogicserver-15.2.3.dist-info}/RECORD +23 -16
  20. api_logic_server_cli/prototypes/base/.github/.copilot-instructionsZ.mdx +0 -661
  21. {apilogicserver-15.2.0.dist-info → apilogicserver-15.2.3.dist-info}/WHEEL +0 -0
  22. {apilogicserver-15.2.0.dist-info → apilogicserver-15.2.3.dist-info}/entry_points.txt +0 -0
  23. {apilogicserver-15.2.0.dist-info → apilogicserver-15.2.3.dist-info}/licenses/LICENSE +0 -0
  24. {apilogicserver-15.2.0.dist-info → apilogicserver-15.2.3.dist-info}/top_level.txt +0 -0
@@ -19,11 +19,38 @@ Step 1c. Scan api/api_discovery/*.py → Discover custom APIs (PHASE 2!)
19
19
  Step 2. Decide Phase 1 vs Phase 2 → Based on custom API existence
20
20
  Step 3. Generate .feature files → Business language scenarios
21
21
  Step 4. Implement steps/*.py → Using discovered APIs or CRUD
22
- Step 5. Run testspython behave_run.py
22
+ Step 4b. VERIFY STEP ORDERING Multi-item BEFORE single-item (Rule #0.5!)
23
+ Step 5. SUGGEST how to run tests (DO NOT run automatically)
23
24
  ```
24
25
 
26
+ **CRITICAL PRE-TEST CHECKLIST:**
27
+ - [ ] Step 1c completed? (Custom APIs discovered)
28
+ - [ ] **Database values verified?** (Rule #10: Run SQL to check actual prices/flags)
29
+ - [ ] `sqlite3 db.sqlite "SELECT name, unit_price, carbon_neutral FROM product;"`
30
+ - [ ] Don't assume product attributes - verify BEFORE writing expectations!
31
+ - [ ] **Step ordering verified?** (Most specific → Most general)
32
+ - [ ] @when patterns: carbon neutral > multi-item > single-item
33
+ - [ ] @given patterns: multi-item > single-item
34
+ - [ ] Use `grep -n "@when('.*with" steps/*.py` to verify order
35
+ - [ ] Test data uses timestamps? (Rule #0: Repeatability)
36
+ - [ ] Security config read? (SECURITY_ENABLED value)
37
+
25
38
  **DO NOT skip Step 1c!** Custom APIs change the entire testing approach.
26
39
 
40
+ **DO NOT skip Step 4b!** Wrong step ordering causes silent failures with balance=0.
41
+
42
+ **DO NOT run tests automatically!** Instead, suggest this workflow:
43
+
44
+ ```bash
45
+ # 1. Start the server (in separate terminal or background)
46
+ python api_logic_server_run.py
47
+
48
+ # 2. Run the tests (in another terminal)
49
+ python test/api_logic_server_behave/behave_run.py
50
+ ```
51
+
52
+ **Why manual execution?** Tests require a running server. The AI cannot manage multiple terminals or background processes reliably.
53
+
27
54
  ## Phase 1 vs Phase 2: The Core Decision
28
55
 
29
56
  ### Phase 1: CRUD-Level Testing
@@ -213,7 +240,7 @@ def step_impl_general(context, customer_name, quantity, product_name):
213
240
  ...
214
241
  ```
215
242
 
216
- **Example 2: Multi-Item Orders**
243
+ **Example 2: Multi-Item Orders (GIVEN pattern)**
217
244
 
218
245
  ```python
219
246
  # ❌ WRONG ORDER - Single-item pattern matches "3 Widget and 2 Gadget"
@@ -242,6 +269,51 @@ def step_impl_single(context, customer_name, quantity, product_name):
242
269
  ...
243
270
  ```
244
271
 
272
+ **Example 3: Multi-Item Orders (WHEN pattern) - Real Bug Found!**
273
+
274
+ ```python
275
+ # ❌ WRONG ORDER - Causes silent failure with balance=0 instead of expected value
276
+ @when('B2B order placed for "{customer_name}" with {quantity:d} {product_name}')
277
+ def step_impl_single(context, customer_name, quantity, product_name):
278
+ # This matches "3 Widget and 2 Gadget" FIRST!
279
+ # product_name becomes "Widget and 2 Gadget" (entire string)
280
+ # OrderB2B API tries to find product "Widget and 2 Gadget" → fails
281
+ # Returns 200 but no order created → customer balance stays 0
282
+ # Test fails: "expected 570, got 0.0"
283
+ ...
284
+
285
+ @when('B2B order placed for "{customer_name}" with {qty1:d} {product1} and {qty2:d} {product2}')
286
+ def step_impl_multi(context, customer_name, qty1, product1, qty2, product2):
287
+ # NEVER REACHED! Single-item pattern matched first
288
+ ...
289
+
290
+ # ✅ CORRECT ORDER - Multi-item pattern MUST come before single-item
291
+ @when('B2B order placed for "{customer_name}" with {qty1:d} {product1} and {qty2:d} {product2}')
292
+ def step_impl_multi(context, customer_name, qty1, product1, qty2, product2):
293
+ # Now matches "3 Widget and 2 Gadget" correctly
294
+ # Creates order with 2 items, balance = 570
295
+ ...
296
+
297
+ @when('B2B order placed for "{customer_name}" with {quantity:d} {product_name}')
298
+ def step_impl_single(context, customer_name, quantity, product_name):
299
+ # Now only matches single product patterns
300
+ ...
301
+ ```
302
+
303
+ **CRITICAL FILE ORGANIZATION:**
304
+ Within each decorator type (@given, @when, @then), organize patterns like this:
305
+
306
+ ```python
307
+ # For @when patterns:
308
+ @when('... with {qty:d} carbon neutral {product}') # Most specific (3+ keywords)
309
+ @when('... with {qty1:d} {p1} and {qty2:d} {p2}') # More specific (multi-param + "and")
310
+ @when('... with {quantity:d} {product_name}') # General (fewest keywords)
311
+
312
+ # For @given patterns:
313
+ @given('... with {qty1:d} {p1} and {qty2:d} {p2}') # More specific (multi-param + "and")
314
+ @given('... with {quantity:d} {product_name}') # General (fewer params)
315
+ ```
316
+
245
317
  **Why This Matters:**
246
318
  - Wrong order → context.item_id not set → "Then Item amount" step fails with "item_id not set in context"
247
319
  - Behave doesn't warn about unreachable patterns
@@ -401,19 +473,28 @@ Rule.sum(derive=Customer.balance, as_sum_of=Order.amount_total,
401
473
  ```python
402
474
  # ALWAYS check actual product prices/flags before writing test expectations!
403
475
 
404
- # Check prices:
405
- # sqlite3 db.sqlite "SELECT name, unit_price, carbon_neutral FROM Product;"
476
+ # Check prices AND flags:
477
+ # sqlite3 db.sqlite "SELECT name, unit_price, carbon_neutral FROM product;"
406
478
 
407
- # Widget=90, Gadget=150, Green=109
479
+ # Results:
480
+ # 1|Gadget|150|1 ← carbon_neutral = 1 (TRUE)
481
+ # 2|Widget|90| ← carbon_neutral = NULL (not carbon neutral!)
482
+ # 3|Thingamajig|5075|
483
+ # 4|Doodad|110|
484
+ # 5|Green|109|1 ← carbon_neutral = 1 (TRUE)
408
485
 
409
- # ❌ WRONG - Assumed Widget=$100
410
- # Expected: 10 * 100 = 1000
486
+ # ❌ WRONG - Assumed Widget is carbon neutral
487
+ # Scenario: Carbon Neutral Discount
488
+ # When B2B order placed with 10 carbon neutral Widget
489
+ # Then balance should be 810 # Expected 10 * 90 * 0.9 = 810
490
+ # FAILS: Widget is NOT carbon neutral → no discount → balance = 900
411
491
 
412
- # ✅ CORRECT - Verified Widget=$90
413
- # Expected: 10 * 90 = 900
492
+ # ✅ CORRECT - Verified Gadget IS carbon neutral (flag = 1)
493
+ # Scenario: Carbon Neutral Discount
494
+ # When B2B order placed with 10 carbon neutral Gadget
495
+ # Then balance should be 1350 # Correct: 10 * 150 * 0.9 = 1350
414
496
 
415
- # For carbon neutral discount (10% off when qty >= 10):
416
- # Expected: 10 * 90 * 0.9 = 810
497
+ # CRITICAL: Don't assume product attributes - VERIFY with SQL first!
417
498
  ```
418
499
 
419
500
  ### Rule #11: Step Definitions Must Match Feature Files ⚠️ NEW
@@ -433,7 +514,7 @@ def step_impl(context, customer_name): # Missing balance and limit parameters!
433
514
  limit = 1000
434
515
  ```
435
516
 
436
- ### Rule #10: Always Initialize Context Variables ⚠️ NEW
517
+ ### Rule #12: Always Initialize Context Variables ⚠️ NEW
437
518
  ```python
438
519
  # ✅ CORRECT - prevents KeyError in subsequent steps
439
520
  @when('B2B order placed')
@@ -547,6 +628,43 @@ def step_impl(context, expected):
547
628
  | "row altered by another user" | Use direct FK: `"customer_id": int(id)` |
548
629
  | "circular import" | Remove imports from logic/, database/ |
549
630
  | "empty logic log" | Add `test_utils.prt(msg, scenario_name)` |
631
+ | **"balance: expected 570, got 0.0"** | **Step ordering issue (Rule #0.5)! Multi-item pattern after single-item** |
632
+ | **"context has no attribute 'item_id'"** | **Step ordering issue! Specific pattern defined after general pattern** |
633
+ | "Order created but no items" | Check if wrong step matched (print debug in step) |
634
+
635
+ ### Debugging Step Ordering Issues
636
+
637
+ **Symptom**: Test passes WHEN step but THEN assertions fail with unexpected values (often 0 or None).
638
+
639
+ **Diagnosis**:
640
+ 1. **Check which step executed**: Look at test output for line numbers
641
+ ```
642
+ When B2B order placed for "Kevin" with 3 Widget and 2 Gadget # line=261
643
+ ```
644
+ If line 261 is single-item but you expected line 320 (multi-item), wrong pattern matched!
645
+
646
+ 2. **Verify database**: Check if records were actually created
647
+ ```bash
648
+ sqlite3 db.sqlite "SELECT * FROM 'order' WHERE customer_id = X;"
649
+ sqlite3 db.sqlite "SELECT * FROM item WHERE order_id = Y;"
650
+ ```
651
+
652
+ 3. **Check step file organization**: Count literal keywords in each pattern
653
+ ```python
654
+ # line 202: "carbon neutral" = 2 keywords (most specific)
655
+ # line 261: {quantity:d} {product_name} = 0 keywords (general)
656
+ # line 320: {qty1} {p1} "and" {qty2} {p2} = 1 keyword (more specific)
657
+ ```
658
+ Line 320 MUST come BEFORE line 261!
659
+
660
+ **Quick Fix**:
661
+ ```bash
662
+ # Find all @when patterns in your steps file
663
+ grep -n "@when('.*with.*{" features/steps/*.py
664
+
665
+ # Reorder so patterns with MORE keywords/parameters come FIRST
666
+ # Rule: Most specific → Most general (top to bottom)
667
+ ```
550
668
 
551
669
  ## Test Generation Workflow
552
670
 
@@ -591,3 +709,83 @@ def step_impl(context, expected):
591
709
 
592
710
  **The Magic:** Users built OrderB2B for partners → Same API provides natural test scenarios!
593
711
 
712
+ ## Automated Step Ordering Verification
713
+
714
+ **Command to verify step ordering in your test files:**
715
+
716
+ ```bash
717
+ # List all @when patterns with line numbers (should be ordered specific→general)
718
+ cd test/api_logic_server_behave
719
+ grep -n "@when('.*with.*{" features/steps/*.py
720
+
721
+ # Expected output (line numbers ascending = correct order):
722
+ # 202: @when('... with {qty:d} carbon neutral {product}') # Most specific
723
+ # 265: @when('... with {qty1:d} {p1} and {qty2:d} {p2}') # More specific
724
+ # 318: @when('... with {quantity:d} {product_name}') # General
725
+
726
+ # If multi-item (line 265) comes AFTER single-item (line 318) = WRONG!
727
+ ```
728
+
729
+ **Python script to auto-check ordering** (add to test directory):
730
+
731
+ ```python
732
+ #!/usr/bin/env python3
733
+ """Verify Behave step ordering for multi-param patterns."""
734
+ import re, sys
735
+ from pathlib import Path
736
+
737
+ def check_step_order(steps_file):
738
+ """Check if multi-item patterns come before single-item patterns."""
739
+ with open(steps_file) as f:
740
+ lines = f.readlines()
741
+
742
+ issues = []
743
+ when_patterns = []
744
+
745
+ for i, line in enumerate(lines, 1):
746
+ if match := re.match(r"@when\('.*with.*\{", line):
747
+ # Count parameters and keywords
748
+ param_count = line.count('{')
749
+ has_and = ' and ' in line
750
+ has_special_keyword = any(k in line for k in ['carbon neutral', 'shipped'])
751
+
752
+ specificity = param_count + (2 if has_and else 0) + (3 if has_special_keyword else 0)
753
+ when_patterns.append((i, specificity, line.strip()))
754
+
755
+ # Check if patterns are in descending specificity order
756
+ for i in range(len(when_patterns) - 1):
757
+ curr_line, curr_spec, curr_text = when_patterns[i]
758
+ next_line, next_spec, next_text = when_patterns[i + 1]
759
+
760
+ if next_spec > curr_spec:
761
+ issues.append(f"❌ Line {next_line} (specificity={next_spec}) should come BEFORE line {curr_line} (specificity={curr_spec})")
762
+ issues.append(f" More specific: {next_text}")
763
+ issues.append(f" Less specific: {curr_text}")
764
+
765
+ return issues
766
+
767
+ if __name__ == '__main__':
768
+ steps_dir = Path('features/steps')
769
+ all_issues = []
770
+
771
+ for steps_file in steps_dir.glob('*_steps.py'):
772
+ issues = check_step_order(steps_file)
773
+ if issues:
774
+ all_issues.extend([f"\n{steps_file}:"] + issues)
775
+
776
+ if all_issues:
777
+ print("Step Ordering Issues Found:")
778
+ print('\n'.join(all_issues))
779
+ sys.exit(1)
780
+ else:
781
+ print("✅ All step patterns correctly ordered (specific → general)")
782
+ sys.exit(0)
783
+ ```
784
+
785
+ **Usage:**
786
+ ```bash
787
+ cd test/api_logic_server_behave
788
+ python check_step_order.py # Run before committing tests
789
+ ```
790
+
791
+ This automation prevents Rule #0.5 violations across ALL databases and projects!
@@ -193,90 +193,19 @@ def main(behave_log: str, scenario_logs: str, wiki: str, prepend_wiki: str):
193
193
  wiki_data.append(" ")
194
194
  each_line = "## " + each_line
195
195
  if each_line.startswith(" Scenario"):
196
- # Before starting new scenario, show logic for previous one if we saw Then
197
- if just_saw_then and previous_scenario:
198
- show_logic(scenario=previous_scenario, logic_logs_dir=scenario_logs)
199
- just_saw_then = False
200
- each_line = tab + each_line
201
- if each_line.startswith(" Given") or \
202
- each_line.startswith(" When") or \
203
- each_line.startswith(" Then"):
204
- if each_line.startswith(" Then"):
205
- just_saw_then = True
206
- each_line = tab + tab + each_line
207
-
208
- each_line = each_line[:-1]
209
- debug_loc = each_line.find(behave_debug_info)
210
- if debug_loc > 0:
211
- each_line = each_line[0 : debug_loc]
212
- each_line = each_line.rstrip()
213
- if "Scenario" in each_line:
196
+ # Extract scenario name for logic lookup
214
197
  current_scenario = each_line[18:]
215
- previous_scenario = current_scenario
216
198
  wiki_data.append(" ")
217
199
  wiki_data.append(" ")
218
- wiki_data.append("### " + each_line[8:])
219
-
220
- each_line = each_line + " " # wiki for "new line"
221
-
222
- wiki_data.append(each_line)
223
-
224
- # Show logic for the last scenario if we saw Then
225
- if just_saw_then and current_scenario:
226
- show_logic(scenario=current_scenario, logic_logs_dir=scenario_logs)
227
-
228
- with open(wiki, 'w') as rpt:
229
- rpt.write('\n'.join(wiki_data))
230
- wiki_full_path = Path(wiki).absolute()
231
- print(f'Wiki Output: {wiki_full_path}\n\n')
232
-
233
-
234
-
235
- def print_args(args, msg):
236
- print(msg)
237
- for each_arg in args:
238
- print(f' {each_arg}')
239
- print(" ")
240
-
241
-
242
- @click.group()
243
- @click.pass_context
244
- def cli(ctx):
245
- """
246
- Combine behave.log and scenario_logic_logs to create Behave Logic Report
247
-
248
- """
249
- pass
250
-
251
-
252
- @cli.command("run")
253
- @click.pass_context
254
- @click.option('--behave_log',
255
- default=f'logs/behave.log', # cwd set to test/api_logic_server_behave
256
- # prompt="Log from behave test suite run [behave.log]",
257
- help="Help")
258
- @click.option('--scenario_logs',
259
- default=f'logs/scenario_logic_logs',
260
- # prompt="Logic Log directory from ",
261
- help="Help")
262
- @click.option('--wiki',
263
- default=f'reports/Behave Logic Report.md',
264
- # prompt="Log from behave test suite run [api_logic_server_behave]",
265
- help="Help")
266
- @click.option('--prepend_wiki',
267
- default=f'reports/Behave Logic Report Intro micro.md',
268
- # prompt="Log from behave test suite run [Behave Logic Report Intro]",
269
- help="Help")
270
- def run(ctx, behave_log: str, scenario_logs: str, wiki: str, prepend_wiki: str):
271
- main(behave_log = behave_log, scenario_logs = scenario_logs, wiki = wiki, prepend_wiki = prepend_wiki)
272
-
273
-
274
- if __name__ == '__main__': # debugger & python command line start here
275
- # eg: python api_logic_server_cli/cli.py create --project_name=~/Desktop/test_project
276
- # unix: python api_logic_server_cli/cli.py create --project_name=/home/ApiLogicProject
277
-
278
- print(f'\nBehave Logic Report 1.1, started at {os.getcwd()}')
279
- commands = sys.argv
280
- if len(sys.argv) > 1:
281
- print_args(commands, f'\n\nCommand Line Arguments:')
282
- cli()
200
+ # Remove the debug info (# features/...) from the scenario name
201
+ debug_loc = current_scenario.find(behave_debug_info)
202
+ if debug_loc > 0:
203
+ current_scenario = current_scenario[0:debug_loc].strip()
204
+ wiki_data.append(" ")
205
+ wiki_data.append(" ")
206
+ # Remove debug info from header line too
207
+ header_line = each_line[2:]
208
+ debug_loc = header_line.find(behave_debug_info)
209
+ if debug_loc > 0:
210
+ header_line = header_line[0:debug_loc].rstrip()
211
+ wiki_data.append("### " + header_line) # Add scenario header
@@ -0,0 +1,282 @@
1
+ import requests
2
+ from pathlib import Path
3
+ import os
4
+ import ast
5
+ import sys
6
+ import click
7
+
8
+ """
9
+ Creates wiki file from test/behave/behave.log, with rule use.
10
+
11
+ Tips
12
+ * use 2 spaces (at end) for newline
13
+ * for tab: & emsp;
14
+
15
+ """
16
+
17
+ tab = " "
18
+ behave_debug_info = " # "
19
+ wiki_data = []
20
+ debug_scenario = "XXGood Order Custom Service"
21
+
22
+ scenario_doc_strings = {}
23
+ """ dict of scenario_name, array of strings """
24
+
25
+
26
+ def remove_trailer(line: str) -> str:
27
+ """ remove everything after the ## """
28
+ end_here = line.find("\t\t##")
29
+ result = line[0:end_here]
30
+ return result
31
+
32
+ def line_spacer():
33
+ wiki_data.append("\n")
34
+ wiki_data.append(" ")
35
+ wiki_data.append(" ")
36
+ wiki_data.append("\n")
37
+
38
+
39
+ def get_current_readme(prepend_wiki: str):
40
+ """ initialize wiki_data with readme up to {report_name} """
41
+ report_name = "Behave Logic Report"
42
+ with open(prepend_wiki) as readme:
43
+ readme_lines = readme.readlines()
44
+ need_spacer = True
45
+ for each_readme_line in readme_lines:
46
+ if '# ' + report_name in each_readme_line:
47
+ need_spacer = False
48
+ break
49
+ wiki_data.append(each_readme_line[0:-1])
50
+ if need_spacer:
51
+ line_spacer()
52
+ wiki_data.append(f'# {report_name}')
53
+
54
+ def get_truncated_scenario_name(scenario_name: str) -> str:
55
+ """ address max file length (chop at 26), illegal characters """
56
+ scenario_trunc = scenario_name
57
+ if scenario_trunc is not None and len(scenario_trunc) >= 26:
58
+ scenario_trunc = scenario_name[0:25]
59
+ scenario_trunc = f'{str(scenario_trunc).replace(" ", "_")}'
60
+ return scenario_trunc
61
+
62
+
63
+ def show_logic(scenario: str, logic_logs_dir: str):
64
+ """ insert s{logic_logs_dir}/scenario.log into wiki_data as disclosure area """
65
+ scenario_trunc = get_truncated_scenario_name(scenario)
66
+ logic_file_name = f'{logic_logs_dir}/{scenario_trunc}.log'
67
+ logic_file_name_path = Path(logic_file_name)
68
+ if not logic_file_name_path.is_file(): # debug code
69
+ # wiki_data.append(f'unable to find Logic Log file: {logic_file_name}')
70
+ if scenario == debug_scenario:
71
+ print(f'RELATIVE: {logic_file_name} in {os.getcwd()}')
72
+ full_name = f'{os.getcwd()}/{logic_file_name}'
73
+ print(f'..FULL: {os.getcwd()}/{logic_file_name}')
74
+ logic_file_name = '{logic_logs_dir}/test.log'
75
+ with open(logic_file_name) as logic:
76
+ logic_lines = logic.readlines()
77
+ else:
78
+ logic_log = []
79
+ rules_used = []
80
+ wiki_data.append("<details markdown>")
81
+ wiki_data.append("<summary>Tests - and their logic - are transparent.. click to see Logic</summary>")
82
+ line_spacer()
83
+ scenario_trunc = get_truncated_scenario_name(scenario)
84
+ if scenario_trunc in scenario_doc_strings:
85
+ wiki_data.append(f'**Logic Doc** for scenario: {scenario}')
86
+ wiki_data.append(" ")
87
+ for each_doc_string_line in scenario_doc_strings[scenario_trunc]:
88
+ wiki_data.append(each_doc_string_line[0: -1])
89
+ line_spacer()
90
+ wiki_data.append(f'**Rules Used** in Scenario: {scenario}')
91
+ wiki_data.append("```")
92
+ with open(logic_file_name) as logic:
93
+ logic_lines = logic.readlines()
94
+ is_logic_log = True
95
+ last_rules_start = -1
96
+ last_rules_end = -1
97
+
98
+ # First, find the LAST "These Rules Fired" section
99
+ for i, each_logic_line in enumerate(logic_lines):
100
+ if "These Rules Fired" in each_logic_line:
101
+ last_rules_start = i + 1 # Start collecting from next line
102
+ last_rules_end = -1 # Reset end marker to find the next COMPLETE
103
+ elif last_rules_start > 0 and last_rules_end == -1:
104
+ if 'Logic Phase:' in each_logic_line and 'COMPLETE' in each_logic_line:
105
+ last_rules_end = i
106
+
107
+ # Now process the file, collecting logic log and extracting the last rules section
108
+ for i, each_logic_line in enumerate(logic_lines):
109
+ each_logic_line = remove_trailer(each_logic_line)
110
+
111
+ if is_logic_log:
112
+ if "These Rules Fired" in each_logic_line:
113
+ is_logic_log = False
114
+ else:
115
+ logic_log.append(each_logic_line)
116
+
117
+ # Extract rules from the last "These Rules Fired" section
118
+ if last_rules_start <= i < last_rules_end:
119
+ # Skip empty lines
120
+ if each_logic_line.strip():
121
+ wiki_data.append(each_logic_line + " ")
122
+
123
+ wiki_data.append("```")
124
+ wiki_data.append(f'**Logic Log** in Scenario: {scenario}')
125
+ wiki_data.append("```")
126
+ for each_logic_log in logic_log:
127
+ each_line = remove_trailer(each_logic_log)
128
+ wiki_data.append(each_line)
129
+ wiki_data.append("```")
130
+ wiki_data.append("</details>")
131
+
132
+
133
+ def get_docStrings(steps_dir: str):
134
+ steps_dir_files = os.listdir(steps_dir)
135
+ indent = 4 # skip leading blanks
136
+ for each_steps_dir_file in steps_dir_files:
137
+ each_steps_dir_file_path = Path(steps_dir).joinpath(each_steps_dir_file)
138
+ if each_steps_dir_file_path.is_file():
139
+ with open(each_steps_dir_file_path) as f:
140
+ step_code = f.readlines()
141
+ # print(f'Found File: {str(each_steps_dir_file_path)}')
142
+ for index, each_step_code_line in enumerate(step_code):
143
+ if each_step_code_line.startswith('@when'):
144
+ comment_start = index + 2
145
+ if '"""' in step_code[comment_start]:
146
+ # print(".. found doc string")
147
+ doc_string_line = comment_start+1
148
+ doc_string = []
149
+ while (True):
150
+ if '"""' in step_code[doc_string_line]:
151
+ break
152
+ doc_string.append(step_code[doc_string_line][indent:])
153
+ doc_string_line += 1
154
+ scenario_line = doc_string_line+1
155
+ if 'scenario_name' not in step_code[scenario_line]:
156
+ print(f'\n** Warning - scenario_name not found '\
157
+ f'in file {str(each_steps_dir_file_path)}, '\
158
+ f'after line {scenario_line} -- skipped')
159
+ else:
160
+ scenario_code_line = step_code[scenario_line]
161
+ scenario_name_start = scenario_code_line.find("'") + 1
162
+ scenario_name_end = scenario_code_line[scenario_name_start+1:].find("'")
163
+ scenario_name = scenario_code_line[scenario_name_start:
164
+ scenario_name_end + scenario_name_start+1]
165
+ if scenario_name == debug_scenario:
166
+ print(f'got {debug_scenario}')
167
+ scenario_trunc = get_truncated_scenario_name(scenario_name)
168
+ # print(f'.... truncated scenario_name: {scenario_trunc} in {scenario_code_line}')
169
+ scenario_doc_strings[scenario_trunc] = doc_string
170
+ # print("that's all, folks")
171
+
172
+
173
+ def main(behave_log: str, scenario_logs: str, wiki: str, prepend_wiki: str):
174
+ """ main driver """
175
+ get_docStrings(steps_dir="features/steps")
176
+
177
+ get_current_readme(prepend_wiki=prepend_wiki)
178
+
179
+ contents = None
180
+ with open(behave_log) as f:
181
+ contents = f.readlines()
182
+
183
+ just_saw_then = False
184
+ current_scenario = ""
185
+ previous_scenario = ""
186
+ for each_line in contents:
187
+ if just_saw_then and each_line == "\n":
188
+ show_logic(scenario=current_scenario, logic_logs_dir=scenario_logs)
189
+ just_saw_then = False
190
+ previous_scenario = ""
191
+ if each_line.startswith("Feature"):
192
+ wiki_data.append("&nbsp;")
193
+ wiki_data.append("&nbsp;")
194
+ each_line = "## " + each_line
195
+ if each_line.startswith(" Scenario"):
196
+ # Before starting new scenario, show logic for previous one if we saw Then
197
+ if just_saw_then and previous_scenario:
198
+ show_logic(scenario=previous_scenario, logic_logs_dir=scenario_logs)
199
+ just_saw_then = False
200
+ each_line = tab + each_line
201
+ if each_line.startswith(" Given") or \
202
+ each_line.startswith(" When") or \
203
+ each_line.startswith(" Then"):
204
+ if each_line.startswith(" Then"):
205
+ just_saw_then = True
206
+ each_line = tab + tab + each_line
207
+
208
+ each_line = each_line[:-1]
209
+ debug_loc = each_line.find(behave_debug_info)
210
+ if debug_loc > 0:
211
+ each_line = each_line[0 : debug_loc]
212
+ each_line = each_line.rstrip()
213
+ if "Scenario" in each_line:
214
+ current_scenario = each_line[18:]
215
+ previous_scenario = current_scenario
216
+ wiki_data.append("&nbsp;")
217
+ wiki_data.append("&nbsp;")
218
+ wiki_data.append("### " + each_line[8:])
219
+
220
+ each_line = each_line + " " # wiki for "new line"
221
+
222
+ wiki_data.append(each_line)
223
+
224
+ # Show logic for the last scenario if we saw Then
225
+ if just_saw_then and current_scenario:
226
+ show_logic(scenario=current_scenario, logic_logs_dir=scenario_logs)
227
+
228
+ with open(wiki, 'w') as rpt:
229
+ rpt.write('\n'.join(wiki_data))
230
+ wiki_full_path = Path(wiki).absolute()
231
+ print(f'Wiki Output: {wiki_full_path}\n\n')
232
+
233
+
234
+
235
+ def print_args(args, msg):
236
+ print(msg)
237
+ for each_arg in args:
238
+ print(f' {each_arg}')
239
+ print(" ")
240
+
241
+
242
+ @click.group()
243
+ @click.pass_context
244
+ def cli(ctx):
245
+ """
246
+ Combine behave.log and scenario_logic_logs to create Behave Logic Report
247
+
248
+ """
249
+ pass
250
+
251
+
252
+ @cli.command("run")
253
+ @click.pass_context
254
+ @click.option('--behave_log',
255
+ default=f'logs/behave.log', # cwd set to test/api_logic_server_behave
256
+ # prompt="Log from behave test suite run [behave.log]",
257
+ help="Help")
258
+ @click.option('--scenario_logs',
259
+ default=f'logs/scenario_logic_logs',
260
+ # prompt="Logic Log directory from ",
261
+ help="Help")
262
+ @click.option('--wiki',
263
+ default=f'reports/Behave Logic Report.md',
264
+ # prompt="Log from behave test suite run [api_logic_server_behave]",
265
+ help="Help")
266
+ @click.option('--prepend_wiki',
267
+ default=f'reports/Behave Logic Report Intro micro.md',
268
+ # prompt="Log from behave test suite run [Behave Logic Report Intro]",
269
+ help="Help")
270
+ def run(ctx, behave_log: str, scenario_logs: str, wiki: str, prepend_wiki: str):
271
+ main(behave_log = behave_log, scenario_logs = scenario_logs, wiki = wiki, prepend_wiki = prepend_wiki)
272
+
273
+
274
+ if __name__ == '__main__': # debugger & python command line start here
275
+ # eg: python api_logic_server_cli/cli.py create --project_name=~/Desktop/test_project
276
+ # unix: python api_logic_server_cli/cli.py create --project_name=/home/ApiLogicProject
277
+
278
+ print(f'\nBehave Logic Report 1.1, started at {os.getcwd()}')
279
+ commands = sys.argv
280
+ if len(sys.argv) > 1:
281
+ print_args(commands, f'\n\nCommand Line Arguments:')
282
+ cli()