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.
- api_logic_server_cli/api_logic_server.py +2 -2
- api_logic_server_cli/prototypes/base/.github/.copilot-instructions.md +22 -0
- api_logic_server_cli/prototypes/base/docs/training/testing.md +21 -9
- api_logic_server_cli/prototypes/base/test/api_logic_server_behave/behave_logic_report.py +55 -29
- api_logic_server_cli/prototypes/base/test/api_logic_server_behave/behave_logic_report.py.bak +285 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/test/api_logic_server_behave/reports/Behave Logic Report Intro micro.md +35 -0
- api_logic_server_cli/prototypes/basic_demo/customizations/test/api_logic_server_behave/reports/Behave Logic Report Intro.md +35 -0
- api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/docs/training/testing.md +210 -12
- api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/behave_logic_report.py +13 -84
- api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/behave_logic_report.py.bak +282 -0
- api_logic_server_cli/prototypes/manager/system/ApiLogicServer-Internal-Dev/copilot-dev-context.md +39 -4
- api_logic_server_cli/prototypes/manager/system/app_model_editor/test/api_logic_server_behave/behave_logic_report.py +13 -75
- api_logic_server_cli/prototypes/manager/system/app_model_editor/test/api_logic_server_behave/behave_logic_report.py.bak +256 -0
- 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
- 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
- api_logic_server_cli/prototypes/nw/test/api_logic_server_behave/reports/Behave Logic Report Intro micro.md +17 -0
- api_logic_server_cli/prototypes/nw/test/api_logic_server_behave/reports/Behave Logic Report Intro.md +17 -0
- {apilogicserver-15.2.0.dist-info → apilogicserver-15.2.3.dist-info}/METADATA +79 -8
- {apilogicserver-15.2.0.dist-info → apilogicserver-15.2.3.dist-info}/RECORD +23 -16
- api_logic_server_cli/prototypes/base/.github/.copilot-instructionsZ.mdx +0 -661
- {apilogicserver-15.2.0.dist-info → apilogicserver-15.2.3.dist-info}/WHEEL +0 -0
- {apilogicserver-15.2.0.dist-info → apilogicserver-15.2.3.dist-info}/entry_points.txt +0 -0
- {apilogicserver-15.2.0.dist-info → apilogicserver-15.2.3.dist-info}/licenses/LICENSE +0 -0
- {apilogicserver-15.2.0.dist-info → apilogicserver-15.2.3.dist-info}/top_level.txt +0 -0
|
@@ -12,10 +12,10 @@ ApiLogicServer CLI: given a database url, create [and run] customizable ApiLogic
|
|
|
12
12
|
Called from api_logic_server_cli.py, by instantiating the ProjectRun object.
|
|
13
13
|
'''
|
|
14
14
|
|
|
15
|
-
__version__ = "15.02.
|
|
15
|
+
__version__ = "15.02.03" # last public release: 15.02.00
|
|
16
16
|
recent_changes = \
|
|
17
17
|
f'\n\nRecent Changes:\n' +\
|
|
18
|
-
"\t10/
|
|
18
|
+
"\t10/22/2025 - 15.02.03: Copilot test creation from rules and custom APIs with issues [103, 104] \n"\
|
|
19
19
|
"\t10/06/2025 - 15.01.06: Incorrect dbml reln arrows [102] \n"\
|
|
20
20
|
"\t09/27/2025 - 15.01.05: Error reporting for bad test data rebuild [101], Minor fix to ensure react-app cards have links \n"\
|
|
21
21
|
"\t08/17/2025 - 15.01.03: LogicBan 01.30, with fix for copy_children[19] \n"\
|
|
@@ -44,6 +44,28 @@ If the user asks "what do I do now?", these subsections are good suggestions.
|
|
|
44
44
|
|
|
45
45
|
List these as choices, do not just do all of them at once - that would be overwhelming.
|
|
46
46
|
|
|
47
|
+
### 📚 Main Services Available in This Project
|
|
48
|
+
|
|
49
|
+
This GenAI-Logic project provides 13 main customization services:
|
|
50
|
+
|
|
51
|
+
1. **Run Project** - F5 debug or `python api_logic_server_run.py`
|
|
52
|
+
2. **Adding Business Logic** - Declarative rules in `logic/declare_logic.py`
|
|
53
|
+
3. **Discovery Systems** - Auto-load logic and APIs from discovery folders
|
|
54
|
+
4. **Automated Testing** - Behave tests with BLT (Business Logic Traceability) reports
|
|
55
|
+
5. **Adding MCP** - Model Context Protocol client integration
|
|
56
|
+
6. **Configuring Admin UI** - Edit `ui/admin/admin.yaml`
|
|
57
|
+
7. **Create React Apps** - Custom UIs with `genai-logic genai-add-app`
|
|
58
|
+
8. **Security - RBAC** - Role-based access control with `als add-auth`
|
|
59
|
+
9. **Custom API Endpoints** - Add routes in `api/customize_api.py`
|
|
60
|
+
10. **B2B Integration APIs** - Complex endpoints with Row Dict Mappers
|
|
61
|
+
11. **Customize Models** - Add tables, attributes, derived fields
|
|
62
|
+
12. **Adding Events** - Row events for Kafka, webhooks, integrations
|
|
63
|
+
13. **Critical Patterns** - React components, null-safe constraints, test repeatability
|
|
64
|
+
|
|
65
|
+
Each service is detailed below with examples and best practices.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
47
69
|
### Set Up Virtual Environment
|
|
48
70
|
If it is not already set, you can often find a `venv` in the GenAI-Logic Manager - a parent or grandparent directory.
|
|
49
71
|
|
|
@@ -25,6 +25,9 @@ Step 5. SUGGEST how to run tests (DO NOT run automatically)
|
|
|
25
25
|
|
|
26
26
|
**CRITICAL PRE-TEST CHECKLIST:**
|
|
27
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!
|
|
28
31
|
- [ ] **Step ordering verified?** (Most specific → Most general)
|
|
29
32
|
- [ ] @when patterns: carbon neutral > multi-item > single-item
|
|
30
33
|
- [ ] @given patterns: multi-item > single-item
|
|
@@ -470,19 +473,28 @@ Rule.sum(derive=Customer.balance, as_sum_of=Order.amount_total,
|
|
|
470
473
|
```python
|
|
471
474
|
# ALWAYS check actual product prices/flags before writing test expectations!
|
|
472
475
|
|
|
473
|
-
# Check prices:
|
|
474
|
-
# sqlite3 db.sqlite "SELECT name, unit_price, carbon_neutral FROM
|
|
476
|
+
# Check prices AND flags:
|
|
477
|
+
# sqlite3 db.sqlite "SELECT name, unit_price, carbon_neutral FROM product;"
|
|
475
478
|
|
|
476
|
-
#
|
|
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)
|
|
477
485
|
|
|
478
|
-
# ❌ WRONG - Assumed Widget
|
|
479
|
-
#
|
|
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
|
|
480
491
|
|
|
481
|
-
# ✅ CORRECT - Verified
|
|
482
|
-
#
|
|
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
|
|
483
496
|
|
|
484
|
-
#
|
|
485
|
-
# Expected: 10 * 90 * 0.9 = 810
|
|
497
|
+
# CRITICAL: Don't assume product attributes - VERIFY with SQL first!
|
|
486
498
|
```
|
|
487
499
|
|
|
488
500
|
### Rule #11: Step Definitions Must Match Feature Files ⚠️ NEW
|
|
@@ -95,14 +95,43 @@ def show_logic(scenario: str, logic_logs_dir: str):
|
|
|
95
95
|
last_rules_start = -1
|
|
96
96
|
last_rules_end = -1
|
|
97
97
|
|
|
98
|
-
#
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
98
|
+
# Find ALL "These Rules Fired" sections and choose the one with the most rules
|
|
99
|
+
rules_sections = []
|
|
100
|
+
i = 0
|
|
101
|
+
while i < len(logic_lines):
|
|
102
|
+
if "These Rules Fired" in logic_lines[i]:
|
|
103
|
+
start_pos = i + 1
|
|
104
|
+
# Find the end of this rules section
|
|
105
|
+
end_pos = start_pos
|
|
106
|
+
while end_pos < len(logic_lines):
|
|
107
|
+
if 'Logic Phase:' in logic_lines[end_pos] and 'COMPLETE' in logic_lines[end_pos]:
|
|
108
|
+
break
|
|
109
|
+
end_pos += 1
|
|
110
|
+
|
|
111
|
+
# Count non-empty rule lines in this section (after removing trailers)
|
|
112
|
+
rule_count = 0
|
|
113
|
+
for j in range(start_pos, end_pos):
|
|
114
|
+
line = remove_trailer(logic_lines[j]).strip()
|
|
115
|
+
if line and not line.startswith('Logic Phase:'):
|
|
116
|
+
rule_count += 1
|
|
117
|
+
|
|
118
|
+
rules_sections.append({
|
|
119
|
+
'start': start_pos,
|
|
120
|
+
'end': end_pos,
|
|
121
|
+
'rule_count': rule_count
|
|
122
|
+
})
|
|
123
|
+
i = end_pos
|
|
124
|
+
else:
|
|
125
|
+
i += 1
|
|
126
|
+
|
|
127
|
+
# Choose the section with the most rules
|
|
128
|
+
if rules_sections:
|
|
129
|
+
best_section = max(rules_sections, key=lambda x: x['rule_count'])
|
|
130
|
+
last_rules_start = best_section['start']
|
|
131
|
+
last_rules_end = best_section['end']
|
|
132
|
+
else:
|
|
133
|
+
last_rules_start = -1
|
|
134
|
+
last_rules_end = -1
|
|
106
135
|
|
|
107
136
|
# Now process the file, collecting logic log and extracting the last rules section
|
|
108
137
|
for i, each_logic_line in enumerate(logic_lines):
|
|
@@ -182,36 +211,27 @@ def main(behave_log: str, scenario_logs: str, wiki: str, prepend_wiki: str):
|
|
|
182
211
|
|
|
183
212
|
just_saw_then = False
|
|
184
213
|
current_scenario = ""
|
|
214
|
+
previous_scenario = ""
|
|
185
215
|
for each_line in contents:
|
|
186
|
-
|
|
187
|
-
if just_saw_then and (each_line == "\n" or each_line.startswith(" Scenario")):
|
|
216
|
+
if just_saw_then and each_line == "\n":
|
|
188
217
|
show_logic(scenario=current_scenario, logic_logs_dir=scenario_logs)
|
|
189
218
|
just_saw_then = False
|
|
190
|
-
|
|
219
|
+
previous_scenario = ""
|
|
191
220
|
if each_line.startswith("Feature"):
|
|
192
221
|
wiki_data.append(" ")
|
|
193
222
|
wiki_data.append(" ")
|
|
194
223
|
each_line = "## " + each_line
|
|
195
|
-
|
|
196
224
|
if each_line.startswith(" Scenario"):
|
|
197
|
-
#
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
wiki_data.append("### " + each_line[2:]) # Add scenario header
|
|
225
|
+
# Before starting new scenario, show logic for previous one if we saw Then
|
|
226
|
+
if just_saw_then and previous_scenario:
|
|
227
|
+
show_logic(scenario=previous_scenario, logic_logs_dir=scenario_logs)
|
|
228
|
+
just_saw_then = False
|
|
202
229
|
each_line = tab + each_line
|
|
203
|
-
|
|
204
230
|
if each_line.startswith(" Given") or \
|
|
205
231
|
each_line.startswith(" When") or \
|
|
206
|
-
each_line.startswith(" Then")
|
|
207
|
-
|
|
208
|
-
if each_line.startswith(" Then") or each_line.startswith(" And"):
|
|
232
|
+
each_line.startswith(" Then"):
|
|
233
|
+
if each_line.startswith(" Then"):
|
|
209
234
|
just_saw_then = True
|
|
210
|
-
# Add subtle formatting to keywords
|
|
211
|
-
for keyword in ["Given", "When", "Then", "And"]:
|
|
212
|
-
if f" {keyword} " in each_line:
|
|
213
|
-
each_line = each_line.replace(f" {keyword} ", f" **{keyword}** ")
|
|
214
|
-
break
|
|
215
235
|
each_line = tab + tab + each_line
|
|
216
236
|
|
|
217
237
|
each_line = each_line[:-1]
|
|
@@ -219,13 +239,19 @@ def main(behave_log: str, scenario_logs: str, wiki: str, prepend_wiki: str):
|
|
|
219
239
|
if debug_loc > 0:
|
|
220
240
|
each_line = each_line[0 : debug_loc]
|
|
221
241
|
each_line = each_line.rstrip()
|
|
242
|
+
if "Scenario" in each_line:
|
|
243
|
+
current_scenario = each_line[18:]
|
|
244
|
+
previous_scenario = current_scenario
|
|
245
|
+
wiki_data.append(" ")
|
|
246
|
+
wiki_data.append(" ")
|
|
247
|
+
wiki_data.append("### " + each_line[8:])
|
|
222
248
|
|
|
223
249
|
each_line = each_line + " " # wiki for "new line"
|
|
224
250
|
|
|
225
251
|
wiki_data.append(each_line)
|
|
226
|
-
|
|
227
|
-
# Show logic for the last scenario
|
|
228
|
-
if
|
|
252
|
+
|
|
253
|
+
# Show logic for the last scenario if we saw Then
|
|
254
|
+
if just_saw_then and current_scenario:
|
|
229
255
|
show_logic(scenario=current_scenario, logic_logs_dir=scenario_logs)
|
|
230
256
|
|
|
231
257
|
with open(wiki, 'w') as rpt:
|
|
@@ -0,0 +1,285 @@
|
|
|
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
|
+
for each_line in contents:
|
|
186
|
+
# Show logic when we hit a blank line after assertions OR when starting a new scenario
|
|
187
|
+
if just_saw_then and (each_line == "\n" or each_line.startswith(" Scenario")):
|
|
188
|
+
show_logic(scenario=current_scenario, logic_logs_dir=scenario_logs)
|
|
189
|
+
just_saw_then = False
|
|
190
|
+
|
|
191
|
+
if each_line.startswith("Feature"):
|
|
192
|
+
wiki_data.append(" ")
|
|
193
|
+
wiki_data.append(" ")
|
|
194
|
+
each_line = "## " + each_line
|
|
195
|
+
|
|
196
|
+
if each_line.startswith(" Scenario"):
|
|
197
|
+
# Extract scenario name for logic lookup
|
|
198
|
+
current_scenario = each_line.strip().replace("Scenario: ", "").replace(" ", " ").strip()
|
|
199
|
+
wiki_data.append(" ")
|
|
200
|
+
wiki_data.append(" ")
|
|
201
|
+
wiki_data.append("### " + each_line[2:]) # Add scenario header
|
|
202
|
+
each_line = tab + each_line
|
|
203
|
+
|
|
204
|
+
if each_line.startswith(" Given") or \
|
|
205
|
+
each_line.startswith(" When") or \
|
|
206
|
+
each_line.startswith(" Then") or \
|
|
207
|
+
each_line.startswith(" And"):
|
|
208
|
+
if each_line.startswith(" Then") or each_line.startswith(" And"):
|
|
209
|
+
just_saw_then = True
|
|
210
|
+
# Add subtle formatting to keywords
|
|
211
|
+
for keyword in ["Given", "When", "Then", "And"]:
|
|
212
|
+
if f" {keyword} " in each_line:
|
|
213
|
+
each_line = each_line.replace(f" {keyword} ", f" **{keyword}** ")
|
|
214
|
+
break
|
|
215
|
+
each_line = tab + tab + each_line
|
|
216
|
+
|
|
217
|
+
each_line = each_line[:-1]
|
|
218
|
+
debug_loc = each_line.find(behave_debug_info)
|
|
219
|
+
if debug_loc > 0:
|
|
220
|
+
each_line = each_line[0 : debug_loc]
|
|
221
|
+
each_line = each_line.rstrip()
|
|
222
|
+
|
|
223
|
+
each_line = each_line + " " # wiki for "new line"
|
|
224
|
+
|
|
225
|
+
wiki_data.append(each_line)
|
|
226
|
+
|
|
227
|
+
# Show logic for the last scenario
|
|
228
|
+
if current_scenario and just_saw_then:
|
|
229
|
+
show_logic(scenario=current_scenario, logic_logs_dir=scenario_logs)
|
|
230
|
+
|
|
231
|
+
with open(wiki, 'w') as rpt:
|
|
232
|
+
rpt.write('\n'.join(wiki_data))
|
|
233
|
+
wiki_full_path = Path(wiki).absolute()
|
|
234
|
+
print(f'Wiki Output: {wiki_full_path}\n\n')
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def print_args(args, msg):
|
|
239
|
+
print(msg)
|
|
240
|
+
for each_arg in args:
|
|
241
|
+
print(f' {each_arg}')
|
|
242
|
+
print(" ")
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@click.group()
|
|
246
|
+
@click.pass_context
|
|
247
|
+
def cli(ctx):
|
|
248
|
+
"""
|
|
249
|
+
Combine behave.log and scenario_logic_logs to create Behave Logic Report
|
|
250
|
+
|
|
251
|
+
"""
|
|
252
|
+
pass
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
@cli.command("run")
|
|
256
|
+
@click.pass_context
|
|
257
|
+
@click.option('--behave_log',
|
|
258
|
+
default=f'logs/behave.log', # cwd set to test/api_logic_server_behave
|
|
259
|
+
# prompt="Log from behave test suite run [behave.log]",
|
|
260
|
+
help="Help")
|
|
261
|
+
@click.option('--scenario_logs',
|
|
262
|
+
default=f'logs/scenario_logic_logs',
|
|
263
|
+
# prompt="Logic Log directory from ",
|
|
264
|
+
help="Help")
|
|
265
|
+
@click.option('--wiki',
|
|
266
|
+
default=f'reports/Behave Logic Report.md',
|
|
267
|
+
# prompt="Log from behave test suite run [api_logic_server_behave]",
|
|
268
|
+
help="Help")
|
|
269
|
+
@click.option('--prepend_wiki',
|
|
270
|
+
default=f'reports/Behave Logic Report Intro micro.md',
|
|
271
|
+
# prompt="Log from behave test suite run [Behave Logic Report Intro]",
|
|
272
|
+
help="Help")
|
|
273
|
+
def run(ctx, behave_log: str, scenario_logs: str, wiki: str, prepend_wiki: str):
|
|
274
|
+
main(behave_log = behave_log, scenario_logs = scenario_logs, wiki = wiki, prepend_wiki = prepend_wiki)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
if __name__ == '__main__': # debugger & python command line start here
|
|
278
|
+
# eg: python api_logic_server_cli/cli.py create --project_name=~/Desktop/test_project
|
|
279
|
+
# unix: python api_logic_server_cli/cli.py create --project_name=/home/ApiLogicProject
|
|
280
|
+
|
|
281
|
+
print(f'\nBehave Logic Report 1.1, started at {os.getcwd()}')
|
|
282
|
+
commands = sys.argv
|
|
283
|
+
if len(sys.argv) > 1:
|
|
284
|
+
print_args(commands, f'\n\nCommand Line Arguments:')
|
|
285
|
+
cli()
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
## Basic Demo Sample
|
|
2
|
+
|
|
3
|
+
This is a basic demonstration project created from a simple natural language prompt using API Logic Server's GenAI capabilities.
|
|
4
|
+
|
|
5
|
+
### Data Model
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
### Creation Prompt
|
|
10
|
+
|
|
11
|
+
This project was created from the following natural language prompt:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
Create a system with customers, orders, items and products.
|
|
15
|
+
|
|
16
|
+
Include a notes field for orders.
|
|
17
|
+
|
|
18
|
+
Use case: Check Credit
|
|
19
|
+
1. The Customer's balance is less than the credit limit
|
|
20
|
+
2. The Customer's balance is the sum of the Order amount_total where date_shipped is null
|
|
21
|
+
3. The Order's amount_total is the sum of the Item amount
|
|
22
|
+
4. The Item amount is the quantity * unit_price
|
|
23
|
+
5. The Item unit_price is copied from the Product unit_price
|
|
24
|
+
|
|
25
|
+
Use case: App Integration
|
|
26
|
+
1. Send the Order to Kafka topic 'order_shipping' if the date_shipped is not None.
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The sample Scenarios below were chosen to illustrate the basic patterns of using rules. Open the disclosure box ("Tests - and their logic...") to see the implementation and notes.
|
|
30
|
+
|
|
31
|
+
The following report was created during test suite execution.
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Behave Logic Report
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
## Basic Demo Sample
|
|
2
|
+
|
|
3
|
+
This is a basic demonstration project created from a simple natural language prompt using API Logic Server's GenAI capabilities (with a custom Behave Report Intro).
|
|
4
|
+
|
|
5
|
+
### Data Model
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
### Creation Prompt
|
|
10
|
+
|
|
11
|
+
This project was created from the following natural language prompt:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
Create a system with customers, orders, items and products.
|
|
15
|
+
|
|
16
|
+
Include a notes field for orders.
|
|
17
|
+
|
|
18
|
+
Use case: Check Credit
|
|
19
|
+
1. The Customer's balance is less than the credit limit
|
|
20
|
+
2. The Customer's balance is the sum of the Order amount_total where date_shipped is null
|
|
21
|
+
3. The Order's amount_total is the sum of the Item amount
|
|
22
|
+
4. The Item amount is the quantity * unit_price
|
|
23
|
+
5. The Item unit_price is copied from the Product unit_price
|
|
24
|
+
|
|
25
|
+
Use case: App Integration
|
|
26
|
+
1. Send the Order to Kafka topic 'order_shipping' if the date_shipped is not None.
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The sample Scenarios below were chosen to illustrate the basic patterns of using rules. Open the disclosure box ("Tests - and their logic...") to see the implementation and notes.
|
|
30
|
+
|
|
31
|
+
The following report was created during test suite execution.
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Behave Logic Report
|