ApiLogicServer 15.2.0__py3-none-any.whl → 15.2.7__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 (43) hide show
  1. api_logic_server_cli/api_logic_server.py +3 -2
  2. api_logic_server_cli/prototypes/base/.github/.copilot-instructions.md +115 -31
  3. api_logic_server_cli/prototypes/base/docs/training/testing.md +116 -18
  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/{base/.github/.copilot-instructionsZ.mdx → basic_demo/.github/.copilot-instructions.md} +111 -30
  7. api_logic_server_cli/prototypes/basic_demo/customizations/test/api_logic_server_behave/reports/Behave Logic Report Intro micro.md +35 -0
  8. api_logic_server_cli/prototypes/basic_demo/customizations/test/api_logic_server_behave/reports/Behave Logic Report Intro.md +35 -0
  9. api_logic_server_cli/prototypes/basic_demo/readme.md +12 -4
  10. api_logic_server_cli/prototypes/basic_demo/tutor.md +1196 -0
  11. api_logic_server_cli/prototypes/manager/.github/.copilot-instructions.md +50 -23
  12. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/.github/.copilot-instructions.md +3 -0
  13. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/docs/training/testing.md +305 -21
  14. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/behave_logic_report.py +13 -84
  15. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/behave_logic_report.py.bak +282 -0
  16. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/features/order_processing.feature +59 -50
  17. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/features/steps/order_processing_steps.py +395 -248
  18. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/logs/behave.log +66 -62
  19. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/logs/scenario_logic_logs/Carbon_Neutral_Discount_A.log +51 -41
  20. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/logs/scenario_logic_logs/Change_Order_Customer.log +29 -0
  21. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/logs/scenario_logic_logs/Change_Product_in_Item.log +35 -0
  22. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/logs/scenario_logic_logs/Delete_Item_Reduces_Order.log +39 -19
  23. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/logs/scenario_logic_logs/Exceed_Credit_Limit_Rejec.log +36 -45
  24. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/logs/scenario_logic_logs/Good_Order_Placed_via_B2B.log +50 -40
  25. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/logs/scenario_logic_logs/Item_Quantity_Change.log +33 -0
  26. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/logs/scenario_logic_logs/Multi-Item_Order_via_B2B_.log +67 -0
  27. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/logs/scenario_logic_logs/Ship_Order_Excludes_from_.log +24 -14
  28. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/logs/scenario_logic_logs/Transaction_Processing.log +26 -17
  29. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/logs/scenario_logic_logs/Unship_Order_Includes_in_.log +24 -14
  30. api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/reports/Behave Logic Report.md +361 -146
  31. api_logic_server_cli/prototypes/manager/system/ApiLogicServer-Internal-Dev/copilot-dev-context.md +275 -4
  32. api_logic_server_cli/prototypes/manager/system/app_model_editor/test/api_logic_server_behave/behave_logic_report.py +13 -75
  33. api_logic_server_cli/prototypes/manager/system/app_model_editor/test/api_logic_server_behave/behave_logic_report.py.bak +256 -0
  34. 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
  35. 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
  36. api_logic_server_cli/prototypes/nw/test/api_logic_server_behave/reports/Behave Logic Report Intro micro.md +17 -0
  37. api_logic_server_cli/prototypes/nw/test/api_logic_server_behave/reports/Behave Logic Report Intro.md +17 -0
  38. {apilogicserver-15.2.0.dist-info → apilogicserver-15.2.7.dist-info}/METADATA +103 -23
  39. {apilogicserver-15.2.0.dist-info → apilogicserver-15.2.7.dist-info}/RECORD +43 -30
  40. {apilogicserver-15.2.0.dist-info → apilogicserver-15.2.7.dist-info}/WHEEL +0 -0
  41. {apilogicserver-15.2.0.dist-info → apilogicserver-15.2.7.dist-info}/entry_points.txt +0 -0
  42. {apilogicserver-15.2.0.dist-info → apilogicserver-15.2.7.dist-info}/licenses/LICENSE +0 -0
  43. {apilogicserver-15.2.0.dist-info → apilogicserver-15.2.7.dist-info}/top_level.txt +0 -0
@@ -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()
@@ -1,72 +1,81 @@
1
1
  Feature: Order Processing with Business Logic
2
2
 
3
- # Phase 2: CREATE using OrderB2B custom API
4
- # Phase 1: UPDATE/DELETE using CRUD for granular rule testing
3
+ # Tests all declarative rules:
4
+ # 1. Customer.balance = sum(Order.amount_total) WHERE date_shipped is None
5
+ # 2. Order.amount_total = sum(Item.amount)
6
+ # 3. Item.amount = quantity * unit_price (with 10% carbon neutral discount for qty >= 10)
7
+ # 4. Item.unit_price copied from Product.unit_price
8
+ # 5. Customer.balance <= credit_limit constraint
9
+ # 6. Kafka integration when order shipped
5
10
 
6
11
  Scenario: Good Order Placed via B2B API
7
- Given Customer "Alice" with balance 0 and credit limit 1000
12
+ Given Customer "Alice" with balance 0 and credit limit 5000
8
13
  When B2B order placed for "Alice" with 5 Widget
9
14
  Then Customer balance should be 450
10
15
  And Order amount_total should be 450
11
- And Order created successfully
16
+ And Item amount should be 450
17
+ And Item unit_price should be 90
12
18
 
