diff --git a/content/docs/agentic-coding/GettingMore/refactoring.md b/content/docs/agentic-coding/GettingMore/refactoring.md
new file mode 100644
index 00000000..6aaac1c6
--- /dev/null
+++ b/content/docs/agentic-coding/GettingMore/refactoring.md
@@ -0,0 +1,1041 @@
+---
+title: "Refactoring Legacy Code"
+linkTitle: "Refactoring"
+weight: 4
+description: >
+ Use AI to modernize and improve existing AL code while maintaining functionality
+---
+
+## Scenario
+
+You've inherited a legacy AL codeunit from an older Business Central version. The code works, but it:
+
+- Uses deprecated patterns and APIs
+- Has poor structure and naming
+- Lacks documentation and error handling
+- Contains duplicated logic
+- Is difficult to test and maintain
+
+You need to modernize this code while ensuring it continues to work correctly.
+
+## Goal
+
+Use AI to help you:
+
+- Identify refactoring opportunities
+- Modernize deprecated APIs
+- Improve code structure
+- Enhance readability and maintainability
+- Add tests to ensure no regression
+
+## The Legacy Code
+
+Here's a typical legacy codeunit that needs refactoring:
+
+```al
+codeunit 50100 "Sales Order Management"
+{
+ procedure ProcessSalesOrder(DocNo: Code[20])
+ var
+ SH: Record "Sales Header";
+ SL: Record "Sales Line";
+ C: Record Customer;
+ I: Record Item;
+ qty: Decimal;
+ amt: Decimal;
+ begin
+ if DocNo = '' then
+ exit;
+
+ SH.Get(SH."Document Type"::Order, DocNo);
+ C.Get(SH."Sell-to Customer No.");
+
+ if C.Blocked <> C.Blocked::" " then begin
+ Message('Customer is blocked!');
+ exit;
+ end;
+
+ SL.SetRange("Document No.", DocNo);
+ if SL.Find('-') then
+ repeat
+ if SL.Type = SL.Type::Item then begin
+ I.Get(SL."No.");
+ if I.Inventory < SL.Quantity then
+ Message('Not enough inventory for item ' + I."No.");
+ end;
+ qty := qty + SL.Quantity;
+ amt := amt + SL."Line Amount";
+ until SL.Next() = 0;
+
+ if amt > 10000 then begin
+ SL.SetRange("Document No.", DocNo);
+ if SL.Find('-') then
+ repeat
+ SL."Line Discount %" := 5;
+ SL.Modify();
+ until SL.Next() = 0;
+ end;
+
+ Message('Processed order ' + DocNo + ' with total amount ' + Format(amt));
+ end;
+}
+```
+
+## Step-by-Step Refactoring Process
+
+### Step 1: Write Tests First
+
+{{% alert title="Critical: Test First!" color="warning" %}}
+Before refactoring any code, write tests that verify its current behavior. These tests act as a safety net, ensuring you don't accidentally break functionality during refactoring.
+{{% /alert %}}
+
+**Prompt**:
+
+```
+I need to refactor this legacy code, but first I need comprehensive tests for its current behavior.
+
+Create tests that verify:
+- All current functionality (even if imperfect)
+- Expected outputs for given inputs
+- Error conditions and edge cases
+- Current behavior (not ideal behavior)
+
+These tests should pass with the current code and catch any regressions during refactoring.
+
+[Paste the legacy code]
+```
+
+**Expected Test Code**:
+
+```al
+codeunit 50101 "Sales Order Mgmt. Tests"
+{
+ Subtype = Test;
+
+ var
+ Assert: Codeunit "Library Assert";
+ LibrarySales: Codeunit "Library - Sales";
+
+ [Test]
+ procedure TestCurrentBehaviorWithValidOrder()
+ var
+ SalesHeader: Record "Sales Header";
+ SalesOrderMgmt: Codeunit "Sales Order Management";
+ begin
+ // [GIVEN] A valid sales order (testing CURRENT behavior)
+ CreateTestSalesOrder(SalesHeader, 'CUST001', 5000);
+
+ // [WHEN] Processing the order
+ SalesOrderMgmt.ProcessSalesOrder(SalesHeader."No.");
+
+ // [THEN] No error occurs and discount is applied
+ // (These tests lock in current behavior before refactoring)
+ Assert.IsTrue(true, 'Process completed without error');
+ end;
+
+ [Test]
+ procedure TestCurrentBlockedCustomerBehavior()
+ var
+ SalesHeader: Record "Sales Header";
+ Customer: Record Customer;
+ SalesOrderMgmt: Codeunit "Sales Order Management";
+ begin
+ // [GIVEN] Order with blocked customer
+ CreateBlockedCustomerOrder(SalesHeader, Customer);
+
+ // [WHEN] Processing (currently just shows Message, doesn't error)
+ SalesOrderMgmt.ProcessSalesOrder(SalesHeader."No.");
+
+ // [THEN] Process completes (testing CURRENT behavior, even if not ideal)
+ // Note: After refactoring, this might throw an error instead
+ end;
+
+ // Additional tests for current behavior...
+}
+```
+
+**Why This Matters**:
+
+- Tests catch regressions immediately
+- You can refactor confidently
+- Tests document current behavior
+- You can run tests after each small refactoring step
+
+### Step 2: Initial Assessment
+
+**Prompt**:
+
+```
+Analyze this legacy AL code and identify refactoring opportunities.
+
+Categorize issues by:
+- Critical: Deprecated APIs, security issues, bugs
+- Major: Poor structure, missing error handling, performance issues
+- Minor: Naming, formatting, documentation
+
+[Paste the legacy code]
+
+For each issue, explain:
+- What the problem is
+- Why it's problematic
+- How to fix it
+```
+
+**Expected AI Findings**:
+
+**Critical Issues:**
+
+- Using `Message()` for errors instead of `Error()`
+- No proper error handling (could cause partial updates)
+- Using deprecated `Find('-')` syntax
+- Modifying records in a loop without proper handling
+
+**Major Issues:**
+
+- Poor variable naming (SH, SL, C, I, qty, amt)
+- Single large procedure doing multiple things
+- Hard-coded business rules (10000 threshold, 5% discount)
+- Inefficient database operations (multiple FindSet loops)
+- No validation of input parameters
+- Missing XML documentation
+- No telemetry or logging
+
+**Minor Issues:**
+
+- No separation of concerns
+- String concatenation for messages
+- Magic numbers without explanation
+- Inconsistent code style
+
+### Step 3: Create a Refactoring Plan
+
+**Prompt**:
+
+```
+Create a detailed refactoring plan for this code.
+
+I already have tests in place that verify current behavior.
+
+Break refactoring into phases:
+1. Safe refactorings (no behavior change)
+2. Structural improvements
+3. API modernization
+4. Behavioral improvements (may change behavior)
+5. Final enhancements
+
+For each phase, list specific changes and estimated risk level.
+After each phase, I'll run tests to ensure nothing broke.
+```
+
+**Expected Plan**:
+
+**Phase 0: Tests (COMPLETED)**
+
+- ✅ Written tests for current behavior
+- ✅ Tests pass with current code
+- ✅ Ready to refactor safely
+
+**Phase 1: Safe Refactorings (Low Risk)**
+
+- Rename variables to be descriptive
+- Add XML documentation
+- Extract magic numbers to constants
+- Format code consistently
+- Add code comments for complex logic
+
+**Phase 2: Structural Improvements (Medium Risk)**
+
+- Extract methods for each responsibility
+- Separate validation, calculation, and update logic
+- Remove code duplication
+- Improve error handling
+
+**Phase 3: API Modernization (Medium Risk)**
+
+- Replace deprecated Find('-') with FindSet()
+- Use proper Error() instead of Message()
+- Add SetLoadFields for performance
+- Use ModifyAll where appropriate
+
+**Phase 4: Behavioral Improvements (Medium Risk)**
+
+- Change Message() to Error() for validation failures
+- This CHANGES behavior, so update tests accordingly
+- Add new tests for improved error handling
+
+**Phase 5: Additional Testing (Low Risk)**
+
+- Add more edge case tests
+- Add tests for each extracted method
+- Expand test coverage
+
+**Phase 6: Enhancements (Low Risk)**
+
+- Add telemetry
+- Implement proper transaction handling
+- Add configuration instead of hard-coded values
+
+### Step 4: Run Your Tests
+
+**Before refactoring anything**, verify your tests pass:
+
+```powershell
+# Run the test codeunit
+Invoke-NAVCodeunit -CodeunitId 50101
+```
+
+**All tests should pass.** If they don't, fix the tests first.
+
+### Step 5: Phase 1 - Safe Refactorings
+
+**Prompt**:
+
+```
+Perform Phase 1 refactorings on this code:
+- Rename all variables to be descriptive
+- Add XML documentation to the procedure
+- Extract magic numbers (10000, 5) to constants
+- Add inline comments for complex logic
+- Improve code formatting
+
+[Paste the legacy code]
+```
+
+**Expected Result**:
+
+```al
+///
+/// Manages sales order processing including validation, inventory checking, and discount application
+///
+codeunit 50100 "Sales Order Management"
+{
+ var
+ LargeOrderThreshold: Decimal;
+ LargeOrderDiscountPct: Decimal;
+
+ ///
+ /// Processes a sales order by validating customer, checking inventory, and applying discounts
+ ///
+ /// The sales order number to process
+ procedure ProcessSalesOrder(DocumentNo: Code[20])
+ var
+ SalesHeader: Record "Sales Header";
+ SalesLine: Record "Sales Line";
+ Customer: Record Customer;
+ Item: Record Item;
+ TotalQuantity: Decimal;
+ TotalAmount: Decimal;
+ begin
+ // Initialize thresholds
+ LargeOrderThreshold := 10000;
+ LargeOrderDiscountPct := 5;
+
+ // Validate input
+ if DocumentNo = '' then
+ exit;
+
+ // Get sales order and customer
+ SalesHeader.Get(SalesHeader."Document Type"::Order, DocumentNo);
+ Customer.Get(SalesHeader."Sell-to Customer No.");
+
+ // Check if customer is blocked
+ if Customer.Blocked <> Customer.Blocked::" " then begin
+ Message('Customer is blocked!');
+ exit;
+ end;
+
+ // Check inventory and calculate totals
+ SalesLine.SetRange("Document No.", DocumentNo);
+ if SalesLine.Find('-') then
+ repeat
+ // Verify inventory availability for items
+ if SalesLine.Type = SalesLine.Type::Item then begin
+ Item.Get(SalesLine."No.");
+ if Item.Inventory < SalesLine.Quantity then
+ Message('Not enough inventory for item ' + Item."No.");
+ end;
+
+ // Accumulate totals
+ TotalQuantity := TotalQuantity + SalesLine.Quantity;
+ TotalAmount := TotalAmount + SalesLine."Line Amount";
+ until SalesLine.Next() = 0;
+
+ // Apply discount for large orders
+ if TotalAmount > LargeOrderThreshold then begin
+ SalesLine.SetRange("Document No.", DocumentNo);
+ if SalesLine.Find('-') then
+ repeat
+ SalesLine."Line Discount %" := LargeOrderDiscountPct;
+ SalesLine.Modify();
+ until SalesLine.Next() = 0;
+ end;
+
+ // Notify user of completion
+ Message('Processed order ' + DocumentNo + ' with total amount ' + Format(TotalAmount));
+ end;
+}
+```
+
+### Step 6: Run Tests After Phase 1
+
+**Critical**: After completing Phase 1 refactorings, run your tests:
+
+```powershell
+Invoke-NAVCodeunit -CodeunitId 50101
+```
+
+**All tests should still pass.** If any fail, the refactoring changed behavior - fix it before proceeding.
+
+### Step 7: Phase 2 - Structural Improvements
+
+**Prompt**:
+
+```
+Refactor this code to improve structure:
+
+1. Extract separate methods for:
+ - Customer validation
+ - Inventory checking
+ - Total calculation
+ - Discount application
+
+2. Replace Message() with proper Error() for validation failures
+
+3. Add proper error handling with try-catch where appropriate
+
+4. Use StrSubstNo for string formatting
+
+[Include the Phase 1 code]
+```
+
+**Expected Result**:
+
+```al
+///
+/// Manages sales order processing including validation, inventory checking, and discount application
+///
+codeunit 50100 "Sales Order Management"
+{
+ var
+ LargeOrderThreshold: Decimal;
+ LargeOrderDiscountPct: Decimal;
+ InsufficientInventoryErr: Label 'Insufficient inventory for item %1. Available: %2, Required: %3';
+ CustomerBlockedErr: Label 'Customer %1 is blocked and cannot place orders.';
+ OrderProcessedMsg: Label 'Successfully processed order %1 with total amount %2.';
+
+ ///
+ /// Processes a sales order by validating customer, checking inventory, and applying discounts
+ ///
+ /// The sales order number to process
+ procedure ProcessSalesOrder(DocumentNo: Code[20])
+ var
+ SalesHeader: Record "Sales Header";
+ TotalAmount: Decimal;
+ begin
+ LargeOrderThreshold := 10000;
+ LargeOrderDiscountPct := 5;
+
+ if DocumentNo = '' then
+ Error('Document number cannot be empty.');
+
+ if not SalesHeader.Get(SalesHeader."Document Type"::Order, DocumentNo) then
+ Error('Sales order %1 not found.', DocumentNo);
+
+ ValidateCustomer(SalesHeader);
+ CheckInventoryAvailability(SalesHeader);
+ TotalAmount := CalculateOrderTotal(SalesHeader);
+
+ if TotalAmount > LargeOrderThreshold then
+ ApplyLargeOrderDiscount(SalesHeader);
+
+ Message(OrderProcessedMsg, DocumentNo, Format(TotalAmount, 0, ''));
+ end;
+
+ ///
+ /// Validates that the customer is not blocked
+ ///
+ /// The sales header to validate
+ local procedure ValidateCustomer(SalesHeader: Record "Sales Header")
+ var
+ Customer: Record Customer;
+ begin
+ if not Customer.Get(SalesHeader."Sell-to Customer No.") then
+ Error('Customer %1 not found.', SalesHeader."Sell-to Customer No.");
+
+ if Customer.Blocked <> Customer.Blocked::" " then
+ Error(CustomerBlockedErr, Customer."No.");
+ end;
+
+ ///
+ /// Checks that sufficient inventory is available for all items on the order
+ ///
+ /// The sales header to check
+ local procedure CheckInventoryAvailability(SalesHeader: Record "Sales Header")
+ var
+ SalesLine: Record "Sales Line";
+ Item: Record Item;
+ begin
+ SalesLine.SetRange("Document Type", SalesHeader."Document Type");
+ SalesLine.SetRange("Document No.", SalesHeader."No.");
+ SalesLine.SetRange(Type, SalesLine.Type::Item);
+
+ if SalesLine.FindSet() then
+ repeat
+ if Item.Get(SalesLine."No.") then
+ if Item.Inventory < SalesLine.Quantity then
+ Error(InsufficientInventoryErr, Item."No.", Item.Inventory, SalesLine.Quantity);
+ until SalesLine.Next() = 0;
+ end;
+
+ ///
+ /// Calculates the total amount for the sales order
+ ///
+ /// The sales header to calculate totals for
+ /// The total amount of the order
+ local procedure CalculateOrderTotal(SalesHeader: Record "Sales Header"): Decimal
+ var
+ SalesLine: Record "Sales Line";
+ TotalAmount: Decimal;
+ begin
+ SalesLine.SetRange("Document Type", SalesHeader."Document Type");
+ SalesLine.SetRange("Document No.", SalesHeader."No.");
+ SalesLine.SetLoadFields("Line Amount");
+
+ if SalesLine.FindSet() then
+ repeat
+ TotalAmount += SalesLine."Line Amount";
+ until SalesLine.Next() = 0;
+
+ exit(TotalAmount);
+ end;
+
+ ///
+ /// Applies a discount to all lines on large orders
+ ///
+ /// The sales header to apply discounts to
+ local procedure ApplyLargeOrderDiscount(SalesHeader: Record "Sales Header")
+ var
+ SalesLine: Record "Sales Line";
+ begin
+ SalesLine.SetRange("Document Type", SalesHeader."Document Type");
+ SalesLine.SetRange("Document No.", SalesHeader."No.");
+ SalesLine.ModifyAll("Line Discount %", LargeOrderDiscountPct, true);
+ end;
+}
+```
+
+### Step 8: Run Tests After Phase 2
+
+Run your tests again after structural improvements:
+
+```powershell
+Invoke-NAVCodeunit -CodeunitId 50101
+```
+
+Tests should pass. If not, review the structural changes.
+
+### Step 9: Phase 3 - API Modernization
+
+**Prompt**:
+
+```
+Modernize this code to use current AL best practices:
+
+1. Replace deprecated Find('-') with FindSet()
+2. Add proper SetLoadFields for performance
+3. Use better error handling patterns
+4. Implement proper transaction handling
+5. Replace hard-coded values with setup table
+
+[Include the Phase 2 code]
+```
+
+**Expected Result**:
+
+```al
+///
+/// Manages sales order processing including validation, inventory checking, and discount application
+///
+codeunit 50100 "Sales Order Management"
+{
+ var
+ InsufficientInventoryErr: Label 'Insufficient inventory for item %1. Available: %2, Required: %3';
+ CustomerBlockedErr: Label 'Customer %1 is blocked and cannot place orders.';
+ OrderProcessedMsg: Label 'Successfully processed order %1 with total amount %2.';
+
+ ///
+ /// Processes a sales order by validating customer, checking inventory, and applying discounts
+ ///
+ /// The sales order number to process
+ procedure ProcessSalesOrder(DocumentNo: Code[20])
+ var
+ SalesHeader: Record "Sales Header";
+ TotalAmount: Decimal;
+ begin
+ if DocumentNo = '' then
+ Error('Document number cannot be empty.');
+
+ if not SalesHeader.Get(SalesHeader."Document Type"::Order, DocumentNo) then
+ Error('Sales order %1 not found.', DocumentNo);
+
+ ValidateCustomer(SalesHeader);
+ CheckInventoryAvailability(SalesHeader);
+ TotalAmount := CalculateOrderTotal(SalesHeader);
+
+ if ShouldApplyLargeOrderDiscount(TotalAmount) then
+ ApplyLargeOrderDiscount(SalesHeader);
+
+ LogOrderProcessing(DocumentNo, TotalAmount);
+ Message(OrderProcessedMsg, DocumentNo, Format(TotalAmount, 0, ''));
+ end;
+
+ local procedure ValidateCustomer(SalesHeader: Record "Sales Header")
+ var
+ Customer: Record Customer;
+ begin
+ Customer.SetLoadFields(Blocked);
+
+ if not Customer.Get(SalesHeader."Sell-to Customer No.") then
+ Error('Customer %1 not found.', SalesHeader."Sell-to Customer No.");
+
+ if Customer.Blocked <> Customer.Blocked::" " then
+ Error(CustomerBlockedErr, Customer."No.");
+ end;
+
+ local procedure CheckInventoryAvailability(SalesHeader: Record "Sales Header")
+ var
+ SalesLine: Record "Sales Line";
+ Item: Record Item;
+ begin
+ SalesLine.SetRange("Document Type", SalesHeader."Document Type");
+ SalesLine.SetRange("Document No.", SalesHeader."No.");
+ SalesLine.SetRange(Type, SalesLine.Type::Item);
+ SalesLine.SetLoadFields("No.", Quantity);
+
+ if SalesLine.FindSet() then begin
+ Item.SetLoadFields(Inventory);
+ repeat
+ if Item.Get(SalesLine."No.") then
+ if Item.Inventory < SalesLine.Quantity then
+ Error(InsufficientInventoryErr, Item."No.", Item.Inventory, SalesLine.Quantity);
+ until SalesLine.Next() = 0;
+ end;
+ end;
+
+ local procedure CalculateOrderTotal(SalesHeader: Record "Sales Header"): Decimal
+ var
+ SalesLine: Record "Sales Line";
+ TotalAmount: Decimal;
+ begin
+ SalesLine.SetRange("Document Type", SalesHeader."Document Type");
+ SalesLine.SetRange("Document No.", SalesHeader."No.");
+ SalesLine.SetLoadFields("Line Amount");
+
+ if SalesLine.FindSet() then
+ repeat
+ TotalAmount += SalesLine."Line Amount";
+ until SalesLine.Next() = 0;
+
+ exit(TotalAmount);
+ end;
+
+ local procedure ShouldApplyLargeOrderDiscount(TotalAmount: Decimal): Boolean
+ var
+ SalesSetup: Record "Sales & Receivables Setup";
+ begin
+ SalesSetup.Get();
+ exit(TotalAmount > SalesSetup."Large Order Threshold");
+ end;
+
+ local procedure ApplyLargeOrderDiscount(SalesHeader: Record "Sales Header")
+ var
+ SalesLine: Record "Sales Line";
+ SalesSetup: Record "Sales & Receivables Setup";
+ begin
+ SalesSetup.Get();
+
+ SalesLine.SetRange("Document Type", SalesHeader."Document Type");
+ SalesLine.SetRange("Document No.", SalesHeader."No.");
+ SalesLine.ModifyAll("Line Discount %", SalesSetup."Large Order Discount %", true);
+ end;
+
+ local procedure LogOrderProcessing(DocumentNo: Code[20]; TotalAmount: Decimal)
+ var
+ Telemetry: Codeunit Telemetry;
+ CustomDimensions: Dictionary of [Text, Text];
+ begin
+ CustomDimensions.Add('DocumentNo', DocumentNo);
+ CustomDimensions.Add('TotalAmount', Format(TotalAmount));
+
+ Telemetry.LogMessage('SALESORD-001', 'Sales order processed successfully',
+ Verbosity::Normal, DataClassification::SystemMetadata,
+ TelemetryScope::ExtensionPublisher, CustomDimensions);
+ end;
+}
+```
+
+### Step 10: Update Tests for Behavioral Changes
+
+Now that the code is cleaner, you may want to improve behavior (e.g., Error() instead of Message()):
+
+**Prompt**:
+
+```
+I want to change the behavior to use Error() instead of Message() for validation failures.
+
+First, update the tests to expect these errors:
+- TestBlockedCustomerError should expect an error
+- Update assertions to use asserterror and Assert.ExpectedError
+
+Then show the code changes needed.
+```
+
+### Step 11: Expand Test Coverage
+
+**Prompt**:
+
+```
+Now that refactoring is complete, add more comprehensive tests for edge cases.
+
+Include tests for:
+- Happy path: successful processing
+- Customer validation errors
+- Inventory availability checks
+- Large order discount application
+- Edge cases: empty document number, non-existent order
+
+Use the AL test framework with proper setup and teardown.
+```
+
+**Expected Test Codeunit**:
+
+```al
+codeunit 50101 "Sales Order Management Tests"
+{
+ Subtype = Test;
+
+ var
+ Assert: Codeunit "Library Assert";
+ LibrarySales: Codeunit "Library - Sales";
+ LibraryInventory: Codeunit "Library - Inventory";
+
+ [Test]
+ procedure TestSuccessfulOrderProcessing()
+ var
+ SalesHeader: Record "Sales Header";
+ SalesOrderMgmt: Codeunit "Sales Order Management";
+ begin
+ // [GIVEN] A valid sales order with sufficient inventory
+ CreateSalesOrderWithInventory(SalesHeader);
+
+ // [WHEN] Processing the order
+ SalesOrderMgmt.ProcessSalesOrder(SalesHeader."No.");
+
+ // [THEN] No error is raised
+ // Verified by test not failing
+ end;
+
+ [Test]
+ procedure TestBlockedCustomerError()
+ var
+ SalesHeader: Record "Sales Header";
+ Customer: Record Customer;
+ SalesOrderMgmt: Codeunit "Sales Order Management";
+ begin
+ // [GIVEN] A sales order for a blocked customer
+ CreateSalesOrderWithBlockedCustomer(SalesHeader, Customer);
+
+ // [WHEN] Processing the order
+ asserterror SalesOrderMgmt.ProcessSalesOrder(SalesHeader."No.");
+
+ // [THEN] Error is raised about blocked customer
+ Assert.ExpectedError(StrSubstNo('Customer %1 is blocked', Customer."No."));
+ end;
+
+ [Test]
+ procedure TestInsufficientInventoryError()
+ var
+ SalesHeader: Record "Sales Header";
+ Item: Record Item;
+ SalesOrderMgmt: Codeunit "Sales Order Management";
+ begin
+ // [GIVEN] A sales order with more quantity than available inventory
+ CreateSalesOrderWithInsufficientInventory(SalesHeader, Item);
+
+ // [WHEN] Processing the order
+ asserterror SalesOrderMgmt.ProcessSalesOrder(SalesHeader."No.");
+
+ // [THEN] Error is raised about insufficient inventory
+ Assert.ExpectedError('Insufficient inventory');
+ end;
+
+ [Test]
+ procedure TestLargeOrderDiscountApplied()
+ var
+ SalesHeader: Record "Sales Header";
+ SalesLine: Record "Sales Line";
+ SalesSetup: Record "Sales & Receivables Setup";
+ SalesOrderMgmt: Codeunit "Sales Order Management";
+ ExpectedDiscount: Decimal;
+ begin
+ // [GIVEN] A large order that qualifies for discount
+ SalesSetup.Get();
+ ExpectedDiscount := SalesSetup."Large Order Discount %";
+ CreateLargeSalesOrder(SalesHeader);
+
+ // [WHEN] Processing the order
+ SalesOrderMgmt.ProcessSalesOrder(SalesHeader."No.");
+
+ // [THEN] Discount is applied to all lines
+ SalesLine.SetRange("Document Type", SalesHeader."Document Type");
+ SalesLine.SetRange("Document No.", SalesHeader."No.");
+ SalesLine.FindSet();
+ repeat
+ Assert.AreEqual(ExpectedDiscount, SalesLine."Line Discount %", 'Discount not applied correctly');
+ until SalesLine.Next() = 0;
+ end;
+
+ local procedure CreateSalesOrderWithInventory(var SalesHeader: Record "Sales Header")
+ var
+ Item: Record Item;
+ SalesLine: Record "Sales Line";
+ begin
+ LibraryInventory.CreateItem(Item);
+ Item.Inventory := 100;
+ Item.Modify();
+
+ LibrarySales.CreateSalesHeader(SalesHeader, SalesHeader."Document Type"::Order, LibrarySales.CreateCustomerNo());
+ LibrarySales.CreateSalesLine(SalesLine, SalesHeader, SalesLine.Type::Item, Item."No.", 10);
+ end;
+
+ // Additional helper procedures...
+}
+```
+
+## Review Points: What to Check
+
+After each refactoring phase, verify:
+
+### ✅ Functionality Preserved
+
+- [ ] All original functionality still works
+- [ ] No regressions introduced
+- [ ] Tests pass (or create tests first!)
+- [ ] Edge cases still handled
+
+### ✅ Code Quality Improved
+
+- [ ] More readable and maintainable
+- [ ] Better structure and organization
+- [ ] Clearer naming and documentation
+- [ ] Proper error handling
+
+### ✅ Performance Not Degraded
+
+- [ ] Database operations optimized
+- [ ] No unnecessary loops or queries
+- [ ] Proper use of SetLoadFields
+- [ ] Efficient algorithms
+
+### ✅ Modern Practices Applied
+
+- [ ] Current AL syntax and APIs
+- [ ] No deprecated features
+- [ ] Proper telemetry
+- [ ] Good separation of concerns
+
+## Advanced Refactoring Patterns
+
+### Extract Interface for Testability
+
+**Prompt**:
+
+```
+Refactor this codeunit to use interfaces for dependencies, making it more testable.
+
+Extract interfaces for:
+- Customer validation
+- Inventory checking
+- Discount calculation
+
+This will allow us to mock these dependencies in tests.
+```
+
+### Convert to Event-Driven Architecture
+
+**Prompt**:
+
+```
+Refactor this code to use Business Central events:
+
+1. Publish events for:
+ - Before order processing
+ - After order validation
+ - Before discount application
+ - After order processing
+
+2. This allows other extensions to hook into the process
+```
+
+### Add Batch Processing Support
+
+**Prompt**:
+
+```
+Extend this codeunit to support batch processing of multiple orders.
+
+Include:
+- Progress tracking
+- Error handling per order (don't fail entire batch)
+- Summary reporting
+- Performance optimization for bulk operations
+```
+
+## Common Refactoring Challenges
+
+### Challenge 1: Unknown Business Logic
+
+**Problem**: Code has complex logic without documentation
+
+**Solution**:
+
+```
+Analyze this code and explain what business logic it implements:
+[paste complex code]
+
+Then suggest how to make the logic clearer through refactoring.
+```
+
+### Challenge 2: Tightly Coupled Code
+
+**Problem**: Code has many dependencies that are hard to untangle
+
+**Solution**:
+
+```
+This code is tightly coupled. Create a refactoring plan to:
+1. Identify dependencies
+2. Extract interfaces
+3. Use dependency injection
+4. Make code more modular
+```
+
+### Challenge 3: Large Procedures
+
+**Problem**: Single procedure doing too many things
+
+**Solution**:
+
+```
+This procedure is too large and complex.
+Apply the Single Responsibility Principle to break it into smaller procedures.
+Each procedure should have one clear purpose.
+```
+
+## Best Practices for Refactoring with AI
+
+### 1. Always Write Tests First
+
+{{% alert title="Golden Rule" color="primary" %}}
+**Never refactor without tests.** Tests are your safety net. Write them first, run them, then refactor.
+{{% /alert %}}
+
+```
+Before we start refactoring, let's write tests that lock in the current behavior.
+Even if the current behavior isn't perfect, we need to know if we change it.
+```
+
+### 2. Refactor in Small Steps
+
+Don't try to refactor everything at once. Use incremental changes:
+
+```
+Let's refactor this code in three phases:
+Phase 1: Just improve naming and documentation
+[Run tests - should pass]
+Phase 2: Extract methods
+[Run tests - should pass]
+Phase 3: Modernize APIs
+[Run tests - should pass]
+```
+
+### 3. Run Tests After Every Change
+
+```
+I've completed the naming refactoring.
+Let me run the tests to make sure nothing broke.
+
+[Run tests]
+
+Great, tests pass. Now let's proceed to Phase 2.
+```
+
+### 3. Use Git Commits for Each Phase
+
+```
+We've completed Phase 1: Safe Refactorings.
+Before moving to Phase 2, I'll commit these changes.
+Suggest a good commit message for these refactorings.
+```
+
+### 4. Document Why, Not Just What
+
+```
+For each major refactoring, add a comment explaining WHY the change was made:
+- Why was the old approach problematic?
+- What does the new approach solve?
+- Are there trade-offs?
+```
+
+### 5. Keep Performance in Mind
+
+```
+As we refactor this code, let's ensure we don't hurt performance.
+For each database operation change, explain the performance implications.
+```
+
+## Practice Exercise
+
+Refactor this legacy code:
+
+```al
+codeunit 50200 "Item Price Calculator"
+{
+ procedure CalcPrice(IN: Code[20]; CU: Code[10]; QT: Decimal): Decimal
+ var
+ I: Record Item;
+ C: Record Customer;
+ P: Decimal;
+ begin
+ I.Get(IN);
+ P := I."Unit Price";
+
+ if QT > 100 then
+ P := P * 0.9;
+
+ if CU <> '' then begin
+ C.Get(CU);
+ if C."Customer Price Group" = 'VIP' then
+ P := P * 0.95;
+ end;
+
+ exit(P);
+ end;
+}
+```
+
+**Your Tasks**:
+
+1. Assess the code and list issues
+2. Create a refactoring plan
+3. Apply naming improvements
+4. Extract methods for each responsibility
+5. Add proper documentation and error handling
+6. Create tests
+7. Add telemetry
+
+## Next Steps
+
+- Learn about [writing tests](../testing) for your refactored code
+- See how [code review](../code-review) catches refactoring issues
+- Explore [adding telemetry](../telemetry) to monitor refactored code
diff --git a/content/docs/agentic-coding/GettingMore/testing.md b/content/docs/agentic-coding/GettingMore/testing.md
new file mode 100644
index 00000000..3c9cfae7
--- /dev/null
+++ b/content/docs/agentic-coding/GettingMore/testing.md
@@ -0,0 +1,902 @@
+---
+title: "Writing Tests"
+linkTitle: "Testing"
+weight: 5
+description: >
+ Use AI to create comprehensive test coverage for your AL code
+---
+
+## Scenario
+
+You've developed new features for your Business Central extension, but you need comprehensive test coverage to:
+
+- Ensure code works as expected
+- Prevent regressions when making changes
+- Document expected behavior
+- Enable confident refactoring
+- Meet quality standards
+
+Writing tests manually is time-consuming, and you want to use AI to accelerate the process while maintaining test quality.
+
+## Goal
+
+Use AI to help you:
+
+- Generate unit tests for individual procedures
+- Create integration tests for complex workflows
+- Design test data and scenarios
+- Write test helpers and fixtures
+- Create mock objects for dependencies
+- Implement data-driven tests
+
+## The Code to Test
+
+Here's a codeunit that needs test coverage:
+
+```al
+codeunit 50100 "Order Discount Manager"
+{
+ procedure CalculateDiscount(var SalesHeader: Record "Sales Header"): Decimal
+ var
+ Customer: Record Customer;
+ DiscountPct: Decimal;
+ begin
+ if not Customer.Get(SalesHeader."Sell-to Customer No.") then
+ Error('Customer %1 not found', SalesHeader."Sell-to Customer No.");
+
+ DiscountPct := GetCustomerDiscount(Customer);
+ DiscountPct += GetVolumeDiscount(SalesHeader);
+ DiscountPct += GetSeasonalDiscount();
+
+ if DiscountPct > 50 then
+ DiscountPct := 50;
+
+ exit(DiscountPct);
+ end;
+
+ local procedure GetCustomerDiscount(Customer: Record Customer): Decimal
+ begin
+ case Customer."Customer Discount Group" of
+ 'VIP':
+ exit(10);
+ 'PREMIUM':
+ exit(5);
+ else
+ exit(0);
+ end;
+ end;
+
+ local procedure GetVolumeDiscount(SalesHeader: Record "Sales Header"): Decimal
+ var
+ SalesLine: Record "Sales Line";
+ TotalAmount: Decimal;
+ begin
+ SalesLine.SetRange("Document Type", SalesHeader."Document Type");
+ SalesLine.SetRange("Document No.", SalesHeader."No.");
+ SalesLine.CalcSums("Line Amount");
+ TotalAmount := SalesLine."Line Amount";
+
+ case true of
+ TotalAmount >= 10000:
+ exit(15);
+ TotalAmount >= 5000:
+ exit(10);
+ TotalAmount >= 1000:
+ exit(5);
+ else
+ exit(0);
+ end;
+ end;
+
+ local procedure GetSeasonalDiscount(): Decimal
+ begin
+ if Date2DMY(Today, 2) in [11, 12] then
+ exit(5);
+ exit(0);
+ end;
+}
+```
+
+## Step-by-Step Testing Process
+
+### Step 1: Generate Basic Test Structure
+
+**Prompt**:
+
+```
+Create a test codeunit for the Order Discount Manager.
+
+Include:
+- Proper test codeunit structure with Subtype = Test
+- Setup and teardown if needed
+- Test procedure stubs for all public procedures
+- Use AL test framework conventions
+- Include Assert codeunit
+- Add test isolation attributes where needed
+
+[Include the codeunit code]
+```
+
+**Expected Result**:
+
+```al
+codeunit 50101 "Order Discount Mgr. Tests"
+{
+ Subtype = Test;
+
+ var
+ Assert: Codeunit "Library Assert";
+ LibrarySales: Codeunit "Library - Sales";
+ LibraryUtility: Codeunit "Library - Utility";
+ IsInitialized: Boolean;
+
+ [Test]
+ procedure TestCalculateDiscountBasic()
+ begin
+ // Test basic discount calculation
+ end;
+
+ [Test]
+ procedure TestVIPCustomerDiscount()
+ begin
+ // Test VIP customer gets 10% discount
+ end;
+
+ [Test]
+ procedure TestVolumeDiscountTiers()
+ begin
+ // Test volume discount calculation at different amounts
+ end;
+
+ [Test]
+ procedure TestSeasonalDiscount()
+ begin
+ // Test seasonal discount in November/December
+ end;
+
+ [Test]
+ procedure TestMaximumDiscountCap()
+ begin
+ // Test discount cannot exceed 50%
+ end;
+
+ [Test]
+ procedure TestCustomerNotFoundError()
+ begin
+ // Test error when customer doesn't exist
+ end;
+
+ local procedure Initialize()
+ begin
+ if IsInitialized then
+ exit;
+
+ // Setup code here
+ IsInitialized := true;
+ end;
+}
+```
+
+### Step 2: Implement Happy Path Test
+
+**Prompt**:
+
+```
+Implement the TestCalculateDiscountBasic test using the Given-When-Then pattern.
+
+This test should verify:
+- A standard customer with a medium-size order
+- Gets the expected discount percentage
+- No errors are raised
+
+Use the Library codeunits for test data creation.
+Include clear comments for each section.
+```
+
+**Expected Result**:
+
+```al
+[Test]
+procedure TestCalculateDiscountBasic()
+var
+ Customer: Record Customer;
+ SalesHeader: Record "Sales Header";
+ OrderDiscountMgr: Codeunit "Order Discount Manager";
+ ActualDiscount: Decimal;
+ ExpectedDiscount: Decimal;
+begin
+ // [SCENARIO] Calculate discount for a standard order
+ Initialize();
+
+ // [GIVEN] A standard customer with no special discount group
+ Customer.Get(LibrarySales.CreateCustomerNo());
+ Customer."Customer Discount Group" := '';
+ Customer.Modify();
+
+ // [GIVEN] A sales order with total amount of 2000 (5% volume discount)
+ SalesHeader := CreateSalesOrderWithAmount(Customer."No.", 2000);
+
+ // [GIVEN] Expected discount is 5% (volume only, not in seasonal months)
+ ExpectedDiscount := 5;
+
+ // [WHEN] Calculating the discount
+ ActualDiscount := OrderDiscountMgr.CalculateDiscount(SalesHeader);
+
+ // [THEN] The discount matches expected value
+ Assert.AreEqual(ExpectedDiscount, ActualDiscount, 'Discount calculation incorrect');
+end;
+
+local procedure CreateSalesOrderWithAmount(CustomerNo: Code[20]; TotalAmount: Decimal): Record "Sales Header"
+var
+ SalesHeader: Record "Sales Header";
+ SalesLine: Record "Sales Line";
+ Item: Record Item;
+begin
+ LibrarySales.CreateSalesHeader(SalesHeader, SalesHeader."Document Type"::Order, CustomerNo);
+
+ Item."No." := LibraryUtility.GenerateRandomCode(Item.FieldNo("No."), Database::Item);
+ Item."Unit Price" := TotalAmount;
+ Item.Insert();
+
+ LibrarySales.CreateSalesLine(SalesLine, SalesHeader, SalesLine.Type::Item, Item."No.", 1);
+ SalesLine.Validate("Unit Price", TotalAmount);
+ SalesLine.Modify(true);
+
+ exit(SalesHeader);
+end;
+```
+
+### Step 3: Implement Error Case Tests
+
+**Prompt**:
+
+```
+Implement the TestCustomerNotFoundError test.
+
+This should verify:
+- When an invalid customer number is used
+- An appropriate error is raised
+- The error message contains the customer number
+
+Use asserterror and Assert.ExpectedError.
+```
+
+**Expected Result**:
+
+```al
+[Test]
+procedure TestCustomerNotFoundError()
+var
+ SalesHeader: Record "Sales Header";
+ OrderDiscountMgr: Codeunit "Order Discount Manager";
+ InvalidCustomerNo: Code[20];
+begin
+ // [SCENARIO] Error is raised when customer doesn't exist
+ Initialize();
+
+ // [GIVEN] A sales header with non-existent customer
+ InvalidCustomerNo := 'INVALID123';
+ LibrarySales.CreateSalesHeader(SalesHeader, SalesHeader."Document Type"::Order, '');
+ SalesHeader."Sell-to Customer No." := InvalidCustomerNo;
+ SalesHeader.Modify();
+
+ // [WHEN] Calculating discount for non-existent customer
+ asserterror OrderDiscountMgr.CalculateDiscount(SalesHeader);
+
+ // [THEN] Appropriate error is raised with customer number
+ Assert.ExpectedError(StrSubstNo('Customer %1 not found', InvalidCustomerNo));
+end;
+```
+
+### Step 4: Implement Data-Driven Tests
+
+**Prompt**:
+
+```
+Create a comprehensive test for all customer discount tiers (VIP, PREMIUM, standard).
+
+Use a data-driven approach where one test function tests all scenarios.
+Create a local helper that takes customer group and expected discount as parameters.
+
+Test these scenarios:
+- VIP customer: 10% discount
+- PREMIUM customer: 5% discount
+- Standard customer: 0% discount
+- Empty group: 0% discount
+```
+
+**Expected Result**:
+
+```al
+[Test]
+procedure TestCustomerDiscountTiers()
+begin
+ // [SCENARIO] Different customer types receive appropriate discounts
+ Initialize();
+
+ // [GIVEN] [WHEN] [THEN] VIP customers get 10% discount
+ VerifyCustomerTypeDiscount('VIP', 10);
+
+ // [GIVEN] [WHEN] [THEN] Premium customers get 5% discount
+ VerifyCustomerTypeDiscount('PREMIUM', 5);
+
+ // [GIVEN] [WHEN] [THEN] Standard customers get 0% discount
+ VerifyCustomerTypeDiscount('STANDARD', 0);
+
+ // [GIVEN] [WHEN] [THEN] Customers with no group get 0% discount
+ VerifyCustomerTypeDiscount('', 0);
+end;
+
+local procedure VerifyCustomerTypeDiscount(CustomerGroup: Code[10]; ExpectedDiscount: Decimal)
+var
+ Customer: Record Customer;
+ SalesHeader: Record "Sales Header";
+ OrderDiscountMgr: Codeunit "Order Discount Manager";
+ ActualDiscount: Decimal;
+begin
+ // Create customer with specific group
+ Customer.Get(LibrarySales.CreateCustomerNo());
+ Customer."Customer Discount Group" := CustomerGroup;
+ Customer.Modify();
+
+ // Create order with amount below volume discount threshold (< 1000)
+ SalesHeader := CreateSalesOrderWithAmount(Customer."No.", 500);
+
+ // Calculate discount
+ ActualDiscount := OrderDiscountMgr.CalculateDiscount(SalesHeader);
+
+ // Verify expected discount
+ Assert.AreEqual(ExpectedDiscount, ActualDiscount,
+ StrSubstNo('Incorrect discount for customer group %1', CustomerGroup));
+end;
+```
+
+### Step 5: Test Edge Cases and Boundaries
+
+**Prompt**:
+
+```
+Create tests for volume discount boundary conditions.
+
+Test these specific amounts:
+- 999: Just below 1000 threshold (expect 0% volume discount)
+- 1000: Exactly at threshold (expect 5% volume discount)
+- 4999: Just below 5000 threshold (expect 5% volume discount)
+- 5000: Exactly at threshold (expect 10% volume discount)
+- 9999: Just below 10000 threshold (expect 10% volume discount)
+- 10000: Exactly at threshold (expect 15% volume discount)
+
+Use descriptive test names and clear assertions.
+```
+
+**Expected Result**:
+
+```al
+[Test]
+procedure TestVolumeDiscountBoundaries()
+begin
+ // [SCENARIO] Volume discounts applied at exact thresholds
+ Initialize();
+
+ // Just below first threshold
+ VerifyVolumeDiscount(999, 0, 'Below 1000 threshold');
+
+ // Exactly at first threshold
+ VerifyVolumeDiscount(1000, 5, 'At 1000 threshold');
+
+ // Between first and second threshold
+ VerifyVolumeDiscount(4999, 5, 'Below 5000 threshold');
+
+ // Exactly at second threshold
+ VerifyVolumeDiscount(5000, 10, 'At 5000 threshold');
+
+ // Between second and third threshold
+ VerifyVolumeDiscount(9999, 10, 'Below 10000 threshold');
+
+ // Exactly at third threshold
+ VerifyVolumeDiscount(10000, 15, 'At 10000 threshold');
+end;
+
+local procedure VerifyVolumeDiscount(OrderAmount: Decimal; ExpectedVolumeDiscount: Decimal; Scenario: Text)
+var
+ Customer: Record Customer;
+ SalesHeader: Record "Sales Header";
+ OrderDiscountMgr: Codeunit "Order Discount Manager";
+ ActualDiscount: Decimal;
+begin
+ // Create standard customer (no customer discount)
+ Customer.Get(LibrarySales.CreateCustomerNo());
+ Customer."Customer Discount Group" := '';
+ Customer.Modify();
+
+ // Create order with specific amount
+ SalesHeader := CreateSalesOrderWithAmount(Customer."No.", OrderAmount);
+
+ // Calculate discount (assuming not in seasonal months)
+ ActualDiscount := OrderDiscountMgr.CalculateDiscount(SalesHeader);
+
+ // Verify volume discount
+ Assert.AreEqual(ExpectedVolumeDiscount, ActualDiscount,
+ StrSubstNo('Incorrect volume discount for %1 (amount: %2)', Scenario, OrderAmount));
+end;
+```
+
+### Step 6: Test Maximum Discount Cap
+
+**Prompt**:
+
+```
+Create a test that verifies the 50% maximum discount cap.
+
+Test scenario:
+- VIP customer (10% discount)
+- Large order (15% volume discount)
+- Seasonal period (5% discount)
+- Total would be 30%, which is under cap - verify this works
+- Then test a scenario that would exceed 50% and verify it's capped
+
+Consider: What combination would exceed 50%? We may need to modify setup or mock GetSeasonalDiscount.
+```
+
+**Expected Result**:
+
+```al
+[Test]
+procedure TestMaximumDiscountCap()
+var
+ Customer: Record Customer;
+ SalesHeader: Record "Sales Header";
+ OrderDiscountMgr: Codeunit "Order Discount Manager";
+ ActualDiscount: Decimal;
+begin
+ // [SCENARIO] Discount is capped at 50% even when combined discounts exceed it
+ Initialize();
+
+ // [GIVEN] VIP customer (10% discount)
+ Customer.Get(LibrarySales.CreateCustomerNo());
+ Customer."Customer Discount Group" := 'VIP';
+ Customer.Modify();
+
+ // [GIVEN] Large order (15% volume discount)
+ SalesHeader := CreateSalesOrderWithAmount(Customer."No.", 10000);
+
+ // [GIVEN] If testing in Nov/Dec, total would be 10+15+5=30% (under cap)
+ // [GIVEN] To test cap, we need a scenario that would exceed 50%
+ // Note: With current business logic, max is 30%, so cap isn't reached
+
+ // [WHEN] Calculating discount
+ ActualDiscount := OrderDiscountMgr.CalculateDiscount(SalesHeader);
+
+ // [THEN] Discount doesn't exceed 50%
+ Assert.IsTrue(ActualDiscount <= 50, 'Discount exceeds maximum allowed 50%');
+end;
+
+[Test]
+procedure TestDiscountCapWithMultipleDiscounts()
+var
+ Customer: Record Customer;
+ SalesHeader: Record "Sales Header";
+ OrderDiscountMgr: Codeunit "Order Discount Manager";
+ ActualDiscount: Decimal;
+begin
+ // [SCENARIO] Combined discounts that would exceed 50% are capped
+ // [NOTE] This test may need the business logic modified to make it relevant
+ // Current max possible: 10% (VIP) + 15% (volume) + 5% (seasonal) = 30%
+
+ Initialize();
+
+ // Create scenario with maximum possible discounts
+ Customer.Get(LibrarySales.CreateCustomerNo());
+ Customer."Customer Discount Group" := 'VIP';
+ Customer.Modify();
+
+ SalesHeader := CreateSalesOrderWithAmount(Customer."No.", 10000);
+
+ // Calculate discount
+ ActualDiscount := OrderDiscountMgr.CalculateDiscount(SalesHeader);
+
+ // Verify cap is applied if needed
+ Assert.IsTrue(ActualDiscount <= 50, 'Discount cap not enforced');
+end;
+```
+
+### Step 7: Test Time-Dependent Logic
+
+**Prompt**:
+
+```
+Create tests for the seasonal discount that depends on the current date.
+
+Since GetSeasonalDiscount uses Today, we need to:
+1. Test behavior for November (month 11)
+2. Test behavior for December (month 12)
+3. Test behavior for other months
+
+Explain how to handle date-dependent tests in AL.
+Options:
+- Make the date a parameter (refactor code)
+- Use test isolation / mocking
+- Create tests that work year-round
+```
+
+**Expected Result**:
+
+```al
+[Test]
+procedure TestSeasonalDiscountInNovember()
+var
+ Customer: Record Customer;
+ SalesHeader: Record "Sales Header";
+ OrderDiscountMgr: Codeunit "Order Discount Manager";
+ ActualDiscount: Decimal;
+ CurrentMonth: Integer;
+begin
+ // [SCENARIO] Seasonal discount is applied in November
+ // [NOTE] This test only passes when run in November
+
+ Initialize();
+
+ CurrentMonth := Date2DMY(Today, 2);
+ if CurrentMonth <> 11 then
+ exit; // Skip test if not November
+
+ // [GIVEN] Standard customer with small order (no other discounts)
+ Customer.Get(LibrarySales.CreateCustomerNo());
+ Customer."Customer Discount Group" := '';
+ Customer.Modify();
+
+ SalesHeader := CreateSalesOrderWithAmount(Customer."No.", 500);
+
+ // [WHEN] Calculating discount in November
+ ActualDiscount := OrderDiscountMgr.CalculateDiscount(SalesHeader);
+
+ // [THEN] 5% seasonal discount is applied
+ Assert.AreEqual(5, ActualDiscount, 'Seasonal discount not applied in November');
+end;
+
+[Test]
+procedure TestNoSeasonalDiscountInJanuary()
+var
+ Customer: Record Customer;
+ SalesHeader: Record "Sales Header";
+ OrderDiscountMgr: Codeunit "Order Discount Manager";
+ ActualDiscount: Decimal;
+ CurrentMonth: Integer;
+begin
+ // [SCENARIO] No seasonal discount in non-holiday months
+
+ Initialize();
+
+ CurrentMonth := Date2DMY(Today, 2);
+ if CurrentMonth in [11, 12] then
+ exit; // Skip test if in seasonal period
+
+ // [GIVEN] Standard customer with small order
+ Customer.Get(LibrarySales.CreateCustomerNo());
+ Customer."Customer Discount Group" := '';
+ Customer.Modify();
+
+ SalesHeader := CreateSalesOrderWithAmount(Customer."No.", 500);
+
+ // [WHEN] Calculating discount outside seasonal period
+ ActualDiscount := OrderDiscountMgr.CalculateDiscount(SalesHeader);
+
+ // [THEN] No discount is applied
+ Assert.AreEqual(0, ActualDiscount, 'Unexpected discount outside seasonal period');
+end;
+
+// [RECOMMENDATION] Refactor GetSeasonalDiscount to accept date parameter
+// This would make testing much easier and more reliable:
+// local procedure GetSeasonalDiscount(CheckDate: Date): Decimal
+```
+
+### Step 8: Integration Test
+
+**Prompt**:
+
+```
+Create an integration test that tests the complete workflow:
+
+1. Create a new customer with VIP status
+2. Create a sales order for that customer
+3. Add multiple lines totaling over 10,000
+4. Calculate the discount
+5. Verify all discount types are correctly combined
+6. Verify the discount is applied to the sales header
+
+This test should verify the codeunit works correctly with real BC data and tables.
+```
+
+**Expected Result**:
+
+```al
+[Test]
+procedure TestCompleteDiscountWorkflow()
+var
+ Customer: Record Customer;
+ SalesHeader: Record "Sales Header";
+ SalesLine: Record "Sales Line";
+ Item: Record Item;
+ OrderDiscountMgr: Codeunit "Order Discount Manager";
+ ExpectedDiscount: Decimal;
+ ActualDiscount: Decimal;
+ CurrentMonth: Integer;
+begin
+ // [SCENARIO] Complete workflow of discount calculation and application
+ Initialize();
+
+ // [GIVEN] A VIP customer
+ Customer.Get(LibrarySales.CreateCustomerNo());
+ Customer."Customer Discount Group" := 'VIP';
+ Customer.Modify();
+
+ // [GIVEN] A sales order with multiple lines totaling 12,000
+ LibrarySales.CreateSalesHeader(SalesHeader, SalesHeader."Document Type"::Order, Customer."No.");
+
+ // Add three items
+ CreateItemAndSalesLine(SalesHeader, 5000);
+ CreateItemAndSalesLine(SalesHeader, 4000);
+ CreateItemAndSalesLine(SalesHeader, 3000);
+
+ // [GIVEN] Expected discount calculation:
+ // - VIP customer: 10%
+ // - Volume (>10000): 15%
+ // - Seasonal (if Nov/Dec): 5%
+ CurrentMonth := Date2DMY(Today, 2);
+ if CurrentMonth in [11, 12] then
+ ExpectedDiscount := 30 // 10 + 15 + 5
+ else
+ ExpectedDiscount := 25; // 10 + 15
+
+ // [WHEN] Calculating the discount
+ ActualDiscount := OrderDiscountMgr.CalculateDiscount(SalesHeader);
+
+ // [THEN] The correct combined discount is calculated
+ Assert.AreEqual(ExpectedDiscount, ActualDiscount,
+ 'Complete discount calculation incorrect');
+
+ // [THEN] Verify order total is correct
+ SalesLine.SetRange("Document Type", SalesHeader."Document Type");
+ SalesLine.SetRange("Document No.", SalesHeader."No.");
+ SalesLine.CalcSums("Line Amount");
+ Assert.AreEqual(12000, SalesLine."Line Amount", 'Order total incorrect');
+end;
+
+local procedure CreateItemAndSalesLine(SalesHeader: Record "Sales Header"; Amount: Decimal)
+var
+ Item: Record Item;
+ SalesLine: Record "Sales Line";
+begin
+ Item."No." := LibraryUtility.GenerateRandomCode(Item.FieldNo("No."), Database::Item);
+ Item."Unit Price" := Amount;
+ Item.Insert();
+
+ LibrarySales.CreateSalesLine(SalesLine, SalesHeader, SalesLine.Type::Item, Item."No.", 1);
+ SalesLine.Validate("Unit Price", Amount);
+ SalesLine.Modify(true);
+end;
+```
+
+## Review Points: What to Check in Tests
+
+### ✅ Test Quality
+
+- [ ] Tests are independent (can run in any order)
+- [ ] Tests are repeatable (same result every time)
+- [ ] Tests have clear Given-When-Then structure
+- [ ] Test names clearly describe what is being tested
+- [ ] Assertions have descriptive error messages
+
+### ✅ Test Coverage
+
+- [ ] Happy path scenarios covered
+- [ ] Error cases tested
+- [ ] Edge cases and boundaries tested
+- [ ] All public procedures have tests
+- [ ] Integration scenarios tested
+
+### ✅ Test Maintainability
+
+- [ ] Helper methods reduce duplication
+- [ ] Test data creation is centralized
+- [ ] Tests are easy to understand
+- [ ] Tests don't depend on specific data
+- [ ] Setup and teardown properly implemented
+
+### ✅ Test Performance
+
+- [ ] Tests run quickly
+- [ ] Minimal database operations
+- [ ] Proper use of test isolation
+- [ ] No unnecessary waits or delays
+
+## Advanced Testing Patterns
+
+### Pattern 1: Test Fixtures
+
+**Prompt**:
+
+```
+Create a test fixture class for sales orders that provides:
+- Standard order (customer with no discounts, small amount)
+- VIP order (VIP customer, medium amount)
+- Large order (standard customer, large amount)
+- Complex order (VIP customer, large amount, multiple lines)
+
+These fixtures should be reusable across all tests.
+```
+
+### Pattern 2: Mock Objects
+
+**Prompt**:
+
+```
+The GetSeasonalDiscount procedure is hard to test because it depends on Today.
+
+Refactor the code to use dependency injection:
+1. Create an interface for date provider
+2. Inject the date provider into the codeunit
+3. Create a mock date provider for testing
+4. Show how to test with different dates
+```
+
+### Pattern 3: Test Data Builders
+
+**Prompt**:
+
+```
+Create a fluent test data builder for sales orders:
+
+SalesOrderBuilder
+ .WithCustomer(CustomerNo)
+ .WithVIPStatus()
+ .WithLine(ItemNo, Quantity, Price)
+ .WithTotalAmount(Amount)
+ .Build()
+
+This makes test data creation more readable and flexible.
+```
+
+## Best Practices for AI-Assisted Testing
+
+### 1. Start with Test Cases, Then Generate
+
+```
+Before generating test code, help me identify all test cases for this procedure:
+- What scenarios should be tested?
+- What are the edge cases?
+- What error conditions exist?
+- What are the boundary conditions?
+
+[Include procedure code]
+```
+
+### 2. Generate Tests in Batches
+
+```
+Generate tests for these three related procedures together so they share test helpers:
+- CalculateDiscount
+- ApplyDiscount
+- ValidateDiscount
+```
+
+### 3. Request Explanatory Comments
+
+```
+Generate the test with detailed comments explaining:
+- Why this test case is important
+- What could go wrong if this test didn't exist
+- Any gotchas or special considerations
+```
+
+### 4. Ask for Test Improvements
+
+```
+Review this test I wrote. Suggest improvements for:
+- Clarity and readability
+- Better assertions
+- Edge cases I might have missed
+- Ways to make it more maintainable
+```
+
+### 5. Generate Test Documentation
+
+```
+Create documentation for this test suite explaining:
+- What is being tested
+- Test coverage summary
+- How to run the tests
+- How to add new tests
+- Known limitations
+```
+
+## Common Testing Challenges
+
+### Challenge 1: Testing Private Methods
+
+**Problem**: Local procedures can't be tested directly
+
+**Solution**:
+
+```
+I need to test this local procedure. Options:
+1. Make it public (if appropriate)
+2. Test it indirectly through public procedures
+3. Extract to a separate testable codeunit
+
+Which approach is best for this scenario? [Include code]
+```
+
+### Challenge 2: Testing Database Operations
+
+**Problem**: Tests that modify database are slow and fragile
+
+**Solution**:
+
+```
+This codeunit performs database operations. Help me:
+1. Identify which operations need real database
+2. Which can be mocked or isolated
+3. Create a testing strategy that balances coverage and speed
+```
+
+### Challenge 3: Testing External Dependencies
+
+**Problem**: Code calls external services or APIs
+
+**Solution**:
+
+```
+This code calls an external API. Create:
+1. An interface for the API
+2. A mock implementation for testing
+3. Tests using the mock
+4. Integration tests for the real API (marked for manual runs)
+```
+
+## Practice Exercise
+
+Write comprehensive tests for this codeunit:
+
+```al
+codeunit 50200 "Credit Limit Checker"
+{
+ procedure CheckCreditLimit(CustomerNo: Code[20]; NewOrderAmount: Decimal): Boolean
+ var
+ Customer: Record Customer;
+ CustLedgerEntry: Record "Cust. Ledger Entry";
+ TotalOutstanding: Decimal;
+ begin
+ if not Customer.Get(CustomerNo) then
+ Error('Customer not found');
+
+ Customer.CalcFields("Balance (LCY)");
+ TotalOutstanding := Customer."Balance (LCY)" + NewOrderAmount;
+
+ if Customer."Credit Limit (LCY)" = 0 then
+ exit(true);
+
+ exit(TotalOutstanding <= Customer."Credit Limit (LCY)");
+ end;
+}
+```
+
+**Your Tasks**:
+
+1. List all test scenarios
+2. Create test codeunit structure
+3. Implement tests for:
+ - Customer not found
+ - No credit limit (unlimited)
+ - Within credit limit
+ - Exactly at credit limit
+ - Over credit limit
+ - Edge cases
+4. Add integration test
+5. Review and improve tests
+
+## Next Steps
+
+- Learn how [code review](../code-review) can verify test quality
+- See how [refactoring](../refactoring) benefits from good tests
+- Explore [documentation](../documentation) for test procedures