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 and who holds a key are both 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(); /// /// A push channel that never pushes. /// /// /// Not NotSupportedException like its neighbours: the background synchronisation loop reads /// this on every wait, so a layout test that opened a screen would throw from a timer thread rather /// than draw anything. Waiting for ever is the honest stand-in — an offline layout test has no /// server to be pushed from. /// public IVaultEventStream Events => IdleVaultEventStream.Instance; /// 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 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(); /// /// 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 DeleteVaultAsync(Guid vaultId, 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 Task RekeyVaultAsync( Guid vaultId, RekeyVaultRequest request, CancellationToken cancellationToken) => throw new NotSupportedException(); /// public void Dispose() { // Nothing held. } }