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 TeamsViewModel needs in order to be laid out with something in it. /// /// /// /// The teams screen is the one screen in this suite whose content cannot come from an unlocked vault, /// because none of it is vault content: a team, its members, its invitations and who holds a key to a /// vault are all read from the server 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. /// /// /// 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; /// 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(); /// public Task CreateTeamVaultAsync( Guid teamId, CreateTeamVaultRequest 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. } }