Fix API test authentication in BCApps - #10085
Conversation
5dee938 to
d459cb4
Compare
There was a problem hiding this comment.
Pull request overview
This PR centralizes Basic authentication injection for Library - Graph Mgt-based API tests so they can run in NavUserPassword (UserPassword) containers, and then re-enables several previously disabled API/E2E test suites. It replaces an app-specific Expense Agent auth helper with a shared TestLibraries subscriber and adds a container-side “credential bridge” file so the test runner can obtain the password without requiring Azure Key Vault.
Changes:
- Added
Library - Graph Auth Mgt.as an event subscriber to inject Basic auth for non-Windows test users, sourcing the password from a container file or (fallback) Azure Key Vault. - Removed the Expense Agent test-only auth helper + manual subscription binding; tests now rely on the shared subscriber.
- Re-enabled multiple API/E2E test suites by removing entries from various
*.DisabledTest.jsonfiles (and deleting the APIV1/APIV2 exclusion lists), and updated container provisioning to create the API-test password bridge file.
Reviewed changes
Copilot reviewed 14 out of 15 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/Layers/W1/Tests/TestLibraries/LibraryGraphAuthMgt.Codeunit.al | New shared subscriber that injects Basic auth into Library - Graph Mgt requests for NavUserPassword scenarios. |
| build/scripts/NewBcContainer.ps1 | Writes the container password bridge file and sets ACLs so server-side AL can read it. |
| src/Apps/W1/ExpenseAgent/test/src/Helper/ExpenseAPITestAuthHelper.Codeunit.al | Removes app-specific auth injection helper in favor of shared TestLibraries implementation. |
| src/Apps/W1/ExpenseAgent/test/src/API/ExpenseUsersAPITest.Codeunit.al | Drops manual subscription binding to the removed helper. |
| src/Apps/W1/ExpenseAgent/test/src/API/ExpenseProjectsAPITest.Codeunit.al | Drops manual subscription binding to the removed helper. |
| src/Apps/W1/ExpenseAgent/test/src/API/ExpensePerDiemLocationsTest.Codeunit.al | Drops manual subscription binding to the removed helper. |
| src/Apps/W1/ExpenseAgent/test/src/API/ExpenseCapabilitiesAPITest.Codeunit.al | Drops manual subscription binding to the removed helper. |
| src/DisabledTests/Tests-Integration/Tests-Integration.DisabledTest.json | Re-enables specific integration API tests by removing disable entries. |
| src/DisabledTests/Tests-Graph/Tests-Graph.DisabledTest.json | Re-enables specific Graph E2E tests by removing disable entries. |
| src/DisabledTests/Sustainability_Tests/Sustainability_Tests.DisabledTest.json | Re-enables Sustainability API tests by removing disable entries. |
| src/DisabledTests/Quality_Management-Tests/Quality_Management-Tests.DisabledTest.json | Re-enables Quality Management API tests by removing disable entry. |
| src/DisabledTests/IRS_Forms_Tests/IRS_Forms_Tests.DisabledTest.json | Re-enables IRS 1099 API test by removing disable entry. |
| src/DisabledTests/E-Document_Core_Tests/E-Document Core Tests.DisabledTest.json | Re-enables E-Document API tests by removing disable entry. |
| src/DisabledTests/_Exclude_APIV2__Tests/_Exclude_APIV2__Tests.DisabledTest.json | Deletes the APIV2 exclusion list (re-enables APIV2 suite). |
| src/DisabledTests/_Exclude_APIV1__Tests/_Exclude_APIV1__Tests.DisabledTest.json | Deletes the APIV1 exclusion list (re-enables APIV1 suite). |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Agentic PR Review - Round 1Recommendation: AcceptWhat this PR doesThis PR adds an explicit authentication bridge for API tests that use Library - Graph Mgt. in BCApps UserPassword containers. The subscriber is manual, each affected API test codeunit opts in, Windows-authenticated NAV gates keep their existing behavior, and local NAV UserPassword runs can use the existing Key Vault secret. The change addresses the gate difference directly. It does not change application API behavior, and it avoids making partner test code automatically depend on the BCApps credential bridge. SuggestionsNone. Risk assessment and necessityRisk: The change touches shared test infrastructure and re-enables many existing suites, so CI isolation and concurrency failures may still need separate gate work. The authentication subscriber itself is manually scoped and internal. Necessity: The change is required because BCApps runs these tests with UserPassword while NAV's normal uptake gates use Windows authentication. Without the bridge, the API suites fail with 401 responses and remain disabled.
|
Preserve ordinary Legacy execution because untyped discovery ignores required isolation. Add OS temp fallback and explicit final credential cleanup with 69 passing mocked regressions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3952f078-a881-4da8-ad96-13b727e48a91
|
Review follow-ups are implemented in 6cd9bf1 and propagated through native stack11232. Linux temp-path handling now falls back to System.IO.Path.GetTempPath at both cached-state locations, with a regression that unsets both environment variables. S5 credential lifetime now has an explicit final always() cleanup: setup records the backing path before copying; cleanup stops remaining container consumers before deleting the exact credential file, then normal container teardown still runs. Missing files/containers are handled; stop/deletion errors are surfaced, and hard runner/VM loss remains a limitation. No credential is removed after only the first provider read. The combined mocked/Pester suite passes69/69; current pipeline34316231134 is validating real execution. Separately, this batch corrects an observed Legacy discovery regression that mistakenly scheduled938ordinarycodeunits as clean Disabled work. NAV selection is unchanged. IN/VAT author threads and the Internal-access advisory have been replied to and resolved per the agreed disposition; production PDF remains only in #11230. |
Good Sense Reviewer - Round 9Recommendation: AcceptWhat this PR doesThis PR adds provider-based authentication for API tests and runs HTTP-dependent Disabled-isolation API codeunits on clean tenant workers. The latest commit records and cleans up the shared credential file, uses a safe temp-directory fallback, and keeps Legacy buckets on ordinary execution. I rechecked the current three-dot diff. Authentication is selected in each Graph test initialization before initialized guards, the Microsoft provider exits for SaaS and Windows authentication before reading credentials, and the clean scheduler now uses typed discovery only. The current cleanup step stops remaining container consumers and removes the exact host-backed Status of previous suggestions
New observations (commits since round 8)None. The latest changes address the remaining credential cleanup concern, fix temp-directory fallback behavior, and avoid untyped Legacy clean discovery without introducing a new changed-span issue. Risk assessment and necessityRisk: The main risk is broad test-infrastructure behavior: API authentication, tenant resets, disabled-isolation scheduling, result merging, and credential lifetime. The PowerShell validation for this head passed, and the static checks I reran found no diff whitespace issue, no PowerShell parse error, no missing Graph authentication initialization, and no production report file in this PR. The full pull-request build was still running at review time. Necessity: The change is needed so UserPassword API tests can authenticate while Windows and SaaS runs keep ambient authentication. The clean-tenant scheduler is also needed because these HTTP requests run in a separate server session and must see committed setup.
|
|
Final verification for auth head6cd9bf1a09: PowerShell checks and69localPsterregressions passed. Final W1Legacy1JUnit confirms all6provider-contract tests passed with0failures/0skips. The all-country run34316231134 finished with112/113test jobs passing; all113explicitcredential-cleanup steps succeeded. OverallCI remains failed because CZDefault repeats14missing-General-Posting-Setup failures in unchanged CZZadvance-payment tests; the aggregate statuscheck reflects that lane. No extra exclusions were added to hide them. The Legacy discovery regression is corrected and ordinaryLegacyexecution/provider-testexecution are verified. Windows validation2726212/2726213 passed the identical AL implementation, with211APIIntegrationmethods confirmed in gatejob3634036. Native stack11232 remains intact with #10085at its bottom and seven focused draft layers; nothing was merged or force-pushed. Both PR descriptions now contain final results and evidence limits. |
Replace mocked Windows host drives with Pester TestDrive paths; production cleanup behavior is unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3952f078-a881-4da8-ad96-13b727e48a91
Good Sense Reviewer - Round 10Recommendation: AcceptWhat this PR doesSince round 9, the only new non-merge commit changes the credential cleanup tests to use paths built from the Pester test drive instead of hard-coded Windows paths. The changed span is present in the net PR diff and is limited to The new test fixture still checks the same cleanup contract: only the exact ApiTestPassword file is accepted, the backing mount path is recorded before the credential copy, and the expected workflow environment value is built from the same mocked host mount path. I found no new production-script or AL behavior change in this round. Status of previous suggestions
New observations (commits since round 9)None - the new commit only makes the credential cleanup tests platform-neutral. Risk assessment and necessityRisk: This round adds no new runtime path. The remaining regression surface is the larger API test authentication and clean-tenant scheduler work already reviewed in earlier rounds; the latest commit reduces platform-specific test risk by removing Windows-only mocked paths. Necessity: The change is needed because the cleanup tests should validate the workflow path logic on non-Windows runners as well as Windows. The scope is narrow and matches that need.
|
Merge current main, retain12new Travel Request/capability exclusions and10existing deferred entries, and adapt the newly merged test's obsolete auth helper reference without enabling its tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3952f078-a881-4da8-ad96-13b727e48a91
| /// <summary> | ||
| /// Specifies the authentication provider used for API test requests. | ||
| /// </summary> | ||
| enum 131023 "API Test Authentication" implements "API Test Auth Provider" |
There was a problem hiding this comment.
This new authentication seam is modelled as an extensible enum that implements the interface, so tests must extend the enum just to inject a mock provider. For an injectable dependency, prefer accepting Interface "API Test Auth Provider" directly and assign the implementing codeunit to the interface variable; keep enum-backed selection only as optional convenience wiring.
Knowledge:
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.41.6
| Caption = 'None'; | ||
| Implementation = "API Test Auth Provider" = "No API Test Auth Provider"; | ||
| } | ||
| value(1; "Microsoft Test Environment") |
There was a problem hiding this comment.
The new public authentication selector exposes privileged work that only looks protected by Access = Internal: any dependent test app can select Enum::"API Test Authentication"::"Microsoft Test Environment" and drive Library - Graph Mgt to attach the current environment's API-test password to a caller-chosen request. internal is not an authorization boundary; keep this provider behind a non-public selector or add an explicit authorization/host restriction before credentials are applied.
Knowledge:
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.41.6
| codeunit 139842 "APIV2 - Pictures E2E" | ||
| { | ||
| Subtype = Test; | ||
| RequiredTestIsolation = Disabled; |
There was a problem hiding this comment.
This PR starts opting the API E2E suites into RequiredTestIsolation = Disabled even though the tests commit setup data before calling the API (for example this codeunit commits customers, vendors, items, contacts, and pictures in nearly every test). That means the suite now runs under a non-isolating runner while leaving committed records behind for later tests, creating order-dependent results instead of a clean database per test/codeunit.
Knowledge:
- microsoft/knowledge/testing/testisolation-belongs-on-the-test-runner.md
- microsoft/knowledge/testing/transactionmodel-attribute-governs-test-transactions.md
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.41.6
Good Sense Reviewer - Round 11Recommendation: Accept with SuggestionsWhat this PR doesThis change adds shared API test authentication, container password provisioning and cleanup, and clean-tenant scheduling so API tests can run under UserPassword without breaking ambient-auth environments. The approach fits the problem: the provider exits for ambient authentication, Basic auth is applied through the shared Graph test library when needed, and the broad API test opt-in matches the enablement scope. The remaining concerns are in the runner: the transient classifier is still too broad, and clean Disabled-isolation result files can collide with later tenant result files. Status of previous suggestions
New observations (commits since round 10)S6 (🟠 Moderate): Narrow the transient error match S7 (🟠 Moderate): Use separate clean-test result files Risk assessment and necessityRisk: Product behavior is not changed, but this test-infrastructure path can hide failures or lose published test evidence. Credential handling now creates a shared file in the container mount; the ACL and cleanup flow reduce exposure, while abrupt runner loss remains outside the code guarantee. Necessity: The API tests cannot be broadly re-enabled in UserPassword containers unless the HTTP request session can authenticate and Disabled-isolation tests have fresh tenant workers. The scope is appropriate for a test-enablement base, with business fixture fixes kept separate.
|
Follow reviewed global-instance lifetimes: provider after existing initialization guards, work date per test. Separate residual APIV1 reliability fixes and preserve deferred method scope for an additional native-stack layer. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3952f078-a881-4da8-ad96-13b727e48a91
| Assert.IsTrue(LibraryGraphMgt.GetObjectIDFromJSON(ResponseText, 'id', ApprovalEntryId), 'Could not find approval entry'); | ||
| end; | ||
|
|
||
| local procedure Initialize() |
There was a problem hiding this comment.
This PR's rollout re-enables broad API/E2E coverage, but 37 of the changed test codeunits call LibraryGraphMgt.SetAuthenticationProvider(Enum::"API Test Authentication"::"Microsoft Test Environment") in their Initialize procedure without also calling LibraryGraphMgt.SetLicenseSafeWorkDate(), unlike the other ~113+ peer suites updated in the same PR. That leaves these tests running on the ambient work date instead of the November-15 license-safe date the rest of the rollout now depends on, which is inconsistent and could reintroduce the license/date sensitivity this change set is otherwise fixing, especially for the tests being re-enabled via the large deletions in _Exclude_APIV1__Tests.DisabledTest.json, _Exclude_APIV2__Tests.DisabledTest.json, and Expense_Agent_Tests.DisabledTest.json. Recommend confirming this omission is intentional (e.g. those specific suites are date-insensitive) or adding the missing SetLicenseSafeWorkDate() call for consistency.
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.41.6
| VerifyNoRemainingCalls(); | ||
| end; | ||
|
|
||
| local procedure Initialize() |
There was a problem hiding this comment.
API Test Auth Provider Tests (codeunit 139494) forces EnvironmentInfoTestLibrary.SetTestabilitySoftwareAsAService(false) inside its Initialize() procedure and never restores the prior environment setting afterward. Environment Info Test Library mocks a SingleInstance-like environment flag, so if this codeunit does not run with full test isolation, later tests executed in the same session can inherit the forced on-prem environment mock and exercise authentication logic (which branches on EnvironmentInfo.IsSaaSInfrastructure() in Microsoft Test Auth Provider) under the wrong environment contract, making suite behavior order-dependent. Recommend explicitly restoring the previous environment setting (or bracketing the mock per test) rather than relying on isolation defaults.
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.41.6
Good Sense Reviewer - Round 12Recommendation: Accept with SuggestionsWhat this PR doesThis change adds shared authentication for API tests, prepares clean tenants for tests that require disabled isolation, and re-enables the affected API suites under UserPassword while preserving ambient-auth runs. The latest commit moves license-safe dates into each test initialization path and keeps provider selection once-only where the Graph library instance is retained. That direction is sound: the static scan found all 160 Graph-requesting test codeunits still select the Microsoft test provider, none select it from Status of previous suggestions
New observations (commits since round 11)None - the latest changes only move authentication/date setup into initialization paths and did not add a new changed-span issue. Risk assessment and necessityRisk: Product behavior is unchanged, but the test-infrastructure path is broad. The remaining risk is that the runner may retry a real failure as transient, or lose clean-codeunit result evidence when a later normal job writes the same tenant result file. Current runtime CI is still pending. Necessity: The API suites need a shared authentication path and clean disabled-isolation execution to run reliably in UserPassword containers. The scope remains justified, and the latest initialization changes are necessary to keep test dates license-safe before fixtures are created.
|
Compare the built-in WebServiceActionResultCode directly and pass the Boolean to Assert.IsTrue instead of requiring an unsupported Variant conversion. Preserve the expected Updated value, diagnostics, fixtures and exclusions. AB#646383 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3952f078-a881-4da8-ad96-13b727e48a91
Include the three-line assertion fix from PR #11340 so typed API-test discovery can compile CU 148339. Keep existing exclusions and authentication behavior unchanged. AB#646383 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3952f078-a881-4da8-ad96-13b727e48a91
| /// <summary> | ||
| /// Leaves API test requests configured with the transport's default authentication behavior. | ||
| /// </summary> | ||
| codeunit 131024 "No API Test Auth Provider" implements "API Test Auth Provider" |
There was a problem hiding this comment.
The new codeunit "No API Test Auth Provider" is public even though it looks like an implementation detail behind the public enum/interface-based authentication extension point. Leaving it public makes the concrete provider itself part of the supported API surface; mark the object internal and keep only the intended extensibility contract public.
Suggested fix (apply manually — could not be anchored as a one-click suggestion):
codeunit 131024 "No API Test Auth Provider" implements "API Test Auth Provider"
{
Access = Internal;Knowledge:
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.41.6
| if IsInitialized then | ||
| exit; | ||
|
|
||
| BindSubscription(APITestAuthProviderTests); |
There was a problem hiding this comment.
This test codeunit correctly uses a manual subscriber for scoped behavior, but it binds the global APITestAuthProviderTests instance in Initialize() and never unbinds it. Because that bound instance outlives the Initialize() call, later unrelated requests in the same session can still hit RecordFinalRequestEvent and keep mutating the recorder. Bind the subscriber only for each test's active scope, or add a matching UnbindSubscription when that scope ends.
Knowledge:
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.41.6
…insic Direct equality of WebServiceActionResultCode is rejected by AL0175. Format emits the explicit ALCompiler.ToNavValue system-option conversion; compare both formatted values while retaining the expected Updated code and existing diagnostics. AB#646383 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3952f078-a881-4da8-ad96-13b727e48a91
Use the corrected Format-based assertions from PR #11340. Retain all authentication, initialization, exclusion and stack scope decisions. AB#646383 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 3952f078-a881-4da8-ad96-13b727e48a91
| /// <param name="Authentication">The authentication context to configure.</param> | ||
| procedure ConfigureAuthentication(var Authentication: Codeunit "API Test Auth Context") | ||
| var | ||
| Password: SecretText; |
There was a problem hiding this comment.
The mock auth provider needlessly materializes the Basic-auth password in a plain Text local before converting it to SecretText. Keep the credential in SecretText from creation to the HTTP sink so it never becomes debugger-visible plain text.
Suggested fix (apply manually — could not be anchored as a one-click suggestion):
Password: SecretText;
begin
InvocationCount += 1;
APITestAuthRecorder.RecordCall(StrSubstNo(ProviderCallTok, InvocationCount));
Password := SecretText.SecretStrSubstNo(PasswordTxt);Knowledge:
👍 useful · ❤️ especially valuable · 👎 wrong - reply with why · AL review agent v1.41.6
Summary
Enable Business Central API tests under UserPassword authentication while retaining Windows/SaaS ambient authentication. HTTP-dependent tests run with Disabled isolation on freshly restored tenant workers.
AB#646383
Current head:
b05ed58024. Includes the corrected three-line prerequisite from independent draft #11340, head86497ab769; all eight native-stack follow-ups are refreshed. No exclusions, NAV selectors, authentication behavior, or other upstream files changed in this update. Fresh runtime CI and PowerShell CI are pending validation.The first prerequisite attempt (
a4ed3c3f18, auth2028f76ec0) failed AL0175 because this built-in system option does not support direct=comparison. Those superseded runs were requested to stop after the corrected heads were published. The correction usesAssert.AreEqual(Format(expected), Format(actual), message). Read-only inspection of the local compiler's Format emitter confirms explicit Joker conversion throughALCompiler.ToNavValue; the runtime has aWebServiceActionResultCodeoverload. This is supporting implementation evidence, not a substitute for actual CI compilation and execution. The unchanged auth/scheduler code passed 69 local checks and PowerShell CI on2028f76ec0.The preceding initializer head
1916826fc7reviewed all 160 Graph callers, moved license-safe date setup fromOnRunintoInitialize, and made provider selection once-only where an existing initialization guard and retained Graph instance permit it. Read-only review of396c38..1916826found no significant issues.Run 34472110108, on
1916826fc7, finished failed at 2026-09-10T17:55:52Z: 68 failed workflow tasks, 94 successful and two skipped. All 67 failed test tasks were classified: 64 confirm CU 148339 CS1503 during discovery; three Uncategorized runners (CA, DE, GB) disconnected, with unavailable logs and confirming check annotations. The remaining failure is the aggregate status check. All 46 Legacy test tasks passed. W1 Legacy1 explicitly reports all six CU 139494 provider-contract methods successful on the initializer-refactored head. Credential removal succeeded on 110 test tasks; the three disconnected runners have no confirmed cleanup result.Merged upstream main
2b77a80c1cand resolved the Expense exclusion-file conflict, preserving all 22 entries. GitHub reported this PR mergeable; no newBCApps-SyncMirrorresult was available at the last check. The discovery blocker came from upstreamSpendRequestTest.Codeunit.al(CU 148339, introduced by #11007), whose threeAssert.AreEqualcalls require an unsupported built-inWebServiceActionResultCodeto Variant conversion. Discovery compiles the codeunit before test filtering. Prerequisite #11340 compares the formatted actual and expectedUpdatedvalues, preserving all three assertions and their messages without hardcoding localized text or numeric values. This separately reviewable prerequisite targets main independently; until it is merged there, its three lines are also present in this PR's diff. No PR is automatically merged.Production head
6cd9bf1a09passed all six AL provider-contract tests and PowerShell validation. Pull Request Build finished with 112/113 test jobs passing; the overall result is failed, solely due to 14 missing-General-Posting-Setup failures in unchanged Czech advance-payment tests (CUs148108/148109, CZ Default). They remain enabled and are not hidden by new exclusions. Credential cleanup succeeded in all113test jobs, including the failing lane. AL is unchanged from2a553ac72f, whose Windows validation passed. Historical timing below is not a fresh measurement of this refactor.Authentication design
API Test Authenticationenum selects anAPI Test Auth Provider;Noneis the no-op default.Microsoft Test EnvironmentinInitialize, on the same codeunit-global Graph instance that sends requests. Selection is after existing early-return guards where the instance is retained (92 callers), inside four existing negative initialization branches, and idempotent in 64 unguarded initializers. Missing or unused initialization paths are reviewed and wired individually; selection is removed fromOnRun. No new guard changes existing fixture/callback frequency.AuthenticationProviderResolvedremains instance-scoped for lazy default resolution.ConfigureAuthentication(var Authentication: Codeunit "API Test Auth Context"). Providers configure credentials through this narrow context, not a mutable HTTP request.Security Group.IsWindowsAuthentication(), the public System Application helper backed by the platform authentication check. It does not infer authentication from a User-table Windows SID or require a User-table read.SecretTextpassword; if uncached, it reads the protected container password when present, or Key Vault when absent. An unreadable/empty configured credential produces an error rather than silent fallback.SecretText; the extra resolved/required booleans and cache-result helper are removed.Http Web Request Mgt.AddBasicAuthentication.OnAfterInitializeWebRequestWithURLremains the final trusted request-customization event.Partners can extend the enum and implement the provider without depending on Microsoft password-file or Key Vault conventions.
The supported trust boundary is admitted OnPrem test code plus protected secret provisioning, not
Access = Internal. Existing public OnPrem Key Vault and file-backed test helpers already expose equivalent credential access in that environment. The wrapper adds no elevated AL permission declarations.Credential setup records the exact backing path before copying the password. A final
always()workflow step stops remaining container consumers and deletes that file before normal container teardown, including failure/cancellation paths where the runner remains available. Cleanup failures are surfaced; abrupt runner/VM loss remains outside the guarantee. The credential is not deleted after the first read or while parallel tests still need it.Provider regression coverage includes the URL-free contract, no-auth default, final-event ordering, provider reuse, repeated selection, selection changes, and instance isolation. Structural checks cover the early Windows/SaaS exit, credential-source/cache ordering, and initialization entry points.
Test execution and scope
The HTTP request executes in a separate NST session and must see committed setup. Ordinary test transaction isolation cannot make that session part of the AL test transaction.
The scheduler discovers
RequiredTestIsolation = Disabledfrom apps already selected for UnitTest, IntegrationTest and Uncategorized lanes, retaining test-type and disabled-test filters. Legacy buckets retain ordinary execution: the underlying helper ignores required isolation when no test type is supplied, so untyped clean discovery is deliberately not used. There is no separate app allowlist and no country-specific disabled-test logic.It creates a database template, restores secondary tenants before clean codeunit execution, batches restores separately from execution, enables Task Scheduler for the clean phase, restores tenants afterward, and merges results into normal JUnit output. BCApps already had multitenant containers; this uses them as clean workers.
License-safe work dates remain: the helper is used by 117 test codeunits. Historical XML results show 46 distinct methods in 19 codeunits encountering the licensed-date restriction; 117 is the rollout scope, not the demonstrated failure count.
All 117 former
OnRundate calls are removed. There are now 117Initializedate sites, including six existing sites, plus 11 preserved method-local calls. Unlike provider state, WorkDate is session state: the runner reapplies company WorkDate before each method. Therefore the date is set before fixture creation and the initialization guard, not once per codeunit. Seventy-six previously indirect APIV2 test entries now call Initialize before creating fixtures.The eight business-fixture/product areas below have been removed from this PR. The first seven account for 37 method-specific temporary exclusions across 20 codeunits (21 observed failures plus 16 additional affected methods). The extra APIV1 layer accounts for ten additional conservative exclusions; these are not newly observed failures of the reverted code. This makes 47 temporarily deferred methods, not entire disabled codeunits. Existing nine APIV2 whole-codeunit exclusions and all 22 newer/out-of-scope Expense Agent exclusions remain.
Native GitHub stack #11232
This PR #10085 is the bottom layer, targeting
main. Eight follow-up PRs form a native GitHub stack above it, in the order below; each targets the preceding PR's branch and shows only its own fix and corresponding test re-enablement. GitHub's stack UI is available from these PRs. Existing PR numbers were retained without force-pushing any branch.Each fix layer removes only its own temporary exclusions (12, 10, 1, 2, 2, 10, 0, and 10 methods respectively). The top layer restores all 47 methods deferred by the auth base, while preserving pre-existing exclusions. Each child's parent tip is verified as an ancestor, and each comparison is restricted to that layer's intended files.
These are separately reviewable drafts, not claims of completed runtime validation. The repository's current pull-request build trigger targets
main,releases/*, andfeatures/*; later updates to layers targeting personal feature branches are not automatically covered by that trigger. No workflow-policy expansion or merge is performed here.The additional APIV1 layer changes eight AL files plus its exclusion manifest. Its RapidStart fixture-lifetime fix removes that codeunit's guard, so provider selection there becomes per-invocation but stays idempotent; license-safe WorkDate still precedes fixture cleanup/generation. Existing Expense fixture changes are not part of this additional split.
Historical validation and NAV evidence
Prior-head refactor evidence (not validation of the latest initializer changes)
Run
34316231134, head6cd9bf1a09: 160 jobs succeeded, two failed (CZ Default and the aggregate status check), two skipped. Of113test jobs,112passed; no other test lane failed.Final W1 Legacy1 JUnit confirms CU139494 has six cases, zero failures, and zero skips: default no-auth, extended provider/event ordering, reuse, instance scoping, repeated selection, and switching to None.
W1 Legacy1 live log confirms ordinary Legacy execution; the erroneous938-codeunit clean-discovery loop is no longer used. All113explicit credential-cleanup steps succeeded.
The remaining CI blocker is14Czech purchase/sales advance-payment methods lacking General Posting Setup. Those source files are unchanged by this PR; no workaround or new exclusion was added.
NAV Buddybuild2726212 and SDL2726213 passed the identical AL implementation (before the final PowerShell/workflow-only review batch). Gatejob3634036 verifies211APIV1/APIV2Integrationmethods passed under Windows. NAV selection remains unchanged and the known legacy selection gap is not claimed fixed.
The refactor's second CI run compiled successfully and produced W1 Integration (2,840 tests) and Uncategorized (2,658 tests) results with zero failures. NAV Buddybuild
2726212and SDL2726213passed; gatejob3634036reports 211 APIV1/APIV2 Integration methods passing under Windows.That CI run was not green overall: CZ Default had 14 missing-General-Posting-Setup fixture failures in unchanged advance-payment tests, and US Uncategorized had a client-close timeout despite zero published JUnit assertion failures.
A real scheduler regression was then identified: untyped Legacy discovery scheduled 938 ordinary W1 codeunits as clean Disabled work, causing six-hour timeouts and preventing provider-contract results from being collected.
6cd9bf1a09corrected this; the final run confirmed ordinary Legacy execution and all six provider cases passed. None of the earlier timeouts is described as successful validation.Earlier BCApps head
a6fac87c69: Pull Request Build and PowerShell checks passed; 173 checks passed and two skipped. Four provider tests and 50 scheduler/static tests passed at that stage. These are not results for the current refactor.Pointer-only NAV PR #253796, using isolated BCApps
cc8167987e: Buddybuild2721741and SDL2721742passed on merge066b652da5b7d058e5fd21ac77089849b37381a7. No automatic merge is enabled. NAV selection, native exclusions and company setup were not changed.ADO published no method-level TRX, but gatejob task logs do contain method results. Uptake job
3633108reported no APIV1/APIV2 methods in W1 web-service execution; its typed Integration task ranAPIV1 - Item Variants E2E.TestDeleteInUsesuccessfully.A normal NAV master baseline, job
3633605, reported 806 APIV1/APIV2 methods in W1 web-service execution and all 12 VAT-related methods passing across W1 web-service/Integration tasks. Item VariantsTestDeleteInUsealso passed in CA and US Integration.The selection gap is separate: NAV's legacy caller uses a Disabled runner but defaults required-isolation selection to
None; newly marked Disabled codeunits can be filtered out. It is reported, not fixed here.Configured company names/types match for the seven VAT-related codeunits. NAV restores legacy Extended demo data; BCApps regenerates CRONUS with Contoso and resets clean tenants per codeunit. Matching names and passing baseline methods do not identify the precise causal VAT-data difference.
How much CI time does API enablement add?
The measured increase was about 13-15% in aggregate runner consumption, not a 13-15% increase in developer waiting time. Overall workflow elapsed increased by 2.44 minutes at the median and 5.73 minutes on average.
A paired experiment used fixed control #10904 and API-enabled #10905, with matching harmless CI markers. Run IDs: 33528153281 and 33528158102. Three complete pairs passed (attempts 1, 4 and 8); infrastructure-invalid attempts were excluded.
Aggregate runner time sums parallel job durations; it is neither elapsed pipeline time nor measured CPU utilization. Longest-job duration is a critical-path proxy, not a full workflow dependency-path measurement.
There is no dedicated API job. Work is absorbed by existing Default, Integration and Uncategorized lanes. Their mean W1 job-duration increases were +27.66, +13.20 and +27.67 minutes respectively; Legacy lane changes were variable. Build startup and runner allocation also affect overall elapsed time.
Limitations: only three of five planned successful pairs were obtained; subsequent frozen runs hit warning-baseline drift. These measurements cover the earlier broader API-enabled implementation, before the current business-fix split and initialization/provider refactor. They are the available empirical estimate, not a guaranteed increase or a fresh measurement of the final refactored PR.