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 Task RekeyVaultAsync(
Guid vaultId,
RekeyVaultRequest request,
CancellationToken cancellationToken) => throw new NotSupportedException();
///
public void Dispose()
{
// Nothing held.
}
}