using DodoSSH.Client.Api; using DodoSSH.Client.Auth; using DodoSSH.Client.Session; using DodoSSH.Client.Sync; using DodoSSH.Contracts; namespace DodoSSH.Client.App.Layout.Tests; /// /// The least server a VaultsViewModel needs in order to be laid out with something in it. /// /// /// /// The vaults screen draws its list from the session and everything under it from the server: who is in a /// vault, who has been invited, and who holds a key are all read on open, and the suite's /// FakeAccountServer implements and nothing else. Rather than teach that /// fake five more interfaces for one screen, this serves fixed rows and refuses everything a layout test /// has no business calling. /// /// /// It does answer , unlike the other writes, because that is how the /// suite gets a shared vault into the session at all — an offline layout test has no other way to reach /// the state this screen exists to draw. /// /// /// The rows are deliberately the long ones. A layout suite that measured "Bob" in a column sized /// for an email address would certify a shape no real team produces — so the names, addresses and status /// sentences here are at or near the length the interface can really be handed, which is what makes an /// overflow show up at the minimum window rather than on somebody's screen. /// /// internal sealed class StubTeamServer : IVaultServer, ITeamApi, IVaultGrantApi { private static readonly Guid OwnerId = Guid.CreateVersion7(); private static readonly Guid ColleagueId = Guid.CreateVersion7(); private static readonly Guid TeamId = Guid.CreateVersion7(); private static readonly Guid VaultId = Guid.CreateVersion7(); /// public Uri ServerUrl { get; } = new("https://dodossh.example"); /// public ITeamApi Teams => this; /// public IVaultGrantApi Grants => this; /// public IAccountApi Account => throw new NotSupportedException(); /// public ISyncApi Sync => throw new NotSupportedException(); /// public IDirectoryApi Directory => throw new NotSupportedException(); /// public IKeyBindingAuthorizer KeyBinding => throw new NotSupportedException(); /// public SyncOptions SyncOptions => new(); /// public string? RefreshToken => null; /// The vault whose key holders are listed, so a test can select it. internal static Guid TeamVaultId => VaultId; /// The membership list behind that vault, so a test can create it in the session. internal static Guid SharedTeamId => TeamId; /// public Task> ListTeamsAsync(CancellationToken cancellationToken) => Task.FromResult>( [ new TeamSummary( TeamId, "Platform Engineering", "platform-engineering", "Everything that runs the estate.", TeamMemberRole.Owner, MemberCount: 2, VaultCount: 1, DateTimeOffset.UnixEpoch), ]); /// public Task> ListTeamMembersAsync( Guid teamId, CancellationToken cancellationToken) => Task.FromResult>( [ new TeamMemberSummary( OwnerId, "alexandra.hollingsworth@dodotech.example", "Alexandra Hollingsworth", TeamMemberRole.Owner, TeamMemberStatus.Active, IsEnrolled: true, DateTimeOffset.UnixEpoch, DateTimeOffset.UnixEpoch), // The unenrolled case on purpose: its key-state phrase is the longest the column ever // carries, and it is the row that decides whether that column is wide enough. new TeamMemberSummary( ColleagueId, "bartholomew.fotheringay@dodotech.example", "Bartholomew Fotheringay", TeamMemberRole.Member, TeamMemberStatus.Active, IsEnrolled: false, DateTimeOffset.UnixEpoch, LastActiveAt: null), ]); /// public Task> ListTeamInvitationsAsync( Guid teamId, CancellationToken cancellationToken) => Task.FromResult>( [ // Pending, because its sentence is the long one — it has to carry the whole mechanism, // since nothing was sent and there is nothing else on the screen that could say so. new TeamInvitationSummary( Guid.CreateVersion7(), "wilhelmina.ashworth-blake@dodotech.example", TeamMemberRole.Admin, TeamInvitationState.Pending, OwnerId, DateTimeOffset.UnixEpoch, DateTimeOffset.UnixEpoch.AddDays(14), AcceptedAt: null), ]); /// public Task ListVaultGrantsAsync( Guid vaultId, CancellationToken cancellationToken) => Task.FromResult(new VaultGrantsResponse( vaultId, KeyGeneration: 2, RekeyRequired: true, Grants: [ new VaultGrantSummary( OwnerId, "alexandra.hollingsworth@dodotech.example", "Alexandra Hollingsworth", KeyGeneration: 2, VaultGrantState.Active, OwnerId, DateTimeOffset.UnixEpoch, RevokedAt: null), // A generation behind, so the "stale" phrasing is the one being measured rather than // the two-word happy case. new VaultGrantSummary( ColleagueId, "bartholomew.fotheringay@dodotech.example", "Bartholomew Fotheringay", KeyGeneration: 1, VaultGrantState.Active, OwnerId, DateTimeOffset.UnixEpoch, RevokedAt: null), ])); /// public Task CreateTeamAsync( CreateTeamRequest request, CancellationToken cancellationToken) => throw new NotSupportedException(); /// public Task UpdateTeamAsync( Guid teamId, UpdateTeamRequest request, CancellationToken cancellationToken) => throw new NotSupportedException(); /// public Task ArchiveTeamAsync(Guid teamId, CancellationToken cancellationToken) => throw new NotSupportedException(); /// public Task TransferTeamOwnershipAsync( Guid teamId, TransferTeamOwnershipRequest request, CancellationToken cancellationToken) => throw new NotSupportedException(); /// public Task AddTeamMemberAsync( Guid teamId, AddTeamMemberRequest request, CancellationToken cancellationToken) => throw new NotSupportedException(); /// public Task ChangeTeamMemberRoleAsync( Guid teamId, Guid userId, ChangeTeamMemberRoleRequest request, CancellationToken cancellationToken) => throw new NotSupportedException(); /// public Task RemoveTeamMemberAsync( Guid teamId, Guid userId, CancellationToken cancellationToken) => throw new NotSupportedException(); /// public Task CreateTeamInvitationAsync( Guid teamId, CreateTeamInvitationRequest request, CancellationToken cancellationToken) => throw new NotSupportedException(); /// public Task RevokeTeamInvitationAsync( Guid teamId, Guid invitationId, CancellationToken cancellationToken) => throw new NotSupportedException(); /// /// Accepts the vault, so a layout test can put a shared one into the session it is drawing. /// /// /// The client's own id and wrapped key are echoed back, exactly as the real endpoint answers: the key /// was generated on this machine and the session adopts its own copy, so anything else here would be /// either discarded or a vault nobody could open. /// /// It comes back owing a rekey, which is not decoration: that is the longer of the two lines a vault row /// can carry, and this suite exists to measure the long one. /// /// public Task CreateTeamVaultAsync( Guid teamId, CreateTeamVaultRequest request, CancellationToken cancellationToken) => Task.FromResult(new VaultSummary( request.VaultId, request.Name, IsPersonal: false, TeamId: teamId, KeyGeneration: 1, Permissions: 31, request.WrappedVaultKey, RekeyRequired: true)); /// public Task RenameVaultAsync( Guid vaultId, UpdateVaultRequest request, CancellationToken cancellationToken) => throw new NotSupportedException(); /// public Task IssueVaultGrantAsync( Guid vaultId, IssueVaultGrantRequest request, CancellationToken cancellationToken) => throw new NotSupportedException(); /// public Task RevokeVaultGrantAsync( Guid vaultId, Guid userId, CancellationToken cancellationToken) => throw new NotSupportedException(); /// public void Dispose() { // Nothing held. } }