valhuber / TDD

Test Driven Development and Transparent Logic Automation

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Automation: Agile, TDD Process

This project illustrates how API Logic Server Extensible Automation, coupled with an Agile (TDD) Process, can dramatically improve Time to Market and Reduce Risk:

  1. Instant App: API Logic Server creates an Admin App (and underlying API) with a single command.

  2. Customer Collaboration: the app (Working Software, Now) drives collaboration, resulting in Features (Stories), Scenarios (tests), and Design Specifications that define how data is computed, validated, and processed (e.g., issues email or messages, auditing, etc.).

  3. Iteration: the Design Specifications often translate directly into Executable Rules, automated in API Logic Server. These can be easily altered as further collaboration uncovers clarifications (no archaeology).

  4. Transparency: the TDD (Test Driven Development) Report documents the functionality of the system as Features (Stories) and Scenarios (tests) that confirm its operation. The report includes the underlying Rules, extending transparency to the implementation level.

  

Key Takeaway: automation drives Time to Market, and provides working software rapidly; it also drives TDD collaboration to define systems that meet actual needs, reducing requirements risk.

  

Sample Project

This is the sample project from API Logic Server, based on the Northwind database (sqlite database located in the database folder - no installation required):

  

Verify Installation

You can confirm its working by installing and running as described here.

  

Process Overview

The created project provides the User Interface and API described below, and implements the transactional logic described in the TDD Report. It was created, customized and tested as described in the subsections below.

  

1. Create Api Logic Project

API Logic Server is used once you have a preliminary database design. Use your existing procedures for database design. Include at least minimal test data.

Then (presuming API Logic Server is installed), create the project with this command, using venv based installs:

ApiLogicServer create  --db_url= --project_name=TDD

or, like this, using docker-based installs:

ApiLogicServer create --db_url= --project_name=/localhost/TDD

  

1a. Creates Admin App

The Agile objective of collaboration is typically best-served with running screens. The problem is, it takes quite a long time to create the API and screens to reach this point. And this work can be wasted if there were misunderstandings.

Ideally, User Interface creation would be automatic.

So, the API Logic Server create command above builds first-cut screens, automatically from the data model.

The app shown below (more detail here) is suitable for initial business user collaboration (further discussed below), and basic back office data maintenance.

You can customize it by editing a simple yamlfile (e.g, field captions, ordering etc.)

  

Key Takeaway: Admin App Automation enables collaboration, instantly.

  

1b. Also creates API

It is not difficult to create a single endpoint API. The problem is that it's quite a bit more work to create an endpoint for each table, with support for related data, pagination, filtering and sorting.

Ideally, API creation would be automatic.

So, the API Logic Server create command above builds such an API instantly, suitable for application integration, and creating custom User Interfaces. The API enforces the business logic described below.

The created project is customizable, using a standard IDE.

  

Key Takeaway: automatic API creation, with support for related data, pagination, filtering and sorting.

  

2. Collaborate using Admin App

As noted above, running screens are an excellent way to engage business user collaboration and ensure the system meets actual user needs. Such collaboration typically leads in two important directions, as described below.

  

2a. Iterate Data Model

You may discover that the data model is incorrect ("Wait! Customers have multiple addresses!!").

In a conventional system, this would mean revising the API and App. However, since these are created instantly through automation, such iterations are trivial. Just rebuild.

  

2b. Define Behave Scenarios

User Interfaces also spark insight about the Features ("Place Order") and Scenarios ("Check Credit"): "When the customer places an order, we need to reject it if it exceeds the credit limit". Capture these as described below.

TDD is designed for business user collaboration by making Features and Scenarios transparent. So, the start of Behave is to define one or more .feature files.

For example, see the place_order.feature, as tested by the Bad Order: Custom Service Scenario, below.

For more on TDD, see here.

  

Add Custom Service

While the automatically-created API is a great start, you may uncover a need for a custom service. This is easy to add - it's only about 10 lines of Python (api/customize_api.py), since the logic (discussed below) is enforced in the underlying data access. For details, see here.

  

2c. Logic Specification

We now choose a scenario (e.g, Bad Order), and engage business users for a clear understanding of check credit. This follows a familiar step-wise definition of terms, which we capture in text as shown below.

Note this "cocktail napkin spec" is short, yet clear. That's because instead of diving unto unecessary technical detail of how (such as pseudocode), it focuses on what.

  

3a. Declare Logic (from spec)

Business Logic is the heart of the system, enforcing our business policies. These consist of multi-table constraints and derivations, and actions such as sending email and messages. A core TDD objective is to define and test such behavior.

