Skip to content

Commit 14a4389

Browse files
authored
Fix master CI route-404: remove AppDomain force-load that locks out PluginLoader (#14)
Stop force-loading the SamRockProtocol assembly into the AppDomain from the test fixture ctor. PluginManager scans AppDomain first and registers any plugin it finds with Loader=null, which dedup-locks out the file-based DEBUG_PLUGINS / plugins-folder path that would register a non-null PluginLoader. With Loader=null, mvcBuilder.AddPluginLoader is skipped at PluginManager.cs:255 and the plugin's controllers never make it into MVC ApplicationParts - the plugin shows in DI but all its routes 404. Removing the force-load lets the file-based path resolve the plugin via PluginLoader.CreateFromAssemblyFile (Loader non-null) and registers the controllers with MVC. Retry-with-backoff on the OTP create call kept as cheap defense for any residual timing window.
1 parent c881ef2 commit 14a4389

2 files changed

Lines changed: 32 additions & 9 deletions

File tree

SamRockProtocol.Tests/SamRockProtocolHappyPathTest.cs

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,31 @@ public async Task SamRockProtocol_AcceptsAquaDescriptors()
6262
$"{user.RegisterDetails.Email}:{user.RegisterDetails.Password}"));
6363
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", basic);
6464

65+
// Retry the OTP create call up to ~10s on 404. Background: the plugin's
66+
// controllers can race ServerTester boot - the plugin assembly is loaded
67+
// and the IBTCPayServerPlugin shows in DI (logged above), but MVC
68+
// ApplicationParts may not have registered the plugin's controllers by
69+
// the time this request fires. The race rate is low but non-zero on CI;
70+
// master run #22 vs. PR-branch run #21 had identical tree SHAs and one
71+
// 404'd while the other passed. Retry until the route is reachable or
72+
// the warm-up budget elapses.
6573
var otpReqBody = new { btc = true, btcln = false, lbtc = true };
66-
var otpReq = new StringContent(JsonSerializer.Serialize(otpReqBody), Encoding.UTF8, "application/json");
67-
var otpResp = await client.PostAsync($"api/v1/stores/{storeId}/samrock/otps", otpReq);
68-
var otpRespBody = await otpResp.Content.ReadAsStringAsync();
69-
_helper.WriteLine($"OTP create response ({(int)otpResp.StatusCode}): {otpRespBody}");
74+
HttpResponseMessage otpResp = null;
75+
string otpRespBody = null;
76+
const int otpMaxAttempts = 20;
77+
const int otpDelayMs = 500;
78+
for (var attempt = 1; attempt <= otpMaxAttempts; attempt++)
79+
{
80+
var otpReq = new StringContent(JsonSerializer.Serialize(otpReqBody), Encoding.UTF8, "application/json");
81+
otpResp = await client.PostAsync($"api/v1/stores/{storeId}/samrock/otps", otpReq);
82+
otpRespBody = await otpResp.Content.ReadAsStringAsync();
83+
_helper.WriteLine($"OTP create attempt {attempt}/{otpMaxAttempts} ({(int)otpResp.StatusCode}): {otpRespBody}");
84+
if (otpResp.StatusCode != System.Net.HttpStatusCode.NotFound)
85+
break;
86+
await Task.Delay(otpDelayMs);
87+
}
7088
Assert.True(otpResp.IsSuccessStatusCode,
71-
$"OTP create expected 2xx, got {(int)otpResp.StatusCode}: {otpRespBody}");
89+
$"OTP create expected 2xx after {otpMaxAttempts} attempts, got {(int)otpResp.StatusCode}: {otpRespBody}");
7290

7391
using var otpDoc = JsonDocument.Parse(otpRespBody);
7492
// Property casing depends on the ASP.NET JSON serializer config. Try both.

SamRockProtocol.Tests/SharedPluginTestFixture.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,15 @@ public ConfigurablePluginTestFixture(string testDirName = "SharedPluginTests", b
1313
{
1414
_testDirName = testDirName;
1515
_useNewDb = useNewDb;
16-
// Force-load the SamRockProtocol assembly into the AppDomain so
17-
// PluginManager.PreloadPluginsFromAssemblies discovers it via
18-
// AppDomain.CurrentDomain.GetAssemblies() before BTCPay startup.
19-
_ = typeof(SamRockProtocol.SamRockProtocolPlugin);
16+
// Do NOT force-load the SamRockProtocol assembly into the AppDomain
17+
// here. PluginManager.AddPlugins scans AppDomain assemblies first and
18+
// registers any plugin it finds with Loader=null. Plugins loaded via
19+
// that path skip mvcBuilder.AddPluginLoader at PluginManager.cs:255,
20+
// which means MVC ApplicationParts never includes the plugin's
21+
// assembly -> the IBTCPayServerPlugin shows in DI but its controller
22+
// routes 404. Let the DEBUG_PLUGINS / plugins-folder path resolve the
23+
// plugin via PluginLoader.CreateFromAssemblyFile so the Loader is
24+
// non-null and MVC integration happens.
2025
}
2126

2227
public ServerTester ServerTester { get; private set; }

0 commit comments

Comments
 (0)