13
- Scenario: Item Quantity Update Recalculates Amounts
14
- Given Customer "Bob" with balance 0 and credit limit 2000
15
- And Order exists for "Bob" with 5 Widget
19
+ Scenario: Multi-Item Order via B2B API
20
+ Given Customer "Bob" with balance 0 and credit limit 3000
21
+ When B2B order placed for "Bob" with 3 Widget and 2 Gadget
22
+ Then Customer balance should be 570
23
+ And Order amount_total should be 570
24
+
25
+ Scenario: Carbon Neutral Discount Applied
26
+ Given Customer "Diana" with balance 0 and credit limit 5000
27
+ When B2B order placed for "Diana" with 10 carbon neutral Gadget
28
+ Then Customer balance should be 1350
29
+ And Item amount should be 1350
30
+
31
+ Scenario: Item Quantity Change
32
+ Given Customer "Charlie" with balance 0 and credit limit 2000
33
+ And Order is created for "Charlie" with 5 Widget
16
34
  When Item quantity changed to 10
17
35
  Then Item amount should be 900
18
36
  And Order amount_total should be 900
19
37
  And Customer balance should be 900
20
38
 
21
- Scenario: Change Order Customer Adjusts Both Balances
22
- Given Customer "Charlie" with balance 0 and credit limit 1500
23
- And Customer "Diana" with balance 0 and credit limit 2000
24
- And Order exists for "Charlie" with 2 Gadget
25
- When Order customer changed to "Diana"
26
- Then Customer "Charlie" balance should be 0
27
- And Customer "Diana" balance should be 300
39
+ Scenario: Change Product in Item
40
+ Given Customer "Alice" with balance 0 and credit limit 5000
41
+ And Order is created for "Alice" with 5 Widget
42
+ When Item product changed to "Gadget"
43
+ Then Item unit_price should be 150
44
+ And Item amount should be 750
45
+ And Order amount_total should be 750
46
+ And Customer balance should be 750
28
47
 
29
- Scenario: Delete Item Reduces Order Total and Customer Balance
30
- Given Customer "TestCustomer" with balance 0 and credit limit 3000
31
- And Order exists for "TestCustomer" with 3 Widget and 2 Gadget
32
- When First item deleted
48
+ Scenario: Delete Item Reduces Order
49
+ Given Customer "Bob" with balance 0 and credit limit 3000
50
+ And Order is created for "Bob" with 3 Widget and 2 Gadget
51
+ When First item is deleted
33
52
  Then Order amount_total should be 300
34
53
  And Customer balance should be 300
35
54
 
36
- Scenario: Ship Order Excludes from Balance (WHERE exclude)
37
- Given Customer "ShipTest" with balance 0 and credit limit 5000
38
- And Order exists for "ShipTest" with 10 Widget
55
+ Scenario: Change Order Customer
56
+ Given Customer "Alice" with balance 0 and credit limit 5000
57
+ And Customer "Bob" with balance 0 and credit limit 3000
58
+ And Order is created for "Alice" with 5 Widget
59
+ When Order customer changed to "Bob"
60
+ Then Customer "Alice" balance should be 0
61
+ And Customer "Bob" balance should be 450
62
+
63
+ Scenario: Ship Order Excludes from Balance
64
+ Given Customer "Charlie" with balance 0 and credit limit 2000
65
+ And Order is created for "Charlie" with 2 Widget
39
66
  When Order is shipped
40
67
  Then Customer balance should be 0
68
+ And Order amount_total should be 180
41
69
 
42
- Scenario: Unship Order Includes in Balance (WHERE include)
43
- Given Customer "UnshipTest" with balance 0 and credit limit 5000
44
- And Shipped order exists for "UnshipTest" with 5 Gadget
70
+ Scenario: Unship Order Includes in Balance
71
+ Given Customer "Diana" with balance 0 and credit limit 5000
72
+ And Shipped order is created for "Diana" with 3 Gadget
45
73
  When Order is unshipped
46
- Then Customer balance should be 750
47
-
48
- Scenario: Exceed Credit Limit Rejected (Constraint FAIL)
49
- Given Customer "LimitTest" with balance 0 and credit limit 500
50
- When B2B order placed for "LimitTest" with 10 Gadget
51
- Then Order creation should fail
52
- And Error message should contain "credit limit"
53
-
54
- Scenario: Carbon Neutral Discount Applied (Custom Logic)
55
- Given Customer "GreenBuyer" with balance 0 and credit limit 2000
56
- When B2B order placed for "GreenBuyer" with 10 carbon neutral Gadget
57
- Then Item amount should be 1350
58
- And Order amount_total should be 1350
59
- And Customer balance should be 1350
60
-
61
- Scenario: Product Unit Price Copied to Item
62
- Given Customer "PriceCopy" with balance 0 and credit limit 3000
63
- When B2B order placed for "PriceCopy" with 1 Green
64
- Then Item unit_price should be 109
74
+ Then Customer balance should be 450
75
+ And Order amount_total should be 450
65
76
 
66
- Scenario: Change Product Updates Unit Price (FK Change)
67
- Given Customer "ProductChange" with balance 0 and credit limit 5000
68
- And Order exists for "ProductChange" with 2 Widget
69
- When Item product changed to "Gadget"
70
- Then Item unit_price should be 150
71
- And Item amount should be 300
72
- And Order amount_total should be 300
77
+ Scenario: Exceed Credit Limit Rejected
78
+ Given Customer "Silent" with balance 0 and credit limit 1000
79
+ When B2B order placed for "Silent" with 20 Widget
80
+ Then Order should be rejected
81
+ And Error message should contain "exceeds credit limit"