It's generally accepted that such domain-specific logic must require domain-specific code. The problem is that this is:

  • slow (it's often nearly half the system)
  • opaque to business users
  • painful to maintain - it's no secret that developers hate maintenance, since it's less coding than "archaeology": read the existing code to understand where to insert the new logic

Ideally, our logic specification is executable.

So, API Logic Server provides Logic Automation, where logic is implemented as:

  • Spreadsheet-like rules for multi-table derivations and constraints, and

  • Python, to implement logic not addressed in rules such as sending email or messages

So, instead of several hundred lines of code, we declared 5 rules (more details here).

Rules are entered in Python, with code completion. 5 key rules are shown below. Oserve how they exactly correspond to our specification, and are executable by the API Logic Server rules engine:

Unlike manual code, logic is declarative:

  • automatically reused - it is enforced as part of the API, so automatically shared across all screens and services.
  • automatically ordered - maintenance is simply altering the rules; the system computes their execution order by automatically discovering their dependencies. No more archaeology.
  • transparent - business users can read the spreadsheet-like rules. We'll exploit this in the TDD Report, described below.

  

Key Takeaway: spreadsheet-like rules can dramatically reduce the effort for backend logic, and make it transparent

  

Key Takeaway: keep your Logic Specification high level (what not how -- think spreadsheet), and your specification will often map directly to executable rules.

  

3b. Code/Run TDD Scenarios

Implement the actual scenarios (tests) in Python (place_order.py), using annotations (@when) to match scenarios and implementations. In this project, the implementation is basically calling APIs to get old data, run transactions, and check results.

Execute the tests using the pre-supplied Launch Configurations (shown at the bottom):

  1. Run Launch Configuration API Logic Server
  2. Run Launch Configuration Debug Behave Logic

The rules fire as transactions are run, and produce files later used in Report Behave Logic (described below):

  1. test/api_logic_server_behave/behave.log - summarizes test success / failure
  2. api_logic_server_behave/scenario_logic_logs/Bad_Order_Custom_Service.log - Logic Log output.
    • The code on line 121 signals the name of Logic Log
    • Note the Logic Log actually consists of 2 sections:
      • The first shows each rule firing, including complete old/new row values, with indentation for multi-table chaining
      • The "Rules Fired" summarizes which rules actually fired, representing a confirmation of our Logic Specification

You can use the debugger to stop in a test and verify results

  

4. Create TDD/Logic Report

This is pretty interesting: a record of all our Features and Scenarios, including transparent underlying logic. The problem is that it's buried in some text files inside our project.

Ideally, publishing this in a transparent manner (e.g., a wiki accessible via the Browser) would be a great asset to the team.

So, this project provides report_behave_logic.py to create a TDD Report - including logic - as a wiki file.

To run it, use Launch Configuration Report Behave Logic:

  1. Reads your current readme.md file (text like you are reading now), and
  2. Appends the TDD Report: by processing the files created in step 3c
    1. Reading the behave.log, and
    2. Injecting the scenario_logic_logs files
  3. Creates the output report as a wiki file named report_behave_logic.md

  

Key Takeaway: TDD makes requirements and tests transparent; rules make your logic transparent; combine them both into the TDD Report.

  

   

   

TDD Report

   

Feature: About Sample

   

Scenario: Transaction Processing

  Scenario: Transaction Processing
   Given Sample Database
   When Transactions are submitted
   Then Enforce business policies with Logic (rules + code)

Tests - and their logic - are transparent.. click to see Logic

   

Rules Used in Scenario: Transaction Processing

Logic Log in Scenario: Transaction Processing

AbstractRule Bank[0x10f151af0] (loaded 2022-03-22 20:10:53.857548
Mapped Class[Customer] rules
  Constraint Function: None
  Derive Customer.Balance as Sum(Order.AmountTotal Where <function declare_logic.<locals>.<lambda> at 0x10f1da940>
  Derive Customer.UnpaidOrderCount as Count(<class 'database.models.Order'> Where <function declare_logic.<locals>.<lambda> at 0x10f2ca310>
  Derive Customer.OrderCount as Count(<class 'database.models.Order'> Where None
Mapped Class[Order] rules
  Derive Order.AmountTotal as Sum(OrderDetail.Amount Where None
  RowEvent Order.congratulate_sales_rep()
  Derive Order.OrderDetailCount as Count(<class 'database.models.OrderDetail'> Where None
Mapped Class[OrderDetail] rules
  Derive OrderDetail.Amount as Formula (1): as_expression=lambda row: row.UnitPrice * row.Qua [...
  Derive OrderDetail.UnitPrice as Copy(Product.UnitPrice
  Derive OrderDetail.ShippedDate as Formula (2): row.Order.ShippedDat
Mapped Class[Product] rules
  Derive Product.UnitsShipped as Sum(OrderDetail.Quantity Where <function declare_logic.<locals>.<lambda> at 0x10f2ca1f0>
  Derive Product.UnitsInStock as Formula (1): <function
Mapped Class[Employee] rules
  Constraint Function: <function declare_logic.<locals>.raise_over_20_percent at 0x10f2caa60>
  RowEvent Employee.audit_by_event()
  Copy to: EmployeeAudi
Logic Bank - 21 rules loaded - 2022-03-22 20:11:03,026 - logic_logger - INF

   

Feature: Application Integration

   

Scenario: GET Customer

  Scenario: GET Customer
   Given Customer Account: VINET
   When GET Orders API
   Then VINET retrieved

   

Scenario: GET Department

  Scenario: GET Department
   Given Department 2
   When GET Department with SubDepartments API
   Then SubDepartments returned

   

Feature: Place Order

   

Scenario: Good Order Custom Service

  Scenario: Good Order Custom Service
   Given Customer Account: ALFKI
   When Good Order Placed
   Then Logic adjusts Balance (demo: chain up)
   Then Logic adusts Products Reordered
   Then Logic adjusts aggregates down on delete order

Tests - and their logic - are transparent.. click to see Logic

   

Logic Doc for scenario: Good Order Custom Service

We place an Order with an Order Detail. It's one transaction.

Note how the Order.OrderTotal and Customer.Balance are adjusted as Order Details are processed. Similarly, the Product.UnitsShipped is adjusted, and used to recompute UnitsInStock

Key Takeaway: sum/count aggregates (e.g., Customer.Balance) automate chain up multi-table transactions.

Inspect the log for send mail.

The congratulate_sales_rep event illustrates logic Extensibility

  • using Python to provide logic not covered by rules, like non-database operations such as sending email or messages.

There are actually multiple kinds of events:

  • Before row logic
  • After row logic
  • On commit, after all row logic has completed (as here), so that your code "sees" the full logic results

Events are passed the row and old_row, as well as logic_row which enables you to test the actual operation, chaining nest level, etc.

You can set breakpoints in events, and inspect these.

   

Rules Used in Scenario: Good Order Custom Service

  Customer  
    1. Derive Customer.UnpaidOrderCount as Count(<class 'database.models.Order'> Where <function declare_logic.<locals>.<lambda> at 0x10f2ca310>)  
    2. Derive Customer.OrderCount as Count(<class 'database.models.Order'> Where None)  
    3. Derive Customer.Balance as Sum(Order.AmountTotal Where <function declare_logic.<locals>.<lambda> at 0x10f1da940>)  
  Order  
    4. Derive Order.OrderDetailCount as Count(<class 'database.models.OrderDetail'> Where None)  
    5. Derive Order.AmountTotal as Sum(OrderDetail.Amount Where None)  
    6. RowEvent Order.congratulate_sales_rep()   
  Product  
    7. Derive Product.UnitsShipped as Sum(OrderDetail.Quantity Where <function declare_logic.<locals>.<lambda> at 0x10f2ca1f0>)  
    8. Derive Product.UnitsInStock as Formula (1): <function>  
  

Logic Log in Scenario: Good Order Custom Service

Logic Phase:		ROW LOGIC(session=0x10f9f6e20) (sqlalchemy before_flush)			 - 2022-03-22 20:11:03,203 - logic_logger - INF
..Order[11124] {Delete - client} Id: 11124, CustomerId: ALFKI, EmployeeId: 1, OrderDate: None, RequiredDate: None, ShippedDate: None, ShipVia: None, Freight: 11.0000000000, ShipName: None, ShipAddress: None, ShipCity: None, ShipRegion: None, ShipPostalCode: None, ShipCountry: None, AmountTotal: 56.00, Country: None, City: None, Ready: True, OrderDetailCount: 2  row: 0x10fa06c10  session: 0x10f9f6e20 - 2022-03-22 20:11:03,204 - logic_logger - INF
....Customer[ALFKI] {Update - Adjusting Customer: Balance, UnpaidOrderCount, OrderCount} Id: ALFKI, CompanyName: Alfreds Futterkiste, ContactName: Maria Anders, ContactTitle: Sales Representative, Address: Obere Str. 57A, City: Berlin, Region: Western Europe, PostalCode: 12209, Country: Germany, Phone: 030-0074321, Fax: 030-0076545, Balance:  [2158.0000000000-->] 2102.0000000000, CreditLimit: 2300.0000000000, OrderCount:  [16-->] 15, UnpaidOrderCount:  [11-->] 10  row: 0x10fa2f100  session: 0x10f9f6e20 - 2022-03-22 20:11:03,205 - logic_logger - INF
..OrderDetail[2249] {Delete - client} Id: 2249, OrderId: 11124, ProductId: 1, UnitPrice: 18.0000000000, Quantity: 1, Discount: 0.0, Amount: 18.0000000000, ShippedDate: None  row: 0x10fa27c40  session: 0x10f9f6e20 - 2022-03-22 20:11:03,206 - logic_logger - INF
....Product[1] {Update - Adjusting Product: UnitsShipped} Id: 1, ProductName: Chai, SupplierId: 1, CategoryId: 1, QuantityPerUnit: 10 boxes x 20 bags, UnitPrice: 18.0000000000, UnitsInStock: 39, UnitsOnOrder: 0, ReorderLevel: 10, Discontinued: 0, UnitsShipped:  [0-->] -1  row: 0x10fa27040  session: 0x10f9f6e20 - 2022-03-22 20:11:03,208 - logic_logger - INF
....Product[1] {Formula UnitsInStock} Id: 1, ProductName: Chai, SupplierId: 1, CategoryId: 1, QuantityPerUnit: 10 boxes x 20 bags, UnitPrice: 18.0000000000, UnitsInStock:  [39-->] 40, UnitsOnOrder: 0, ReorderLevel: 10, Discontinued: 0, UnitsShipped:  [0-->] -1  row: 0x10fa27040  session: 0x10f9f6e20 - 2022-03-22 20:11:03,208 - logic_logger - INF
..OrderDetail[2249] {No adjustment on deleted parent: Order} Id: 2249, OrderId: 11124, ProductId: 1, UnitPrice: 18.0000000000, Quantity: 1, Discount: 0.0, Amount: 18.0000000000, ShippedDate: None  row: 0x10fa27c40  session: 0x10f9f6e20 - 2022-03-22 20:11:03,209 - logic_logger - INF
..OrderDetail[2250] {Delete - client} Id: 2250, OrderId: 11124, ProductId: 2, UnitPrice: 19.0000000000, Quantity: 2, Discount: 0.0, Amount: 38.0000000000, ShippedDate: None  row: 0x10fa27be0  session: 0x10f9f6e20 - 2022-03-22 20:11:03,209 - logic_logger - INF
....Product[2] {Update - Adjusting Product: UnitsShipped} Id: 2, ProductName: Chang, SupplierId: 1, CategoryId: 1, QuantityPerUnit: 24 - 12 oz bottles, UnitPrice: 19.0000000000, UnitsInStock: 15, UnitsOnOrder: 40, ReorderLevel: 25, Discontinued: 0, UnitsShipped:  [2-->] 0  row: 0x10fa3b100  session: 0x10f9f6e20 - 2022-03-22 20:11:03,210 - logic_logger - INF
....Product[2] {Formula UnitsInStock} Id: 2, ProductName: Chang, SupplierId: 1, CategoryId: 1, QuantityPerUnit: 24 - 12 oz bottles, UnitPrice: 19.0000000000, UnitsInStock:  [15-->] 17, UnitsOnOrder: 40, ReorderLevel: 25, Discontinued: 0, UnitsShipped:  [2-->] 0  row: 0x10fa3b100  session: 0x10f9f6e20 - 2022-03-22 20:11:03,210 - logic_logger - INF
..OrderDetail[2250] {No adjustment on deleted parent: Order} Id: 2250, OrderId: 11124, ProductId: 2, UnitPrice: 19.0000000000, Quantity: 2, Discount: 0.0, Amount: 38.0000000000, ShippedDate: None  row: 0x10fa27be0  session: 0x10f9f6e20 - 2022-03-22 20:11:03,211 - logic_logger - INF
Logic Phase:		COMMIT(session=0x10f9f6e20)   										 - 2022-03-22 20:11:03,211 - logic_logger - INF
..Order[11124] {Commit Event} Id: 11124, CustomerId: ALFKI, EmployeeId: 1, OrderDate: None, RequiredDate: None, ShippedDate: None, ShipVia: None, Freight: 11.0000000000, ShipName: None, ShipAddress: None, ShipCity: None, ShipRegion: None, ShipPostalCode: None, ShipCountry: None, AmountTotal: 56.00, Country: None, City: None, Ready: True, OrderDetailCount: 2  row: 0x10fa06c10  session: 0x10f9f6e20 - 2022-03-22 20:11:03,212 - logic_logger - INF

   

Scenario: Bad Order Custom Service

  Scenario: Bad Order Custom Service
   Given Customer Account: ALFKI
   When Order Placed with excessive quantity
   Then Rejected per Credit Limit
   Then exceeds credit in response

Tests - and their logic - are transparent.. click to see Logic

   

Logic Doc for scenario: Bad Order Custom Service

Familiar logic pattern: constrain a derived result

   

Rules Used in Scenario: Bad Order Custom Service

  Customer  
    1. Derive Customer.UnpaidOrderCount as Count(<class 'database.models.Order'> Where <function declare_logic.<locals>.<lambda> at 0x10f2ca310>)  
    2. Derive Customer.OrderCount as Count(<class 'database.models.Order'> Where None)  
    3. Constraint Function: None   
    4. Derive Customer.Balance as Sum(Order.AmountTotal Where <function declare_logic.<locals>.<lambda> at 0x10f1da940>)  
  Order  
    5. Derive Order.OrderDetailCount as Count(<class 'database.models.OrderDetail'> Where None)  
    6. Derive Order.AmountTotal as Sum(OrderDetail.Amount Where None)  
  OrderDetail  
    7. Derive OrderDetail.ShippedDate as Formula (2): row.Order.ShippedDate  
    8. Derive OrderDetail.UnitPrice as Copy(Product.UnitPrice)  
    9. Derive OrderDetail.Amount as Formula (1): as_expression=lambda row: row.UnitPrice * row.Qua [...]  
  Product  
    10. Derive Product.UnitsShipped as Sum(OrderDetail.Quantity Where <function declare_logic.<locals>.<lambda> at 0x10f2ca1f0>)  
    11. Derive Product.UnitsInStock as Formula (1): <function>  
  

Logic Log in Scenario: Bad Order Custom Service

Logic Phase:		ROW LOGIC(session=0x10fa573d0) (sqlalchemy before_flush)			 - 2022-03-22 20:11:03,267 - logic_logger - INF
..Order[None] {Insert - client} Id: None, CustomerId: ALFKI, EmployeeId: 1, OrderDate: None, RequiredDate: None, ShippedDate: None, ShipVia: None, Freight: 10, ShipName: None, ShipAddress: None, ShipCity: None, ShipRegion: None, ShipPostalCode: None, ShipCountry: None, AmountTotal: None, Country: None, City: None, Ready: None, OrderDetailCount: None  row: 0x10fa57ca0  session: 0x10fa573d0 - 2022-03-22 20:11:03,267 - logic_logger - INF
....Customer[ALFKI] {Update - Adjusting Customer: UnpaidOrderCount, OrderCount} Id: ALFKI, CompanyName: Alfreds Futterkiste, ContactName: Maria Anders, ContactTitle: Sales Representative, Address: Obere Str. 57A, City: Berlin, Region: Western Europe, PostalCode: 12209, Country: Germany, Phone: 030-0074321, Fax: 030-0076545, Balance: 2102.0000000000, CreditLimit: 2300.0000000000, OrderCount:  [15-->] 16, UnpaidOrderCount:  [10-->] 11  row: 0x10fa504c0  session: 0x10fa573d0 - 2022-03-22 20:11:03,270 - logic_logger - INF
..OrderDetail[None] {Insert - client} Id: None, OrderId: None, ProductId: 1, UnitPrice: None, Quantity: 1111, Discount: 0, Amount: None, ShippedDate: None  row: 0x10fa57370  session: 0x10fa573d0 - 2022-03-22 20:11:03,271 - logic_logger - INF
..OrderDetail[None] {copy_rules for role: Product - UnitPrice} Id: None, OrderId: None, ProductId: 1, UnitPrice: 18.0000000000, Quantity: 1111, Discount: 0, Amount: None, ShippedDate: None  row: 0x10fa57370  session: 0x10fa573d0 - 2022-03-22 20:11:03,273 - logic_logger - INF
..OrderDetail[None] {Formula Amount} Id: None, OrderId: None, ProductId: 1, UnitPrice: 18.0000000000, Quantity: 1111, Discount: 0, Amount: 19998.0000000000, ShippedDate: None  row: 0x10fa57370  session: 0x10fa573d0 - 2022-03-22 20:11:03,273 - logic_logger - INF
....Product[1] {Update - Adjusting Product: UnitsShipped} Id: 1, ProductName: Chai, SupplierId: 1, CategoryId: 1, QuantityPerUnit: 10 boxes x 20 bags, UnitPrice: 18.0000000000, UnitsInStock: 40, UnitsOnOrder: 0, ReorderLevel: 10, Discontinued: 0, UnitsShipped:  [-1-->] 1110  row: 0x10fa50e50  session: 0x10fa573d0 - 2022-03-22 20:11:03,273 - logic_logger - INF
....Product[1] {Formula UnitsInStock} Id: 1, ProductName: Chai, SupplierId: 1, CategoryId: 1, QuantityPerUnit: 10 boxes x 20 bags, UnitPrice: 18.0000000000, UnitsInStock:  [40-->] -1071, UnitsOnOrder: 0, ReorderLevel: 10, Discontinued: 0, UnitsShipped:  [-1-->] 1110  row: 0x10fa50e50  session: 0x10fa573d0 - 2022-03-22 20:11:03,274 - logic_logger - INF
....Order[None] {Update - Adjusting Order: AmountTotal, OrderDetailCount} Id: None, CustomerId: ALFKI, EmployeeId: 1, OrderDate: None, RequiredDate: None, ShippedDate: None, ShipVia: None, Freight: 10, ShipName: None, ShipAddress: None, ShipCity: None, ShipRegion: None, ShipPostalCode: None, ShipCountry: None, AmountTotal:  [None-->] 19998.0000000000, Country: None, City: None, Ready: None, OrderDetailCount:  [None-->] 1  row: 0x10fa57ca0  session: 0x10fa573d0 - 2022-03-22 20:11:03,275 - logic_logger - INF
......Customer[ALFKI] {Update - Adjusting Customer: Balance} Id: ALFKI, CompanyName: Alfreds Futterkiste, ContactName: Maria Anders, ContactTitle: Sales Representative, Address: Obere Str. 57A, City: Berlin, Region: Western Europe, PostalCode: 12209, Country: Germany, Phone: 030-0074321, Fax: 030-0076545, Balance:  [2102.0000000000-->] 22100.0000000000, CreditLimit: 2300.0000000000, OrderCount: 16, UnpaidOrderCount: 11  row: 0x10fa504c0  session: 0x10fa573d0 - 2022-03-22 20:11:03,275 - logic_logger - INF
......Customer[ALFKI] {Constraint Failure: balance (22100.0000000000) exceeds credit (2300.0000000000)} Id: ALFKI, CompanyName: Alfreds Futterkiste, ContactName: Maria Anders, ContactTitle: Sales Representative, Address: Obere Str. 57A, City: Berlin, Region: Western Europe, PostalCode: 12209, Country: Germany, Phone: 030-0074321, Fax: 030-0076545, Balance:  [2102.0000000000-->] 22100.0000000000, CreditLimit: 2300.0000000000, OrderCount: 16, UnpaidOrderCount: 11  row: 0x10fa504c0  session: 0x10fa573d0 - 2022-03-22 20:11:03,276 - logic_logger - INF

   

Scenario: Alter Item Qty to exceed credit

  Scenario: Alter Item Qty to exceed credit
   Given Customer Account: ALFKI
   When Order Detail Quantity altered very high
   Then Rejected per Credit Limit
   Then exceeds credit in response

Tests - and their logic - are transparent.. click to see Logic

   

Logic Doc for scenario: Alter Item Qty to exceed credit

Same constraint as above.

Key Takeaway: Automatic Reuse (design one, solve many)

   

Rules Used in Scenario: Alter Item Qty to exceed credit

  Customer  
    1. Derive Customer.UnpaidOrderCount as Count(<class 'database.models.Order'> Where <function declare_logic.<locals>.<lambda> at 0x10f2ca310>)  
    2. Derive Customer.OrderCount as Count(<class 'database.models.Order'> Where None)  
    3. Constraint Function: None   
    4. Derive Customer.Balance as Sum(Order.AmountTotal Where <function declare_logic.<locals>.<lambda> at 0x10f1da940>)  
  Order  
    5. Derive Order.OrderDetailCount as Count(<class 'database.models.OrderDetail'> Where None)  
    6. Derive Order.AmountTotal as Sum(OrderDetail.Amount Where None)  
  OrderDetail  
    7. Derive OrderDetail.Amount as Formula (1): as_expression=lambda row: row.UnitPrice * row.Qua [...]  
  Product  
    8. Derive Product.UnitsShipped as Sum(OrderDetail.Quantity Where <function declare_logic.<locals>.<lambda> at 0x10f2ca1f0>)  
    9. Derive Product.UnitsInStock as Formula (1): <function>  
  

Logic Log in Scenario: Alter Item Qty to exceed credit

Logic Phase:		ROW LOGIC(session=0x10fa5bbe0) (sqlalchemy before_flush)			 - 2022-03-22 20:11:03,320 - logic_logger - INF
..OrderDetail[1040] {Update - client} Id: 1040, OrderId: 10643, ProductId: 28, UnitPrice: 45.6000000000, Quantity:  [15-->] 1110, Discount: 0.25, Amount: 684.0000000000, ShippedDate: None  row: 0x10fa27820  session: 0x10fa5bbe0 - 2022-03-22 20:11:03,321 - logic_logger - INF
..OrderDetail[1040] {Formula Amount} Id: 1040, OrderId: 10643, ProductId: 28, UnitPrice: 45.6000000000, Quantity:  [15-->] 1110, Discount: 0.25, Amount:  [684.0000000000-->] 50616.0000000000, ShippedDate: None  row: 0x10fa27820  session: 0x10fa5bbe0 - 2022-03-22 20:11:03,321 - logic_logger - INF
..OrderDetail[1040] {Prune Formula: ShippedDate [['Order.ShippedDate']]} Id: 1040, OrderId: 10643, ProductId: 28, UnitPrice: 45.6000000000, Quantity:  [15-->] 1110, Discount: 0.25, Amount:  [684.0000000000-->] 50616.0000000000, ShippedDate: None  row: 0x10fa27820  session: 0x10fa5bbe0 - 2022-03-22 20:11:03,321 - logic_logger - INF
....Product[28] {Update - Adjusting Product: UnitsShipped} Id: 28, ProductName: Rössle Sauerkraut, SupplierId: 12, CategoryId: 7, QuantityPerUnit: 25 - 825 g cans, UnitPrice: 45.6000000000, UnitsInStock: 26, UnitsOnOrder: 0, ReorderLevel: 0, Discontinued: 1, UnitsShipped:  [0-->] 1095  row: 0x10fa5bc70  session: 0x10fa5bbe0 - 2022-03-22 20:11:03,322 - logic_logger - INF
....Product[28] {Formula UnitsInStock} Id: 28, ProductName: Rössle Sauerkraut, SupplierId: 12, CategoryId: 7, QuantityPerUnit: 25 - 825 g cans, UnitPrice: 45.6000000000, UnitsInStock:  [26-->] -1069, UnitsOnOrder: 0, ReorderLevel: 0, Discontinued: 1, UnitsShipped:  [0-->] 1095  row: 0x10fa5bc70  session: 0x10fa5bbe0 - 2022-03-22 20:11:03,323 - logic_logger - INF
....Order[10643] {Update - Adjusting Order: AmountTotal} Id: 10643, CustomerId: ALFKI, EmployeeId: 6, OrderDate: 2013-08-25, RequiredDate: 2013-10-13, ShippedDate: None, ShipVia: 1, Freight: 29.4600000000, ShipName: Alfreds Futterkiste, ShipAddress: Obere Str. 57, ShipCity: Berlin, ShipRegion: Western Europe, ShipPostalCode: 12209, ShipCountry: Germany, AmountTotal:  [1086.00-->] 51018.0000000000, Country: None, City: None, Ready: True, OrderDetailCount: 3  row: 0x10fa3b190  session: 0x10fa5bbe0 - 2022-03-22 20:11:03,325 - logic_logger - INF
......Customer[ALFKI] {Update - Adjusting Customer: Balance} Id: ALFKI, CompanyName: Alfreds Futterkiste, ContactName: Maria Anders, ContactTitle: Sales Representative, Address: Obere Str. 57A, City: Berlin, Region: Western Europe, PostalCode: 12209, Country: Germany, Phone: 030-0074321, Fax: 030-0076545, Balance:  [2102.0000000000-->] 52034.0000000000, CreditLimit: 2300.0000000000, OrderCount: 15, UnpaidOrderCount: 10  row: 0x10fa57280  session: 0x10fa5bbe0 - 2022-03-22 20:11:03,326 - logic_logger - INF
......Customer[ALFKI] {Constraint Failure: balance (52034.0000000000) exceeds credit (2300.0000000000)} Id: ALFKI, CompanyName: Alfreds Futterkiste, ContactName: Maria Anders, ContactTitle: Sales Representative, Address: Obere Str. 57A, City: Berlin, Region: Western Europe, PostalCode: 12209, Country: Germany, Phone: 030-0074321, Fax: 030-0076545, Balance:  [2102.0000000000-->] 52034.0000000000, CreditLimit: 2300.0000000000, OrderCount: 15, UnpaidOrderCount: 10  row: 0x10fa57280  session: 0x10fa5bbe0 - 2022-03-22 20:11:03,327 - logic_logger - INF

   

Scenario: Alter Required Date - adjust logic pruned

  Scenario: Alter Required Date - adjust logic pruned
   Given Customer Account: ALFKI
   When Order RequiredDate altered (2013-10-13)
   Then Balance not adjusted

Tests - and their logic - are transparent.. click to see Logic

   

Logic Doc for scenario: Alter Required Date - adjust logic pruned

We set Order.RequiredDate.

This is a normal update. Nothing depends on the columns altered, so this has no effect on the related Customer, Order Details or Products. Contrast this to the Cascade Update Test and the Custom Service test, where logic chaining affects related rows. Only the commit event fires.

Key Takeaway: rule pruning automatically avoids unnecessary SQL overhead.

   

Rules Used in Scenario: Alter Required Date - adjust logic pruned

  Customer  
    1. Derive Customer.UnpaidOrderCount as Count(<class 'database.models.Order'> Where <function declare_logic.<locals>.<lambda> at 0x10f2ca310>)  
    2. Derive Customer.Balance as Sum(Order.AmountTotal Where <function declare_logic.<locals>.<lambda> at 0x10f1da940>)  
    3. Derive Customer.OrderCount as Count(<class 'database.models.Order'> Where None)  
  Order  
    4. RowEvent Order.congratulate_sales_rep()   
  

Logic Log in Scenario: Alter Required Date - adjust logic pruned

Logic Phase:		ROW LOGIC(session=0x10fa50d60) (sqlalchemy before_flush)			 - 2022-03-22 20:11:03,371 - logic_logger - INF
..Order[10643] {Update - client} Id: 10643, CustomerId: ALFKI, EmployeeId: 6, OrderDate: 2013-08-25, RequiredDate:  [2013-10-13-->] 2013-10-13 00:00:00, ShippedDate: None, ShipVia: 1, Freight: 29.4600000000, ShipName: Alfreds Futterkiste, ShipAddress: Obere Str. 57, ShipCity: Berlin, ShipRegion: Western Europe, ShipPostalCode: 12209, ShipCountry: Germany, AmountTotal: 1086.00, Country: None, City: None, Ready: True, OrderDetailCount: 3  row: 0x10fa579d0  session: 0x10fa50d60 - 2022-03-22 20:11:03,372 - logic_logger - INF
Logic Phase:		COMMIT(session=0x10fa50d60)   										 - 2022-03-22 20:11:03,373 - logic_logger - INF
..Order[10643] {Commit Event} Id: 10643, CustomerId: ALFKI, EmployeeId: 6, OrderDate: 2013-08-25, RequiredDate:  [2013-10-13-->] 2013-10-13 00:00:00, ShippedDate: None, ShipVia: 1, Freight: 29.4600000000, ShipName: Alfreds Futterkiste, ShipAddress: Obere Str. 57, ShipCity: Berlin, ShipRegion: Western Europe, ShipPostalCode: 12209, ShipCountry: Germany, AmountTotal: 1086.00, Country: None, City: None, Ready: True, OrderDetailCount: 3  row: 0x10fa579d0  session: 0x10fa50d60 - 2022-03-22 20:11:03,373 - logic_logger - INF

   

Scenario: Set Shipped - adjust logic reuse

  Scenario: Set Shipped - adjust logic reuse
   Given Customer Account: ALFKI
   When Order ShippedDate altered (2013-10-13)
   Then Balance reduced 1086

Tests - and their logic - are transparent.. click to see Logic

   

Logic Doc for scenario: Set Shipped - adjust logic reuse

We set Order.ShippedDate.

This cascades to the Order Details, where it adjusts the Product.UnitsShipped and recomputes UnitsInStock, as above

Key Takeaway: parent references (e.g., OrderDetail.ShippedDate) automate chain-down multi-table transactions.

Key Takeaway: Automatic Reuse (design one, solve many)

   

Rules Used in Scenario: Set Shipped - adjust logic reuse

  Customer  
    1. Derive Customer.UnpaidOrderCount as Count(<class 'database.models.Order'> Where <function declare_logic.<locals>.<lambda> at 0x10f2ca310>)  
    2. Derive Customer.OrderCount as Count(<class 'database.models.Order'> Where None)  
    3. Derive Customer.Balance as Sum(Order.AmountTotal Where <function declare_logic.<locals>.<lambda> at 0x10f1da940>)  
  Order  
    4. Derive Order.OrderDetailCount as Count(<class 'database.models.OrderDetail'> Where None)  
    5. Derive Order.AmountTotal as Sum(OrderDetail.Amount Where None)  
    6. RowEvent Order.congratulate_sales_rep()   
  OrderDetail  
    7. Derive OrderDetail.ShippedDate as Formula (2): row.Order.ShippedDate  
  Product  
    8. Derive Product.UnitsShipped as Sum(OrderDetail.Quantity Where <function declare_logic.<locals>.<lambda> at 0x10f2ca1f0>)  
    9. Derive Product.UnitsInStock as Formula (1): <function>  
  

Logic Log in Scenario: Set Shipped - adjust logic reuse

Logic Phase:		ROW LOGIC(session=0x10fa5b310) (sqlalchemy before_flush)			 - 2022-03-22 20:11:03,459 - logic_logger - INF
..Order[10643] {Update - client} Id: 10643, CustomerId: ALFKI, EmployeeId: 6, OrderDate: 2013-08-25, RequiredDate: 2013-10-13, ShippedDate:  [None-->] 2013-10-13, ShipVia: 1, Freight: 29.4600000000, ShipName: Alfreds Futterkiste, ShipAddress: Obere Str. 57, ShipCity: Berlin, ShipRegion: Western Europe, ShipPostalCode: 12209, ShipCountry: Germany, AmountTotal: 1086.00, Country: None, City: None, Ready: True, OrderDetailCount: 3  row: 0x10fa27e20  session: 0x10fa5b310 - 2022-03-22 20:11:03,460 - logic_logger - INF
....Customer[ALFKI] {Update - Adjusting Customer: Balance, UnpaidOrderCount} Id: ALFKI, CompanyName: Alfreds Futterkiste, ContactName: Maria Anders, ContactTitle: Sales Representative, Address: Obere Str. 57A, City: Berlin, Region: Western Europe, PostalCode: 12209, Country: Germany, Phone: 030-0074321, Fax: 030-0076545, Balance:  [2102.0000000000-->] 1016.0000000000, CreditLimit: 2300.0000000000, OrderCount: 15, UnpaidOrderCount:  [10-->] 9  row: 0x10fa27d60  session: 0x10fa5b310 - 2022-03-22 20:11:03,461 - logic_logger - INF
....OrderDetail[1040] {Update - Cascading Order.ShippedDate (,...)} Id: 1040, OrderId: 10643, ProductId: 28, UnitPrice: 45.6000000000, Quantity: 15, Discount: 0.25, Amount: 684.0000000000, ShippedDate: None  row: 0x10f991c10  session: 0x10fa5b310 - 2022-03-22 20:11:03,463 - logic_logger - INF
....OrderDetail[1040] {Prune Formula: Amount [['UnitPrice', 'Quantity']]} Id: 1040, OrderId: 10643, ProductId: 28, UnitPrice: 45.6000000000, Quantity: 15, Discount: 0.25, Amount: 684.0000000000, ShippedDate: None  row: 0x10f991c10  session: 0x10fa5b310 - 2022-03-22 20:11:03,463 - logic_logger - INF
....OrderDetail[1040] {Formula ShippedDate} Id: 1040, OrderId: 10643, ProductId: 28, UnitPrice: 45.6000000000, Quantity: 15, Discount: 0.25, Amount: 684.0000000000, ShippedDate:  [None-->] 2013-10-13  row: 0x10f991c10  session: 0x10fa5b310 - 2022-03-22 20:11:03,463 - logic_logger - INF
......Product[28] {Update - Adjusting Product: UnitsShipped} Id: 28, ProductName: Rössle Sauerkraut, SupplierId: 12, CategoryId: 7, QuantityPerUnit: 25 - 825 g cans, UnitPrice: 45.6000000000, UnitsInStock: 26, UnitsOnOrder: 0, ReorderLevel: 0, Discontinued: 1, UnitsShipped:  [0-->] -15  row: 0x10fa27d00  session: 0x10fa5b310 - 2022-03-22 20:11:03,464 - logic_logger - INF
......Product[28] {Formula UnitsInStock} Id: 28, ProductName: Rössle Sauerkraut, SupplierId: 12, CategoryId: 7, QuantityPerUnit: 25 - 825 g cans, UnitPrice: 45.6000000000, UnitsInStock:  [26-->] 41, UnitsOnOrder: 0, ReorderLevel: 0, Discontinued: 1, UnitsShipped:  [0-->] -15  row: 0x10fa27d00  session: 0x10fa5b310 - 2022-03-22 20:11:03,465 - logic_logger - INF
....OrderDetail[1041] {Update - Cascading Order.ShippedDate (,...)} Id: 1041, OrderId: 10643, ProductId: 39, UnitPrice: 18.0000000000, Quantity: 21, Discount: 0.25, Amount: 378.0000000000, ShippedDate: None  row: 0x10f991bb0  session: 0x10fa5b310 - 2022-03-22 20:11:03,465 - logic_logger - INF
....OrderDetail[1041] {Prune Formula: Amount [['UnitPrice', 'Quantity']]} Id: 1041, OrderId: 10643, ProductId: 39, UnitPrice: 18.0000000000, Quantity: 21, Discount: 0.25, Amount: 378.0000000000, ShippedDate: None  row: 0x10f991bb0  session: 0x10fa5b310 - 2022-03-22 20:11:03,466 - logic_logger - INF
....OrderDetail[1041] {Formula ShippedDate} Id: 1041, OrderId: 10643, ProductId: 39, UnitPrice: 18.0000000000, Quantity: 21, Discount: 0.25, Amount: 378.0000000000, ShippedDate:  [None-->] 2013-10-13  row: 0x10f991bb0  session: 0x10fa5b310 - 2022-03-22 20:11:03,466 - logic_logger - INF
......Product[39] {Update - Adjusting Product: UnitsShipped} Id: 39, ProductName: Chartreuse verte, SupplierId: 18, CategoryId: 1, QuantityPerUnit: 750 cc per bottle, UnitPrice: 18.0000000000, UnitsInStock: 69, UnitsOnOrder: 0, ReorderLevel: 5, Discontinued: 0, UnitsShipped:  [0-->] -21  row: 0x10fa5b070  session: 0x10fa5b310 - 2022-03-22 20:11:03,467 - logic_logger - INF
......Product[39] {Formula UnitsInStock} Id: 39, ProductName: Chartreuse verte, SupplierId: 18, CategoryId: 1, QuantityPerUnit: 750 cc per bottle, UnitPrice: 18.0000000000, UnitsInStock:  [69-->] 90, UnitsOnOrder: 0, ReorderLevel: 5, Discontinued: 0, UnitsShipped:  [0-->] -21  row: 0x10fa5b070  session: 0x10fa5b310 - 2022-03-22 20:11:03,467 - logic_logger - INF
....OrderDetail[1042] {Update - Cascading Order.ShippedDate (,...)} Id: 1042, OrderId: 10643, ProductId: 46, UnitPrice: 12.0000000000, Quantity: 2, Discount: 0.25, Amount: 24.0000000000, ShippedDate: None  row: 0x10f991eb0  session: 0x10fa5b310 - 2022-03-22 20:11:03,468 - logic_logger - INF
....OrderDetail[1042] {Prune Formula: Amount [['UnitPrice', 'Quantity']]} Id: 1042, OrderId: 10643, ProductId: 46, UnitPrice: 12.0000000000, Quantity: 2, Discount: 0.25, Amount: 24.0000000000, ShippedDate: None  row: 0x10f991eb0  session: 0x10fa5b310 - 2022-03-22 20:11:03,468 - logic_logger - INF
....OrderDetail[1042] {Formula ShippedDate} Id: 1042, OrderId: 10643, ProductId: 46, UnitPrice: 12.0000000000, Quantity: 2, Discount: 0.25, Amount: 24.0000000000, ShippedDate:  [None-->] 2013-10-13  row: 0x10f991eb0  session: 0x10fa5b310 - 2022-03-22 20:11:03,469 - logic_logger - INF
......Product[46] {Update - Adjusting Product: UnitsShipped} Id: 46, ProductName: Spegesild, SupplierId: 21, CategoryId: 8, QuantityPerUnit: 4 - 450 g glasses, UnitPrice: 12.0000000000, UnitsInStock: 95, UnitsOnOrder: 0, ReorderLevel: 0, Discontinued: 0, UnitsShipped:  [0-->] -2  row: 0x10fa27880  session: 0x10fa5b310 - 2022-03-22 20:11:03,470 - logic_logger - INF
......Product[46] {Formula UnitsInStock} Id: 46, ProductName: Spegesild, SupplierId: 21, CategoryId: 8, QuantityPerUnit: 4 - 450 g glasses, UnitPrice: 12.0000000000, UnitsInStock:  [95-->] 97, UnitsOnOrder: 0, ReorderLevel: 0, Discontinued: 0, UnitsShipped:  [0-->] -2  row: 0x10fa27880  session: 0x10fa5b310 - 2022-03-22 20:11:03,470 - logic_logger - INF
Logic Phase:		COMMIT(session=0x10fa5b310)   										 - 2022-03-22 20:11:03,471 - logic_logger - INF
..Order[10643] {Commit Event} Id: 10643, CustomerId: ALFKI, EmployeeId: 6, OrderDate: 2013-08-25, RequiredDate: 2013-10-13, ShippedDate:  [None-->] 2013-10-13, ShipVia: 1, Freight: 29.4600000000, ShipName: Alfreds Futterkiste, ShipAddress: Obere Str. 57, ShipCity: Berlin, ShipRegion: Western Europe, ShipPostalCode: 12209, ShipCountry: Germany, AmountTotal: 1086.00, Country: None, City: None, Ready: True, OrderDetailCount: 3  row: 0x10fa27e20  session: 0x10fa5b310 - 2022-03-22 20:11:03,471 - logic_logger - INF

   

Scenario: Reset Shipped - adjust logic reuse

  Scenario: Reset Shipped - adjust logic reuse
   Given Shipped Order
   When Order ShippedDate set to None
   Then Logic adjusts Balance by -1086

Tests - and their logic - are transparent.. click to see Logic

   

Logic Doc for scenario: Reset Shipped - adjust logic reuse

Same logic as above.

Key Takeaway: Automatic Reuse (design one, solve many)

   

Rules Used in Scenario: Reset Shipped - adjust logic reuse

  Customer  
    1. Derive Customer.UnpaidOrderCount as Count(<class 'database.models.Order'> Where <function declare_logic.<locals>.<lambda> at 0x10f2ca310>)  
    2. Derive Customer.OrderCount as Count(<class 'database.models.Order'> Where None)  
    3. Derive Customer.Balance as Sum(Order.AmountTotal Where <function declare_logic.<locals>.<lambda> at 0x10f1da940>)  
  Order  
    4. Derive Order.OrderDetailCount as Count(<class 'database.models.OrderDetail'> Where None)  
    5. Derive Order.AmountTotal as Sum(OrderDetail.Amount Where None)  
    6. RowEvent Order.congratulate_sales_rep()   
  OrderDetail  
    7. Derive OrderDetail.ShippedDate as Formula (2): row.Order.ShippedDate  
  Product  
    8. Derive Product.UnitsShipped as Sum(OrderDetail.Quantity Where <function declare_logic.<locals>.<lambda> at 0x10f2ca1f0>)  
    9. Derive Product.UnitsInStock as Formula (1): <function>  
  

Logic Log in Scenario: Reset Shipped - adjust logic reuse

Logic Phase:		ROW LOGIC(session=0x10fa27c70) (sqlalchemy before_flush)			 - 2022-03-22 20:11:03,556 - logic_logger - INF
..Order[10643] {Update - client} Id: 10643, CustomerId: ALFKI, EmployeeId: 6, OrderDate: 2013-08-25, RequiredDate: 2013-10-13, ShippedDate:  [2013-10-13-->] None, ShipVia: 1, Freight: 29.4600000000, ShipName: Alfreds Futterkiste, ShipAddress: Obere Str. 57, ShipCity: Berlin, ShipRegion: Western Europe, ShipPostalCode: 12209, ShipCountry: Germany, AmountTotal: 1086.00, Country: None, City: None, Ready: True, OrderDetailCount: 3  row: 0x10f926c70  session: 0x10fa27c70 - 2022-03-22 20:11:03,556 - logic_logger - INF
....Customer[ALFKI] {Update - Adjusting Customer: Balance, UnpaidOrderCount} Id: ALFKI, CompanyName: Alfreds Futterkiste, ContactName: Maria Anders, ContactTitle: Sales Representative, Address: Obere Str. 57A, City: Berlin, Region: Western Europe, PostalCode: 12209, Country: Germany, Phone: 030-0074321, Fax: 030-0076545, Balance:  [1016.0000000000-->] 2102.0000000000, CreditLimit: 2300.0000000000, OrderCount: 15, UnpaidOrderCount:  [9-->] 10  row: 0x10fa2f3d0  session: 0x10fa27c70 - 2022-03-22 20:11:03,558 - logic_logger - INF
....OrderDetail[1040] {Update - Cascading Order.ShippedDate (,...)} Id: 1040, OrderId: 10643, ProductId: 28, UnitPrice: 45.6000000000, Quantity: 15, Discount: 0.25, Amount: 684.0000000000, ShippedDate: 2013-10-13  row: 0x10faf4850  session: 0x10fa27c70 - 2022-03-22 20:11:03,559 - logic_logger - INF
....OrderDetail[1040] {Prune Formula: Amount [['UnitPrice', 'Quantity']]} Id: 1040, OrderId: 10643, ProductId: 28, UnitPrice: 45.6000000000, Quantity: 15, Discount: 0.25, Amount: 684.0000000000, ShippedDate: 2013-10-13  row: 0x10faf4850  session: 0x10fa27c70 - 2022-03-22 20:11:03,560 - logic_logger - INF
....OrderDetail[1040] {Formula ShippedDate} Id: 1040, OrderId: 10643, ProductId: 28, UnitPrice: 45.6000000000, Quantity: 15, Discount: 0.25, Amount: 684.0000000000, ShippedDate:  [2013-10-13-->] None  row: 0x10faf4850  session: 0x10fa27c70 - 2022-03-22 20:11:03,560 - logic_logger - INF
......Product[28] {Update - Adjusting Product: UnitsShipped} Id: 28, ProductName: Rössle Sauerkraut, SupplierId: 12, CategoryId: 7, QuantityPerUnit: 25 - 825 g cans, UnitPrice: 45.6000000000, UnitsInStock: 41, UnitsOnOrder: 0, ReorderLevel: 0, Discontinued: 1, UnitsShipped:  [-15-->] 0  row: 0x10fa1a2e0  session: 0x10fa27c70 - 2022-03-22 20:11:03,561 - logic_logger - INF
......Product[28] {Formula UnitsInStock} Id: 28, ProductName: Rössle Sauerkraut, SupplierId: 12, CategoryId: 7, QuantityPerUnit: 25 - 825 g cans, UnitPrice: 45.6000000000, UnitsInStock:  [41-->] 26, UnitsOnOrder: 0, ReorderLevel: 0, Discontinued: 1, UnitsShipped:  [-15-->] 0  row: 0x10fa1a2e0  session: 0x10fa27c70 - 2022-03-22 20:11:03,562 - logic_logger - INF
....OrderDetail[1041] {Update - Cascading Order.ShippedDate (,...)} Id: 1041, OrderId: 10643, ProductId: 39, UnitPrice: 18.0000000000, Quantity: 21, Discount: 0.25, Amount: 378.0000000000, ShippedDate: 2013-10-13  row: 0x10faf48b0  session: 0x10fa27c70 - 2022-03-22 20:11:03,562 - logic_logger - INF
....OrderDetail[1041] {Prune Formula: Amount [['UnitPrice', 'Quantity']]} Id: 1041, OrderId: 10643, ProductId: 39, UnitPrice: 18.0000000000, Quantity: 21, Discount: 0.25, Amount: 378.0000000000, ShippedDate: 2013-10-13  row: 0x10faf48b0  session: 0x10fa27c70 - 2022-03-22 20:11:03,563 - logic_logger - INF
....OrderDetail[1041] {Formula ShippedDate} Id: 1041, OrderId: 10643, ProductId: 39, UnitPrice: 18.0000000000, Quantity: 21, Discount: 0.25, Amount: 378.0000000000, ShippedDate:  [2013-10-13-->] None  row: 0x10faf48b0  session: 0x10fa27c70 - 2022-03-22 20:11:03,563 - logic_logger - INF
......Product[39] {Update - Adjusting Product: UnitsShipped} Id: 39, ProductName: Chartreuse verte, SupplierId: 18, CategoryId: 1, QuantityPerUnit: 750 cc per bottle, UnitPrice: 18.0000000000, UnitsInStock: 90, UnitsOnOrder: 0, ReorderLevel: 5, Discontinued: 0, UnitsShipped:  [-21-->] 0  row: 0x10fa50250  session: 0x10fa27c70 - 2022-03-22 20:11:03,564 - logic_logger - INF
......Product[39] {Formula UnitsInStock} Id: 39, ProductName: Chartreuse verte, SupplierId: 18, CategoryId: 1, QuantityPerUnit: 750 cc per bottle, UnitPrice: 18.0000000000, UnitsInStock:  [90-->] 69, UnitsOnOrder: 0, ReorderLevel: 5, Discontinued: 0, UnitsShipped:  [-21-->] 0  row: 0x10fa50250  session: 0x10fa27c70 - 2022-03-22 20:11:03,564 - logic_logger - INF
....OrderDetail[1042] {Update - Cascading Order.ShippedDate (,...)} Id: 1042, OrderId: 10643, ProductId: 46, UnitPrice: 12.0000000000, Quantity: 2, Discount: 0.25, Amount: 24.0000000000, ShippedDate: 2013-10-13  row: 0x10faf47f0  session: 0x10fa27c70 - 2022-03-22 20:11:03,565 - logic_logger - INF
....OrderDetail[1042] {Prune Formula: Amount [['UnitPrice', 'Quantity']]} Id: 1042, OrderId: 10643, ProductId: 46, UnitPrice: 12.0000000000, Quantity: 2, Discount: 0.25, Amount: 24.0000000000, ShippedDate: 2013-10-13  row: 0x10faf47f0  session: 0x10fa27c70 - 2022-03-22 20:11:03,565 - logic_logger - INF
....OrderDetail[1042] {Formula ShippedDate} Id: 1042, OrderId: 10643, ProductId: 46, UnitPrice: 12.0000000000, Quantity: 2, Discount: 0.25, Amount: 24.0000000000, ShippedDate:  [2013-10-13-->] None  row: 0x10faf47f0  session: 0x10fa27c70 - 2022-03-22 20:11:03,566 - logic_logger - INF
......Product[46] {Update - Adjusting Product: UnitsShipped} Id: 46, ProductName: Spegesild, SupplierId: 21, CategoryId: 8, QuantityPerUnit: 4 - 450 g glasses, UnitPrice: 12.0000000000, UnitsInStock: 97, UnitsOnOrder: 0, ReorderLevel: 0, Discontinued: 0, UnitsShipped:  [-2-->] 0  row: 0x10fa50490  session: 0x10fa27c70 - 2022-03-22 20:11:03,566 - logic_logger - INF
......Product[46] {Formula UnitsInStock} Id: 46, ProductName: Spegesild, SupplierId: 21, CategoryId: 8, QuantityPerUnit: 4 - 450 g glasses, UnitPrice: 12.0000000000, UnitsInStock:  [97-->] 95, UnitsOnOrder: 0, ReorderLevel: 0, Discontinued: 0, UnitsShipped:  [-2-->] 0  row: 0x10fa50490  session: 0x10fa27c70 - 2022-03-22 20:11:03,567 - logic_logger - INF
Logic Phase:		COMMIT(session=0x10fa27c70)   										 - 2022-03-22 20:11:03,568 - logic_logger - INF
..Order[10643] {Commit Event} Id: 10643, CustomerId: ALFKI, EmployeeId: 6, OrderDate: 2013-08-25, RequiredDate: 2013-10-13, ShippedDate:  [2013-10-13-->] None, ShipVia: 1, Freight: 29.4600000000, ShipName: Alfreds Futterkiste, ShipAddress: Obere Str. 57, ShipCity: Berlin, ShipRegion: Western Europe, ShipPostalCode: 12209, ShipCountry: Germany, AmountTotal: 1086.00, Country: None, City: None, Ready: True, OrderDetailCount: 3  row: 0x10f926c70  session: 0x10fa27c70 - 2022-03-22 20:11:03,568 - logic_logger - INF

   

Feature: Salary Change

   

Scenario: Audit Salary Change

  Scenario: Audit Salary Change
   Given Employee 5 (Buchanan) - Salary 95k
   When Patch Salary to 200k
   Then Salary_audit row created

Tests - and their logic - are transparent.. click to see Logic

   

Logic Doc for scenario: Audit Salary Change

Observe the logic log to see that it creates audit rows:

  1. Discouraged: you can implement auditing with events. But auditing is a common pattern, and this can lead to repetitive, tedious code
  2. Preferred: approaches use extensible rules.

Generic event handlers can also reduce redundant code, illustrated in the time/date stamping handle_all logic.

This is due to the copy_row rule. Contrast this to the tedious audit_by_event alternative:

Key Takeaway: use extensible own rule types to automate pattern you identify; events can result in tedious amounts of code.

   

Rules Used in Scenario: Audit Salary Change

  Employee  
    1. RowEvent Employee.audit_by_event()   
  

Logic Log in Scenario: Audit Salary Change

Logic Phase:		ROW LOGIC(session=0x10faf4c10) (sqlalchemy before_flush)			 - 2022-03-22 20:11:03,619 - logic_logger - INF
..Employee[5] {Update - client} Id: 5, LastName: Buchanan, FirstName: Steven, Title: Sales Manager, TitleOfCourtesy: Mr., BirthDate: 1987-03-04, HireDate: 2025-10-17, Address: 14 Garrett Hill, City: London, Region: British Isles, PostalCode: SW1 8JR, Country: UK, HomePhone: (71) 555-4848, Extension: 3453, Photo: None, Notes: Steven Buchanan graduated from St. Andrews University, Scotland, with a BSC degree in 1976.  Upon joining the company as a sales representative in 1992, he spent 6 months in an orientation program at the Seattle office and then returned to his permanent post in London.  He was promoted to sales manager in March 1993.  Mr. Buchanan has completed the courses 'Successful Telemarketing' and 'International Sales Management.'  He is fluent in French., ReportsTo: 2, PhotoPath: http://accweb/emmployees/buchanan.bmp, IsCommissioned: 0, Salary:  [95000.0000000000-->] 200000, WorksForDepartmentId: 3, OnLoanDepartmentId: None  row: 0x10faf49a0  session: 0x10faf4c10 - 2022-03-22 20:11:03,619 - logic_logger - INF
..Employee[5] {BEGIN Copy to: EmployeeAudit} Id: 5, LastName: Buchanan, FirstName: Steven, Title: Sales Manager, TitleOfCourtesy: Mr., BirthDate: 1987-03-04, HireDate: 2025-10-17, Address: 14 Garrett Hill, City: London, Region: British Isles, PostalCode: SW1 8JR, Country: UK, HomePhone: (71) 555-4848, Extension: 3453, Photo: None, Notes: Steven Buchanan graduated from St. Andrews University, Scotland, with a BSC degree in 1976.  Upon joining the company as a sales representative in 1992, he spent 6 months in an orientation program at the Seattle office and then returned to his permanent post in London.  He was promoted to sales manager in March 1993.  Mr. Buchanan has completed the courses 'Successful Telemarketing' and 'International Sales Management.'  He is fluent in French., ReportsTo: 2, PhotoPath: http://accweb/emmployees/buchanan.bmp, IsCommissioned: 0, Salary:  [95000.0000000000-->] 200000, WorksForDepartmentId: 3, OnLoanDepartmentId: None  row: 0x10faf49a0  session: 0x10faf4c10 - 2022-03-22 20:11:03,621 - logic_logger - INF
....EmployeeAudit[None] {Insert - Copy EmployeeAudit} Id: None, Title: Sales Manager, Salary: 200000, LastName: Buchanan, FirstName: Steven, EmployeeId: None, CreatedOn: None  row: 0x10faf4dc0  session: 0x10faf4c10 - 2022-03-22 20:11:03,622 - logic_logger - INF
....EmployeeAudit[None] {early_row_event_all_classes - handle_all sets 'Created_on} Id: None, Title: Sales Manager, Salary: 200000, LastName: Buchanan, FirstName: Steven, EmployeeId: None, CreatedOn: 2022-03-22 20:11:03.622180  row: 0x10faf4dc0  session: 0x10faf4c10 - 2022-03-22 20:11:03,622 - logic_logger - INF
Logic Phase:		COMMIT(session=0x10faf4c10)   										 - 2022-03-22 20:11:03,622 - logic_logger - INF
..Employee[5] {Commit Event} Id: 5, LastName: Buchanan, FirstName: Steven, Title: Sales Manager, TitleOfCourtesy: Mr., BirthDate: 1987-03-04, HireDate: 2025-10-17, Address: 14 Garrett Hill, City: London, Region: British Isles, PostalCode: SW1 8JR, Country: UK, HomePhone: (71) 555-4848, Extension: 3453, Photo: None, Notes: Steven Buchanan graduated from St. Andrews University, Scotland, with a BSC degree in 1976.  Upon joining the company as a sales representative in 1992, he spent 6 months in an orientation program at the Seattle office and then returned to his permanent post in London.  He was promoted to sales manager in March 1993.  Mr. Buchanan has completed the courses 'Successful Telemarketing' and 'International Sales Management.'  He is fluent in French., ReportsTo: 2, PhotoPath: http://accweb/emmployees/buchanan.bmp, IsCommissioned: 0, Salary:  [95000.0000000000-->] 200000, WorksForDepartmentId: 3, OnLoanDepartmentId: None  row: 0x10faf49a0  session: 0x10faf4c10 - 2022-03-22 20:11:03,623 - logic_logger - INF

   

Scenario: Raise Must be Meaningful

  Scenario: Raise Must be Meaningful
   Given Employee 5 (Buchanan) - Salary 95k
   When Patch Salary to 96k
   Then Reject - Raise too small

Tests - and their logic - are transparent.. click to see Logic

   

Logic Doc for scenario: Raise Must be Meaningful

Observe the use of old_row

Key Takeaway: State Transsition Logic enabled per old_row

   

Rules Used in Scenario: Raise Must be Meaningful

  Employee  
    1. Constraint Function: <function declare_logic.<locals>.raise_over_20_percent at 0x10f2caa60>   
  

Logic Log in Scenario: Raise Must be Meaningful

Logic Phase:		ROW LOGIC(session=0x10fb4f820) (sqlalchemy before_flush)			 - 2022-03-22 20:11:03,774 - logic_logger - INF
..Employee[5] {Update - client} Id: 5, LastName: Buchanan, FirstName: Steven, Title: Sales Manager, TitleOfCourtesy: Mr., BirthDate: 1987-03-04, HireDate: 2025-10-17, Address: 14 Garrett Hill, City: London, Region: British Isles, PostalCode: SW1 8JR, Country: UK, HomePhone: (71) 555-4848, Extension: 3453, Photo: None, Notes: Steven Buchanan graduated from St. Andrews University, Scotland, with a BSC degree in 1976.  Upon joining the company as a sales representative in 1992, he spent 6 months in an orientation program at the Seattle office and then returned to his permanent post in London.  He was promoted to sales manager in March 1993.  Mr. Buchanan has completed the courses 'Successful Telemarketing' and 'International Sales Management.'  He is fluent in French., ReportsTo: 2, PhotoPath: http://accweb/emmployees/buchanan.bmp, IsCommissioned: 0, Salary:  [95000.0000000000-->] 96000, WorksForDepartmentId: 3, OnLoanDepartmentId: None  row: 0x10fb4fa90  session: 0x10fb4f820 - 2022-03-22 20:11:03,775 - logic_logger - INF
..Employee[5] {Constraint Failure: Buchanan needs a more meaningful raise} Id: 5, LastName: Buchanan, FirstName: Steven, Title: Sales Manager, TitleOfCourtesy: Mr., BirthDate: 1987-03-04, HireDate: 2025-10-17, Address: 14 Garrett Hill, City: London, Region: British Isles, PostalCode: SW1 8JR, Country: UK, HomePhone: (71) 555-4848, Extension: 3453, Photo: None, Notes: Steven Buchanan graduated from St. Andrews University, Scotland, with a BSC degree in 1976.  Upon joining the company as a sales representative in 1992, he spent 6 months in an orientation program at the Seattle office and then returned to his permanent post in London.  He was promoted to sales manager in March 1993.  Mr. Buchanan has completed the courses 'Successful Telemarketing' and 'International Sales Management.'  He is fluent in French., ReportsTo: 2, PhotoPath: http://accweb/emmployees/buchanan.bmp, IsCommissioned: 0, Salary:  [95000.0000000000-->] 96000, WorksForDepartmentId: 3, OnLoanDepartmentId: None  row: 0x10fb4fa90  session: 0x10fb4f820 - 2022-03-22 20:11:03,775 - logic_logger - INF

4 features passed, 0 failed, 0 skipped
11 scenarios passed, 0 failed, 0 skipped
37 steps passed, 0 failed, 0 skipped, 0 undefined
Took 0m0.759s

About

Test Driven Development and Transparent Logic Automation


Languages

Language:Python 77.3%Language:Shell 10.3%Language:HTML 7.9%Language:JavaScript 2.5%Language:Gherkin 2.0%