Test: provider-overlap block, grace clauses, proof provider mismatch, requested-undo wording, no passkey promise, bearer prune (red where the code is wrong)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Heimdall 2026-11-14 12:13:56 +00:00
commit e60953e469
5 changed files with 121 additions and 2 deletions

View file

@ -46,11 +46,11 @@ public sealed class MergeAccountsPageTests
public MergeProofs Proofs => Host.Services.GetRequiredService<MergeProofs>();
public static async Task<World> Make(bool enabled = true)
public static async Task<World> Make(bool enabled = true, string[]? otherProviders = null)
{
var rig = new Rig(realSessions: true, accountMerge: enabled);
var me = await rig.AccountAsync("quill-9f3a", "discord");
var other = await rig.AccountAsync(OtherName, "patreon", "google");
var other = await rig.AccountAsync(OtherName, otherProviders ?? ["patreon", "google"]);
var session = await rig.Host.SignInSessionAsync(me.HeimdallId, "discord");
var myName = await rig.InScopeAsync(async sp => (await sp.GetRequiredService<BifrostDbContext>().UserAccounts.AsNoTracking().SingleAsync(x => x.Id == me.Id)).DisplayName);
var world = new World { Rig = rig, Me = me, Other = other, Session = session, MyName = myName };
@ -349,6 +349,46 @@ public sealed class MergeAccountsPageTests
Assert.DoesNotContain("consentSelf", page);
}
[Theory]
[InlineData("self")]
[InlineData("other")]
public async Task Two_accounts_holding_a_login_of_one_provider_are_blocked_before_consent_and_the_block_names_only_the_provider(string keep)
{
using var w = await World.Make(otherProviders: ["discord", "patreon"]);
await w.ProveSelf();
await w.ProveOther("discord");
await w.ProveOther("patreon");
var page = await w.Page($"?keep={keep}");
Assert.Contains("Both accounts hold a Discord login", page);
Assert.DoesNotContain("consentSelf", page);
Assert.DoesNotContain("name=\"digest\"", page);
Assert.DoesNotContain("ProveRemaining", page);
var words = Regex.Match(page, "Both accounts hold a[^<]*").Value;
Assert.DoesNotContain(OtherName, words);
Assert.DoesNotContain(w.MyName, words);
Assert.DoesNotContain(w.Other.HeimdallId, words);
}
[Fact]
public async Task A_confirm_for_accounts_holding_one_providers_login_is_Blocked_writes_nothing_and_keeps_the_proofs()
{
using var w = await World.Make(otherProviders: ["discord", "patreon"]);
await w.ProveSelf();
await w.ProveOther("discord");
await w.ProveOther("patreon");
var digest = await w.Rig.DigestAsync(w.Me, w.Other);
var response = await w.Confirm("self", digest);
Assert.Equal(Where("self", "blocked"), MergePage.Location(response));
Assert.Equal(0, await w.Rig.MergeCountAsync());
Assert.NotNull(w.Proofs.Snapshot(w.Me.Id).Self);
Assert.NotNull(w.Proofs.Snapshot(w.Me.Id).Other);
Assert.Empty(w.Rig.Moves.Requests);
}
// ---- confirm ----
[Fact]

View file

@ -73,6 +73,12 @@ public sealed class MergePreviewTests
Assert.Contains(MergePreview.Statements, x => x.Contains(statement, StringComparison.Ordinal));
}
[Fact]
public void The_statements_promise_no_passkey_because_no_page_offers_one()
{
Assert.DoesNotContain(MergePreview.Statements, x => x.Contains("passkey", StringComparison.OrdinalIgnoreCase));
}
[Theory]
[InlineData("blocked")]
[InlineData("preview_stale")]

View file

@ -61,6 +61,26 @@ public sealed class MergeProofCompletionTests
}
}
[Theory]
[InlineData(MergeSide.Self)]
[InlineData(MergeSide.Other)]
public async Task A_completion_through_another_provider_than_the_attempts_records_no_proof(MergeSide side)
{
using var host = new HeimdallHost(realSessions: true);
var me = await SignedIn(host);
var other = await SignedIn(host);
SetList(host, "discord", "patreon");
var proved = side == MergeSide.Self ? me : other;
var attempt = await Begin(host, me, side, via: "discord");
var done = await Finish(host, me, attempt, proved.Account, host.Token(proved.Account), provider: "patreon");
Assert.NotEqual(Page + "?result=merge_proof_recorded", Location(done));
var held = Proofs(host).Snapshot(me.UserAccountId);
Assert.Null(held.Self);
Assert.Null(held.Other);
}
[Fact]
public async Task A_self_proof_for_the_signed_in_account_is_held_and_the_browser_goes_back_with_a_closed_code()
{

View file

@ -196,4 +196,21 @@ public sealed class MergeProofsTests
Assert.DoesNotContain(_selfAccount, printed);
Assert.DoesNotContain(_otherAccount, printed);
}
/// <summary>Actors with anything in memory: pruning drops a side with no proof and an actor with no side.</summary>
private int HeldProofs() =>
((System.Collections.IDictionary)typeof(MergeProofs).GetField("_held", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)!.GetValue(_proofs)!)
.Count;
[Fact]
public void An_expired_bearer_is_dropped_by_any_later_access_even_one_for_another_actor()
{
PutSelf("discord", Login("discord"));
Assert.Equal(1, HeldProofs());
_clock.Advance(MergeProofs.ProofLifetime + TimeSpan.FromSeconds(1));
_proofs.Clear(Guid.NewGuid());
Assert.Equal(0, HeldProofs());
}
}

View file

@ -85,6 +85,42 @@ public sealed class MergeStatusTests
Assert.Equal(1, Notices(await Html(w, w.SurvivorSession, "/Membership/Status")));
}
[Fact]
public async Task Past_the_grace_and_before_the_sweeper_there_is_no_notice_and_no_undo_offer()
{
var w = await Committed();
using var rig = w.Rig;
var inside = await Html(w, w.SurvivorSession);
Assert.Equal(1, Notices(inside));
Assert.Contains("To undo, sign in again", inside);
rig.Clock.Advance(AccountMergeLedger.Grace + TimeSpan.FromHours(1));
Assert.Equal(AccountMergeState.Committed, (await rig.MergeAsync(w.MergeId)).State);
var late = await Html(w, w.SurvivorSession);
Assert.Equal(0, Notices(late));
Assert.DoesNotContain("To undo, sign in again", late);
Assert.DoesNotContain("Undo this merge", late);
Assert.DoesNotContain("ProveForUndo", late);
Assert.DoesNotContain("can still be undone", late);
}
[Fact]
public async Task Once_the_undo_is_requested_no_page_says_the_merge_can_still_be_undone()
{
var w = await Committed();
using var rig = w.Rig;
await Edit(w, x => x.State = AccountMergeState.UndoRequested);
var page = await Html(w, w.SurvivorSession);
Assert.Equal(1, Notices(page));
Assert.DoesNotContain("can still be undone", page);
Assert.DoesNotContain("can still undo", page);
Assert.Contains("Undo requested", page);
Assert.DoesNotContain("To undo, sign in again", page);
}
[Fact]
public async Task A_strong_proof_merge_says_so_in_its_own_words()
{