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.
- api_logic_server_cli/api_logic_server.py +3 -2
- api_logic_server_cli/prototypes/base/.github/.copilot-instructions.md +115 -31
- api_logic_server_cli/prototypes/base/docs/training/testing.md +116 -18
- 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/{base/.github/.copilot-instructionsZ.mdx → basic_demo/.github/.copilot-instructions.md} +111 -30
- 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/basic_demo/readme.md +12 -4
- api_logic_server_cli/prototypes/basic_demo/tutor.md +1196 -0
- api_logic_server_cli/prototypes/manager/.github/.copilot-instructions.md +50 -23
- api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/.github/.copilot-instructions.md +3 -0
- api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/docs/training/testing.md +305 -21
- 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/samples/basic_demo_sample/test/api_logic_server_behave/features/order_processing.feature +59 -50
- api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/features/steps/order_processing_steps.py +395 -248
- api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/logs/behave.log +66 -62
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/logs/scenario_logic_logs/Transaction_Processing.log +26 -17
- 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
- api_logic_server_cli/prototypes/manager/samples/basic_demo_sample/test/api_logic_server_behave/reports/Behave Logic Report.md +361 -146
- api_logic_server_cli/prototypes/manager/system/ApiLogicServer-Internal-Dev/copilot-dev-context.md +275 -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.7.dist-info}/METADATA +103 -23
- {apilogicserver-15.2.0.dist-info → apilogicserver-15.2.7.dist-info}/RECORD +43 -30
- {apilogicserver-15.2.0.dist-info → apilogicserver-15.2.7.dist-info}/WHEEL +0 -0
- {apilogicserver-15.2.0.dist-info → apilogicserver-15.2.7.dist-info}/entry_points.txt +0 -0
- {apilogicserver-15.2.0.dist-info → apilogicserver-15.2.7.dist-info}/licenses/LICENSE +0 -0
- {apilogicserver-15.2.0.dist-info → apilogicserver-15.2.7.dist-info}/top_level.txt +0 -0
|
@@ -12,10 +12,11 @@ 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.07" # last public release: 15.02.03
|
|
16
16
|
recent_changes = \
|
|
17
17
|
f'\n\nRecent Changes:\n' +\
|
|
18
|
-
"\t10/
|
|
18
|
+
"\t10/26/2025 - 15.02.07: Clarify order created for ship test, security fixes [105], tutor 2.1 \n"\
|
|
19
|
+
"\t10/22/2025 - 15.02.03: Copilot test creation from rules and custom APIs with issues [103, 104] \n"\
|
|
19
20
|
"\t10/06/2025 - 15.01.06: Incorrect dbml reln arrows [102] \n"\
|
|
20
21
|
"\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
22
|
"\t08/17/2025 - 15.01.03: LogicBan 01.30, with fix for copy_children[19] \n"\
|
|
@@ -1,11 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Copilot Instructions for GenAI-Logic Projects
|
|
3
|
+
Description: Project-level instructions for working with generated projects
|
|
4
|
+
Source: ApiLogicServer-src/prototypes/base/.github/.copilot-instructions.md
|
|
5
|
+
Propagation: CLI create command → created projects (non-basic_demo)
|
|
6
|
+
Usage: AI assistants read this when user opens any created project
|
|
7
|
+
version: 2.1
|
|
8
|
+
changelog:
|
|
9
|
+
- 2.1 (Oct 2025) - Streamlined OBX for non-tutorial projects, friendly collaborative tone
|
|
10
|
+
- 2.0 (Oct 2025) - Added front matter, updated for OBX improvements
|
|
11
|
+
- 1.0 (Initial) - Comprehensive technical reference
|
|
12
|
+
---
|
|
1
13
|
|
|
14
|
+
# GitHub Copilot Instructions for GenAI-Logic (aka API Logic Server) Projects
|
|
2
15
|
|
|
16
|
+
⚠️ **IMPORTANT FOR AI ASSISTANTS**: When user first opens this project or asks you to read instructions, present the Welcome section below directly to the user. Do not explain that you're following instructions - just present the welcome message naturally. The technical reference sections after the welcome are for later consultation only.
|
|
3
17
|
|
|
4
|
-
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Welcome
|
|
21
|
+
|
|
22
|
+
**Welcome! I'm here to help with your GenAI-Logic project.**
|
|
23
|
+
|
|
24
|
+
This is a complete, working microservice auto-generated from your database schema.
|
|
25
|
+
|
|
26
|
+
**What's already built:**
|
|
27
|
+
- **Admin Web Application** - Live at `http://localhost:5656`
|
|
28
|
+
- **REST API Endpoints** - All database tables exposed at `/api/*`
|
|
29
|
+
- **Database Models** - Complete SQLAlchemy ORM in `database/models.py`
|
|
30
|
+
- **Authentication System** - JWT-based auth framework
|
|
31
|
+
- **Business Logic Engine** - Declarative rules system
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
**Here are some things I can help you with:**
|
|
36
|
+
|
|
37
|
+
1. **Add business logic** - Describe your requirements in natural language, I'll generate declarative rules
|
|
38
|
+
2. **Customize the API** - Add custom endpoints for your specific needs
|
|
39
|
+
3. **Create custom UIs** - Build React apps with `genai-logic genai-add-app --vibe`
|
|
40
|
+
4. **Add security** - Set up role-based access control with `genai-logic add-auth`
|
|
41
|
+
5. **Test your logic** - Create Behave tests with requirements traceability
|
|
42
|
+
6. **Configure Admin UI** - Customize the auto-generated admin interface
|
|
43
|
+
7. **Add MCP integration** - Enable Model Context Protocol for external AI access
|
|
44
|
+
8. **Create B2B APIs** - Complex integration endpoints with partner systems
|
|
45
|
+
9. **Add events** - Integrate with Kafka, webhooks, or other event systems
|
|
46
|
+
10. **Customize models** - Add tables, attributes, or derived fields
|
|
47
|
+
11. **Discovery systems** - Auto-load logic and APIs from discovery folders
|
|
5
48
|
|
|
6
|
-
|
|
49
|
+
Want to get started? Press **F5** to start the server with debugger support, then open `http://localhost:5656`. (There are also run configurations for running tests and other tasks.) Or just ask me what you'd like to work on.
|
|
7
50
|
|
|
8
|
-
|
|
51
|
+
---
|
|
9
52
|
|
|
10
53
|
## 🔑 Key Technical Points
|
|
11
54
|
|
|
@@ -26,32 +69,15 @@ This is a **GenAI-Logic (aka API Logic Server) project** - a complete, working m
|
|
|
26
69
|
|
|
27
70
|
3. **Automatic Business Logic**: All APIs (standard and custom) automatically inherit LogicBank rules without additional code.
|
|
28
71
|
|
|
29
|
-
|
|
72
|
+
4. **CLI Commands**: Use `genai-logic --help` to see all available commands. When CLI commands exist for a task (e.g., `add-auth`, `genai-add-mcp-client`, `genai-add-app`), ALWAYS use them instead of manual configuration - they handle all setup correctly.
|
|
30
73
|
|
|
31
|
-
|
|
74
|
+
> **📋 Testing:** For comprehensive testing conventions, patterns, and examples, see `docs/training/testing.md` (555 lines - I'll read this before we create any tests)
|
|
32
75
|
|
|
33
|
-
|
|
34
|
-
2. **REST API Endpoints** - All database tables exposed at `/api/*`
|
|
35
|
-
3. **Database Models** - Complete SQLAlchemy ORM in `database/models.py`
|
|
36
|
-
4. **Authentication System** - JWT-based auth framework
|
|
37
|
-
5. **Business Logic Engine** - Declarative rules system
|
|
76
|
+
---
|
|
38
77
|
|
|
39
|
-
|
|
78
|
+
## Detailed Service Documentation
|
|
40
79
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
If the user asks "what do I do now?", these subsections are good suggestions.
|
|
44
|
-
|
|
45
|
-
List these as choices, do not just do all of them at once - that would be overwhelming.
|
|
46
|
-
|
|
47
|
-
### Set Up Virtual Environment
|
|
48
|
-
If it is not already set, you can often find a `venv` in the GenAI-Logic Manager - a parent or grandparent directory.
|
|
49
|
-
|
|
50
|
-
### 🚀 To Run This Project
|
|
51
|
-
|
|
52
|
-
Use F5 (pre-configured run configuration - supports debugger for server logic), or (when focused on client apps):
|
|
53
|
-
|
|
54
|
-
```bash
|
|
80
|
+
The sections below provide complete details on each service. I'll reference these as needed when we work together.
|
|
55
81
|
python api_logic_server_run.py
|
|
56
82
|
# Then open: http://localhost:5656
|
|
57
83
|
```
|
|
@@ -395,20 +421,20 @@ Customize using CoPilot chat, with `docs/training`.
|
|
|
395
421
|
|
|
396
422
|
Configure:
|
|
397
423
|
```
|
|
398
|
-
|
|
399
|
-
|
|
424
|
+
genai-logic add-auth --provider-type=sql --db-url=
|
|
425
|
+
genai-logic add-auth --provider-type=sql --db_url=postgresql://postgres:p@localhost/authdb
|
|
400
426
|
|
|
401
|
-
|
|
402
|
-
|
|
427
|
+
genai-logic add-auth --provider-type=keycloak --db-url=localhost
|
|
428
|
+
genai-logic add-auth --provider-type=keycloak --db-url=hardened
|
|
403
429
|
|
|
404
|
-
|
|
430
|
+
genai-logic add-auth --provider-type=None # to disable
|
|
405
431
|
```
|
|
406
432
|
|
|
407
433
|
Keycloak quick start [(more information here:)](https://apilogicserver.github.io/Docs/Security-Keycloak/)
|
|
408
434
|
```bash
|
|
409
435
|
cd devops/keycloak
|
|
410
436
|
docker compose up
|
|
411
|
-
|
|
437
|
+
genai-logic add-auth --provider-type=keycloak --db-url=localhost
|
|
412
438
|
```
|
|
413
439
|
|
|
414
440
|
For more on KeyCloak: https://apilogicserver.github.io/Docs/Security-Keycloak/
|
|
@@ -419,6 +445,64 @@ Declaration:
|
|
|
419
445
|
Grant(on_entity=Customer, to_role=sales, filter=lambda: Customer.SalesRep == current_user())
|
|
420
446
|
```
|
|
421
447
|
|
|
448
|
+
|
|
449
|
+
#### Testing with Security Enabled
|
|
450
|
+
|
|
451
|
+
**CRITICAL:** When `SECURITY_ENABLED=True`, test code must obtain and include JWT authentication tokens.
|
|
452
|
+
|
|
453
|
+
**Pattern for test steps:**
|
|
454
|
+
```python
|
|
455
|
+
from pathlib import Path
|
|
456
|
+
import os
|
|
457
|
+
from dotenv import load_dotenv
|
|
458
|
+
|
|
459
|
+
# Load config to check SECURITY_ENABLED
|
|
460
|
+
config_path = Path(__file__).parent.parent.parent.parent.parent / 'config' / 'default.env'
|
|
461
|
+
load_dotenv(config_path)
|
|
462
|
+
|
|
463
|
+
# Cache for auth token (obtained once per test session)
|
|
464
|
+
_auth_token = None
|
|
465
|
+
|
|
466
|
+
def get_auth_token():
|
|
467
|
+
"""Login and get JWT token if security is enabled"""
|
|
468
|
+
global _auth_token
|
|
469
|
+
|
|
470
|
+
if _auth_token is not None:
|
|
471
|
+
return _auth_token
|
|
472
|
+
|
|
473
|
+
# Login with default admin credentials
|
|
474
|
+
login_url = f'{BASE_URL}/api/auth/login'
|
|
475
|
+
login_data = {'username': 'admin', 'password': 'p'}
|
|
476
|
+
|
|
477
|
+
response = requests.post(login_url, json=login_data)
|
|
478
|
+
if response.status_code == 200:
|
|
479
|
+
_auth_token = response.json().get('access_token')
|
|
480
|
+
return _auth_token
|
|
481
|
+
else:
|
|
482
|
+
raise Exception(f"Login failed: {response.status_code}")
|
|
483
|
+
|
|
484
|
+
def get_headers():
|
|
485
|
+
"""Get headers including auth token if security is enabled"""
|
|
486
|
+
security_enabled = os.getenv('SECURITY_ENABLED', 'false').lower() not in ['false', 'no']
|
|
487
|
+
|
|
488
|
+
headers = {'Content-Type': 'application/json'}
|
|
489
|
+
|
|
490
|
+
if security_enabled:
|
|
491
|
+
token = get_auth_token()
|
|
492
|
+
if token:
|
|
493
|
+
headers['Authorization'] = f'Bearer {token}'
|
|
494
|
+
|
|
495
|
+
return headers
|
|
496
|
+
|
|
497
|
+
# Use in all API requests
|
|
498
|
+
response = requests.post(url=api_url, json=data, headers=get_headers())
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
**Key points:**
|
|
502
|
+
- Tests DO NOT automatically include auth headers - you must code this pattern
|
|
503
|
+
- Token is cached to avoid repeated logins during test session
|
|
504
|
+
- Pattern works for both `SECURITY_ENABLED=True` and `SECURITY_ENABLED=False`
|
|
505
|
+
- See `test/api_logic_server_behave/features/steps/order_processing_steps.py` for complete example
|
|
422
506
|
### Adding Custom API Endpoints
|
|
423
507
|
|
|
424
508
|
For simple endpoints:
|
|
@@ -25,10 +25,16 @@ 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
|
|
31
34
|
- [ ] Use `grep -n "@when('.*with" steps/*.py` to verify order
|
|
35
|
+
- [ ] **Clear scenario language?** (Rule #13: Action-oriented wording)
|
|
36
|
+
- [ ] Use "Order is created" not "Order exists" (shows when rules fire)
|
|
37
|
+
- [ ] Makes test flow obvious: setup → action → verify
|
|
32
38
|
- [ ] Test data uses timestamps? (Rule #0: Repeatability)
|
|
33
39
|
- [ ] Security config read? (SECURITY_ENABLED value)
|
|
34
40
|
|
|
@@ -164,7 +170,7 @@ def step_impl(context, qty):
|
|
|
164
170
|
r = requests.patch(url=patch_uri, json=patch_data)
|
|
165
171
|
```
|
|
166
172
|
|
|
167
|
-
## The
|
|
173
|
+
## The Critical Rules (Read FIRST!)
|
|
168
174
|
|
|
169
175
|
### Rule #0: TEST REPEATABILITY (MOST CRITICAL!) ⚠️
|
|
170
176
|
|
|
@@ -383,18 +389,65 @@ Scenario: Exceed Credit (Constraint FAIL - negative test)
|
|
|
383
389
|
|
|
384
390
|
**CRITICAL:** Check `logic/declare_logic.py` for custom Python logic affecting calculations (e.g., discounts, adjustments) before writing constraint test expectations!
|
|
385
391
|
|
|
386
|
-
### Rule #5: Security Configuration
|
|
392
|
+
### Rule #5: Security Configuration - JWT Authentication
|
|
393
|
+
|
|
394
|
+
**CRITICAL:** When `SECURITY_ENABLED=True`, tests MUST obtain JWT token and include Authorization header.
|
|
395
|
+
|
|
387
396
|
```python
|
|
388
|
-
|
|
389
|
-
|
|
397
|
+
from pathlib import Path
|
|
398
|
+
import os
|
|
399
|
+
from dotenv import load_dotenv
|
|
390
400
|
|
|
391
|
-
#
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
401
|
+
# Load config to check SECURITY_ENABLED
|
|
402
|
+
config_path = Path(__file__).parent.parent.parent.parent.parent / 'config' / 'default.env'
|
|
403
|
+
load_dotenv(config_path)
|
|
404
|
+
|
|
405
|
+
# Cache for auth token (obtained once per test session)
|
|
406
|
+
_auth_token = None
|
|
407
|
+
|
|
408
|
+
def get_auth_token():
|
|
409
|
+
"""Login and get JWT token if security is enabled"""
|
|
410
|
+
global _auth_token
|
|
411
|
+
|
|
412
|
+
if _auth_token is not None:
|
|
413
|
+
return _auth_token
|
|
414
|
+
|
|
415
|
+
# Login with default admin credentials
|
|
416
|
+
login_url = f'{BASE_URL}/api/auth/login'
|
|
417
|
+
login_data = {'username': 'admin', 'password': 'p'}
|
|
418
|
+
|
|
419
|
+
response = requests.post(login_url, json=login_data)
|
|
420
|
+
if response.status_code == 200:
|
|
421
|
+
_auth_token = response.json().get('access_token')
|
|
422
|
+
return _auth_token
|
|
423
|
+
else:
|
|
424
|
+
raise Exception(f"Login failed: {response.status_code}")
|
|
425
|
+
|
|
426
|
+
def get_headers():
|
|
427
|
+
"""Get headers including auth token if security is enabled"""
|
|
428
|
+
security_enabled = os.getenv('SECURITY_ENABLED', 'false').lower() not in ['false', 'no']
|
|
429
|
+
|
|
430
|
+
headers = {'Content-Type': 'application/json'}
|
|
431
|
+
|
|
432
|
+
if security_enabled:
|
|
433
|
+
token = get_auth_token()
|
|
434
|
+
if token:
|
|
435
|
+
headers['Authorization'] = f'Bearer {token}'
|
|
436
|
+
|
|
437
|
+
return headers
|
|
438
|
+
|
|
439
|
+
# Use in ALL API requests
|
|
440
|
+
response = requests.post(url=api_url, json=data, headers=get_headers())
|
|
441
|
+
response = requests.get(url=api_url, headers=get_headers())
|
|
442
|
+
response = requests.patch(url=api_url, json=data, headers=get_headers())
|
|
396
443
|
```
|
|
397
444
|
|
|
445
|
+
**Key Points:**
|
|
446
|
+
- Tests DO NOT automatically include auth - you must code the pattern above
|
|
447
|
+
- Token is cached globally to avoid repeated login calls
|
|
448
|
+
- Works for both `SECURITY_ENABLED=True` and `SECURITY_ENABLED=False`
|
|
449
|
+
- See complete example: `test/api_logic_server_behave/features/steps/order_processing_steps.py`
|
|
450
|
+
|
|
398
451
|
### Rule #6: Logic Logging
|
|
399
452
|
```python
|
|
400
453
|
@when('Good Order Placed')
|
|
@@ -470,19 +523,28 @@ Rule.sum(derive=Customer.balance, as_sum_of=Order.amount_total,
|
|
|
470
523
|
```python
|
|
471
524
|
# ALWAYS check actual product prices/flags before writing test expectations!
|
|
472
525
|
|
|
473
|
-
# Check prices:
|
|
474
|
-
# sqlite3 db.sqlite "SELECT name, unit_price, carbon_neutral FROM
|
|
526
|
+
# Check prices AND flags:
|
|
527
|
+
# sqlite3 db.sqlite "SELECT name, unit_price, carbon_neutral FROM product;"
|
|
475
528
|
|
|
476
|
-
#
|
|
529
|
+
# Results:
|
|
530
|
+
# 1|Gadget|150|1 ← carbon_neutral = 1 (TRUE)
|
|
531
|
+
# 2|Widget|90| ← carbon_neutral = NULL (not carbon neutral!)
|
|
532
|
+
# 3|Thingamajig|5075|
|
|
533
|
+
# 4|Doodad|110|
|
|
534
|
+
# 5|Green|109|1 ← carbon_neutral = 1 (TRUE)
|
|
477
535
|
|
|
478
|
-
# ❌ WRONG - Assumed Widget
|
|
479
|
-
#
|
|
536
|
+
# ❌ WRONG - Assumed Widget is carbon neutral
|
|
537
|
+
# Scenario: Carbon Neutral Discount
|
|
538
|
+
# When B2B order placed with 10 carbon neutral Widget
|
|
539
|
+
# Then balance should be 810 # Expected 10 * 90 * 0.9 = 810
|
|
540
|
+
# FAILS: Widget is NOT carbon neutral → no discount → balance = 900
|
|
480
541
|
|
|
481
|
-
# ✅ CORRECT - Verified
|
|
482
|
-
#
|
|
542
|
+
# ✅ CORRECT - Verified Gadget IS carbon neutral (flag = 1)
|
|
543
|
+
# Scenario: Carbon Neutral Discount
|
|
544
|
+
# When B2B order placed with 10 carbon neutral Gadget
|
|
545
|
+
# Then balance should be 1350 # Correct: 10 * 150 * 0.9 = 1350
|
|
483
546
|
|
|
484
|
-
#
|
|
485
|
-
# Expected: 10 * 90 * 0.9 = 810
|
|
547
|
+
# CRITICAL: Don't assume product attributes - VERIFY with SQL first!
|
|
486
548
|
```
|
|
487
549
|
|
|
488
550
|
### Rule #11: Step Definitions Must Match Feature Files ⚠️ NEW
|
|
@@ -523,6 +585,40 @@ def step_impl(context):
|
|
|
523
585
|
return # Skip gracefully
|
|
524
586
|
```
|
|
525
587
|
|
|
588
|
+
### Rule #13: Use Clear, Action-Oriented Scenario Language ⚠️ NEW
|
|
589
|
+
```gherkin
|
|
590
|
+
# ❌ AMBIGUOUS - "exists" is passive, doesn't show when balance changes
|
|
591
|
+
Scenario: Ship Order Excludes from Balance
|
|
592
|
+
Given Customer "Charlie" with balance 0 and credit limit 2000
|
|
593
|
+
And Order exists for "Charlie" with 2 Widget
|
|
594
|
+
When Order is shipped
|
|
595
|
+
Then Customer balance should be 0
|
|
596
|
+
|
|
597
|
+
# Question: How can Charlie have balance 0 AND an order for widgets?
|
|
598
|
+
# Answer: The order EXISTS at test start, but balance calculation is unclear
|
|
599
|
+
|
|
600
|
+
# ✅ CLEAR - "is created" shows action that triggers rule
|
|
601
|
+
Scenario: Ship Order Excludes from Balance
|
|
602
|
+
Given Customer "Charlie" with balance 0 and credit limit 2000
|
|
603
|
+
And Order is created for "Charlie" with 2 Widget # Action! Balance becomes 180
|
|
604
|
+
When Order is shipped # Action! Balance drops to 0
|
|
605
|
+
Then Customer balance should be 0 # Verification
|
|
606
|
+
|
|
607
|
+
# Now it's obvious:
|
|
608
|
+
# 1. Customer starts with balance 0
|
|
609
|
+
# 2. Creating unshipped order → rule fires → balance becomes 180
|
|
610
|
+
# 3. Shipping order → WHERE clause excludes it → balance drops to 0
|
|
611
|
+
|
|
612
|
+
# More examples:
|
|
613
|
+
# ❌ "And Shipped order exists for..."
|
|
614
|
+
# ✅ "And Shipped order is created for..."
|
|
615
|
+
|
|
616
|
+
# Why this matters:
|
|
617
|
+
# - Shows WHEN rules fire (on creation, not just existence)
|
|
618
|
+
# - Makes test flow obvious (setup → action → verify)
|
|
619
|
+
# - Clarifies that declarative rules execute automatically on changes
|
|
620
|
+
```
|
|
621
|
+
|
|
526
622
|
## Complete Phase 2 Example
|
|
527
623
|
|
|
528
624
|
### .feature File
|
|
@@ -619,6 +715,8 @@ def step_impl(context, expected):
|
|
|
619
715
|
| **"balance: expected 570, got 0.0"** | **Step ordering issue (Rule #0.5)! Multi-item pattern after single-item** |
|
|
620
716
|
| **"context has no attribute 'item_id'"** | **Step ordering issue! Specific pattern defined after general pattern** |
|
|
621
717
|
| "Order created but no items" | Check if wrong step matched (print debug in step) |
|
|
718
|
+
| **"expected 810, got 900"** (carbon neutral) | **Rule #10: Verify actual database values! Check product flags with SQL first** |
|
|
719
|
+
| **"How can balance be 0 with an order?"** | **Rule #13: Use "is created" not "exists" - shows when rules fire** |
|
|
622
720
|
|
|
623
721
|
### Debugging Step Ordering Issues
|
|
624
722
|
|
|
@@ -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:
|