mirror of
https://github.com/microsoft/BCQuality.git
synced 2026-08-06 09:26:52 +01:00
- Introduced `al-test-writer` to generate AL test codeunits for production objects based on TDD principles. - Added `al-userguide-test-writer` to create test codeunits from user guide steps, mapping actions and assertions. - Implemented `bc-extension-test-guide` to generate a comprehensive TEST_GUIDE.md for Business Central extensions, covering various categories. - Created `bc-webclient-runner` to automate UI testing of the Business Central web client, capturing screenshots and asserting UI states. - Developed `page-scripting-e2e` to produce a recording plan for Page Scripting, ensuring a structured approach to browser-level testing.
67 lines
3.2 KiB
AL
67 lines
3.2 KiB
AL
// Best practice: deduplicate on the source system's stable id (External Reference + Type)
|
|
// BEFORE staging, backed by a unique key. A replay returns the prior result instead of
|
|
// being processed again. The internal Message ID is never the dedup key, because it is
|
|
// freshly generated per insert and so could never match a repeat.
|
|
|
|
table 50140 "Integration Message"
|
|
{
|
|
DataClassification = CustomerContent;
|
|
|
|
fields
|
|
{
|
|
field(1; "Message ID"; Guid) { Caption = 'Message ID'; }
|
|
field(2; Direction; Enum "Integration Direction") { Caption = 'Direction'; }
|
|
field(3; "Type"; Code[40]) { Caption = 'Type'; }
|
|
field(4; Status; Enum "Integration Status") { Caption = 'Status'; }
|
|
// The source-controlled stable id. This, not Message ID, is what dedup keys on.
|
|
field(5; "External Reference"; Text[100]) { Caption = 'External Reference'; }
|
|
}
|
|
|
|
keys
|
|
{
|
|
key(PK; "Message ID") { Clustered = true; }
|
|
// UNIQUE idempotency key: a second concurrent insert of the same delivery fails at
|
|
// the database, so dedup holds even under a race, not only on the explicit lookup.
|
|
key(Idempotency; "External Reference", "Type") { Unique = true; }
|
|
}
|
|
}
|
|
|
|
codeunit 50141 "Inbound Dedup"
|
|
{
|
|
procedure Stage(ExternalRef: Text[100]; MsgType: Code[40]; Payload: Text): Guid
|
|
var
|
|
Existing: Record "Integration Message";
|
|
IntegrationMessage: Record "Integration Message";
|
|
begin
|
|
// The idempotency lookup: a single indexed read on the unique key.
|
|
Existing.SetRange("External Reference", ExternalRef);
|
|
Existing.SetRange(Type, MsgType);
|
|
if Existing.FindFirst() then begin
|
|
case Existing.Status of
|
|
Existing.Status::Resolved:
|
|
// Already processed. Return the prior result; do NOT do the work again.
|
|
exit(Existing."Message ID");
|
|
Existing.Status::"In Progress":
|
|
// A run is already handling this exact external reference. Reject the
|
|
// second one rather than process the same message concurrently.
|
|
Error('Message %1 of type %2 is already in progress', ExternalRef, MsgType);
|
|
end;
|
|
// Any other prior state (for example Failed): return the existing row so the
|
|
// resolution flow handles it, instead of minting a duplicate.
|
|
exit(Existing."Message ID");
|
|
end;
|
|
|
|
// No prior message exists: stage a genuinely new one.
|
|
IntegrationMessage.Init();
|
|
IntegrationMessage."Message ID" := CreateGuid(); // internal id, never the dedup key
|
|
IntegrationMessage.Direction := IntegrationMessage.Direction::Inbound;
|
|
IntegrationMessage."External Reference" := ExternalRef;
|
|
IntegrationMessage.Type := MsgType;
|
|
IntegrationMessage.Status := IntegrationMessage.Status::New;
|
|
IntegrationMessage.SetRequest(Payload);
|
|
// If a concurrent request slipped past the lookup, the unique key makes THIS Insert
|
|
// fail rather than create a duplicate. Either way, the side effect runs at most once.
|
|
IntegrationMessage.Insert(true);
|
|
exit(IntegrationMessage."Message ID");
|
|
end;
|
|
}
|