diff --git a/src/Tgstation.Server.Api/Models/EntityId.cs b/src/Tgstation.Server.Api/Models/EntityId.cs
index bf0319ae6e..a33d3f294c 100644
--- a/src/Tgstation.Server.Api/Models/EntityId.cs
+++ b/src/Tgstation.Server.Api/Models/EntityId.cs
@@ -8,6 +8,7 @@ namespace Tgstation.Server.Api.Models
///
/// The ID of the entity.
///
+ [RequestOptions(FieldPresence.Required)]
[RequestOptions(FieldPresence.Ignored, PutOnly = true)]
public virtual long? Id { get; set; }
}
diff --git a/src/Tgstation.Server.Api/Models/ErrorCode.cs b/src/Tgstation.Server.Api/Models/ErrorCode.cs
index 830fe9135c..2e219e12de 100644
--- a/src/Tgstation.Server.Api/Models/ErrorCode.cs
+++ b/src/Tgstation.Server.Api/Models/ErrorCode.cs
@@ -178,7 +178,7 @@ namespace Tgstation.Server.Api.Models
ConfigurationDirectoryNotEmpty,
///
- /// Tried to clone a repository with a missing property.
+ /// Tried to clone a repository with a missing property.
///
[Obsolete("Deprecated")]
[Description("Cannot clone repository with missing origin field!")]
diff --git a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs
index 1776727b18..425d63eec2 100644
--- a/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs
+++ b/src/Tgstation.Server.Api/Models/Internal/CompileJob.cs
@@ -7,7 +7,7 @@ namespace Tgstation.Server.Api.Models.Internal
///
/// Represents a deployment run.
///
- public class CompileJob : EntityId
+ public abstract class CompileJob : EntityId
{
///
/// The .dme file used for compilation
@@ -19,7 +19,6 @@ namespace Tgstation.Server.Api.Models.Internal
/// Textual output of DM
///
[Required]
- [ResponseOptions]
public string? Output { get; set; }
///
@@ -29,8 +28,9 @@ namespace Tgstation.Server.Api.Models.Internal
public Guid? DirectoryName { get; set; }
///
- /// The minimum required to run the 's output
+ /// The minimum required to run the 's output.
///
+ [ResponseOptions]
public DreamDaemonSecurity? MinimumSecurityLevel { get; set; }
///
diff --git a/src/Tgstation.Server.Api/Models/Internal/InstancePermissionSet.cs b/src/Tgstation.Server.Api/Models/Internal/InstancePermissionSet.cs
index d41063b01a..ff2c24fa4a 100644
--- a/src/Tgstation.Server.Api/Models/Internal/InstancePermissionSet.cs
+++ b/src/Tgstation.Server.Api/Models/Internal/InstancePermissionSet.cs
@@ -11,6 +11,7 @@ namespace Tgstation.Server.Api.Models.Internal
///
/// The of the the belongs to
///
+ [RequestOptions(FieldPresence.Required)]
public long PermissionSetId { get; set; }
///
diff --git a/src/Tgstation.Server.Api/Models/Internal/RepositoryApiBase.cs b/src/Tgstation.Server.Api/Models/Internal/RepositoryApiBase.cs
index de8fe2997a..2a52068888 100644
--- a/src/Tgstation.Server.Api/Models/Internal/RepositoryApiBase.cs
+++ b/src/Tgstation.Server.Api/Models/Internal/RepositoryApiBase.cs
@@ -11,6 +11,7 @@ namespace Tgstation.Server.Api.Models.Internal
/// The branch or tag HEAD points to.
///
[StringLength(Limits.MaximumStringLength)]
+ [ResponseOptions]
public string? Reference { get; set; }
}
}
diff --git a/src/Tgstation.Server.Api/Models/Internal/UserApiBase.cs b/src/Tgstation.Server.Api/Models/Internal/UserApiBase.cs
index c2455905cd..5f988fd475 100644
--- a/src/Tgstation.Server.Api/Models/Internal/UserApiBase.cs
+++ b/src/Tgstation.Server.Api/Models/Internal/UserApiBase.cs
@@ -13,6 +13,7 @@ namespace Tgstation.Server.Api.Models.Internal
///
/// The directly associated with the .
///
+ [ResponseOptions]
public PermissionSet? PermissionSet { get; set; }
///
diff --git a/src/Tgstation.Server.Api/Models/Internal/UserModelBase.cs b/src/Tgstation.Server.Api/Models/Internal/UserModelBase.cs
index 053913fd39..f30e5f84d3 100644
--- a/src/Tgstation.Server.Api/Models/Internal/UserModelBase.cs
+++ b/src/Tgstation.Server.Api/Models/Internal/UserModelBase.cs
@@ -6,13 +6,12 @@ namespace Tgstation.Server.Api.Models.Internal
///
/// Represents a server user.
///
- public abstract class UserModelBase : NamedEntity
+ public abstract class UserModelBase : UserName
{
///
/// If the is enabled since users cannot be deleted. System users cannot be disabled
///
[Required]
- [ResponseOptions]
public bool? Enabled { get; set; }
///
@@ -20,7 +19,6 @@ namespace Tgstation.Server.Api.Models.Internal
///
[Required]
[RequestOptions(FieldPresence.Ignored)]
- [ResponseOptions]
public DateTimeOffset? CreatedAt { get; set; }
///
diff --git a/src/Tgstation.Server.Api/Models/NamedEntity.cs b/src/Tgstation.Server.Api/Models/NamedEntity.cs
index 59d0954ba6..42dc14d95c 100644
--- a/src/Tgstation.Server.Api/Models/NamedEntity.cs
+++ b/src/Tgstation.Server.Api/Models/NamedEntity.cs
@@ -5,15 +5,14 @@ namespace Tgstation.Server.Api.Models
///
/// Base class for named entities.
///
- public class NamedEntity : EntityId
+ public abstract class NamedEntity : EntityId
{
///
/// The name of the entity represented by the .
///
[Required]
- [ResponseOptions]
[RequestOptions(FieldPresence.Required, PutOnly = true)]
[StringLength(Limits.MaximumIndexableStringLength, MinimumLength = 1)]
- public string? Name { get; set; }
+ public virtual string? Name { get; set; }
}
}
diff --git a/src/Tgstation.Server.Api/Models/Request/ChatBotCreateRequest.cs b/src/Tgstation.Server.Api/Models/Request/ChatBotCreateRequest.cs
index 632698bc1f..c40979b9b2 100644
--- a/src/Tgstation.Server.Api/Models/Request/ChatBotCreateRequest.cs
+++ b/src/Tgstation.Server.Api/Models/Request/ChatBotCreateRequest.cs
@@ -1,9 +1,11 @@
+using Tgstation.Server.Api.Models.Internal;
+
namespace Tgstation.Server.Api.Models
{
///
/// Represents a request to update a chat bot.
///
- public sealed class ChatBotCreateRequest : ChatBotUpdateRequest
+ public sealed class ChatBotCreateRequest : ChatBotApiBase
{
}
}
diff --git a/src/Tgstation.Server.Api/Models/Request/InstanceCreateRequest.cs b/src/Tgstation.Server.Api/Models/Request/InstanceCreateRequest.cs
index 08cea8e22a..a04a9e5e61 100644
--- a/src/Tgstation.Server.Api/Models/Request/InstanceCreateRequest.cs
+++ b/src/Tgstation.Server.Api/Models/Request/InstanceCreateRequest.cs
@@ -3,7 +3,7 @@ namespace Tgstation.Server.Api.Models
///
/// A request to create an .
///
- public sealed class InstanceCreateRequest : InstanceUpdateRequest
+ public sealed class InstanceCreateRequest : Instance
{
}
}
diff --git a/src/Tgstation.Server.Api/Models/Request/RepositoryCloneRequest.cs b/src/Tgstation.Server.Api/Models/Request/RepositoryCreateRequest.cs
similarity index 87%
rename from src/Tgstation.Server.Api/Models/Request/RepositoryCloneRequest.cs
rename to src/Tgstation.Server.Api/Models/Request/RepositoryCreateRequest.cs
index 919a41e237..acae78657d 100644
--- a/src/Tgstation.Server.Api/Models/Request/RepositoryCloneRequest.cs
+++ b/src/Tgstation.Server.Api/Models/Request/RepositoryCreateRequest.cs
@@ -6,7 +6,7 @@ namespace Tgstation.Server.Api.Models
///
/// Represents a request to clone the repository.
///
- public sealed class RepositoryCloneRequest : RepositoryApiBase
+ public sealed class RepositoryCreateRequest : RepositoryApiBase
{
///
/// The origin URL to clone.
diff --git a/src/Tgstation.Server.Api/Models/RequestOptionsAttribute.cs b/src/Tgstation.Server.Api/Models/RequestOptionsAttribute.cs
index 3bdd990192..26184bd666 100644
--- a/src/Tgstation.Server.Api/Models/RequestOptionsAttribute.cs
+++ b/src/Tgstation.Server.Api/Models/RequestOptionsAttribute.cs
@@ -5,7 +5,7 @@ namespace Tgstation.Server.Api.Models
///
/// Indicates the for fields in models.
///
- [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
+ [AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = false)]
public sealed class RequestOptionsAttribute : Attribute
{
///
diff --git a/src/Tgstation.Server.Api/Models/Response/ConfigurationFileResponse.cs b/src/Tgstation.Server.Api/Models/Response/ConfigurationFileResponse.cs
index 071dbd9d81..bbc9a24b38 100644
--- a/src/Tgstation.Server.Api/Models/Response/ConfigurationFileResponse.cs
+++ b/src/Tgstation.Server.Api/Models/Response/ConfigurationFileResponse.cs
@@ -9,6 +9,7 @@ namespace Tgstation.Server.Api.Models
public string? Path { get; set; }
///
+ [ResponseOptions]
public string? LastReadHash { get; set; }
///
diff --git a/src/Tgstation.Server.Api/Models/Response/DreamDaemonResponse.cs b/src/Tgstation.Server.Api/Models/Response/DreamDaemonResponse.cs
index 37548a09d7..acf6865dbc 100644
--- a/src/Tgstation.Server.Api/Models/Response/DreamDaemonResponse.cs
+++ b/src/Tgstation.Server.Api/Models/Response/DreamDaemonResponse.cs
@@ -24,6 +24,7 @@ namespace Tgstation.Server.Api.Models
/// The current .
///
[EnumDataType(typeof(WatchdogStatus))]
+ [ResponseOptions]
public WatchdogStatus? Status { get; set; }
///
diff --git a/src/Tgstation.Server.Api/Models/Response/JobResponse.cs b/src/Tgstation.Server.Api/Models/Response/JobResponse.cs
index dc67d3c80c..13de9d5fef 100644
--- a/src/Tgstation.Server.Api/Models/Response/JobResponse.cs
+++ b/src/Tgstation.Server.Api/Models/Response/JobResponse.cs
@@ -8,13 +8,13 @@ namespace Tgstation.Server.Api.Models
///
/// The that started the job
///
- public UserResponse? StartedBy { get; set; }
+ public UserName? StartedBy { get; set; }
///
/// The that cancelled the job
///
[ResponseOptions]
- public UserResponse? CancelledBy { get; set; }
+ public UserName? CancelledBy { get; set; }
///
/// Optional progress between 0 and 100 inclusive
diff --git a/src/Tgstation.Server.Api/Models/Response/UserGroupResponse.cs b/src/Tgstation.Server.Api/Models/Response/UserGroupResponse.cs
index 151ed68f95..9a897f8a00 100644
--- a/src/Tgstation.Server.Api/Models/Response/UserGroupResponse.cs
+++ b/src/Tgstation.Server.Api/Models/Response/UserGroupResponse.cs
@@ -9,6 +9,6 @@ namespace Tgstation.Server.Api.Models
///
/// The s the has.
///
- public ICollection? Users { get; set; }
+ public ICollection? Users { get; set; }
}
}
diff --git a/src/Tgstation.Server.Api/Models/Response/UserResponse.cs b/src/Tgstation.Server.Api/Models/Response/UserResponse.cs
index 895b498c45..01feceff92 100644
--- a/src/Tgstation.Server.Api/Models/Response/UserResponse.cs
+++ b/src/Tgstation.Server.Api/Models/Response/UserResponse.cs
@@ -10,6 +10,6 @@ namespace Tgstation.Server.Api.Models
/// The who created this
///
[Required]
- public NamedEntity? CreatedBy { get; set; }
+ public UserName? CreatedBy { get; set; }
}
}
diff --git a/src/Tgstation.Server.Api/Models/ResponseOptionsAttribute.cs b/src/Tgstation.Server.Api/Models/ResponseOptionsAttribute.cs
index f7e86ac9af..a8c42e0f4c 100644
--- a/src/Tgstation.Server.Api/Models/ResponseOptionsAttribute.cs
+++ b/src/Tgstation.Server.Api/Models/ResponseOptionsAttribute.cs
@@ -3,7 +3,7 @@ using System;
namespace Tgstation.Server.Api.Models
{
///
- /// Indicates API fields that may be null on response.
+ /// Indicates the response of API fields. Changes it from to by default.
///
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class ResponseOptionsAttribute : Attribute
diff --git a/src/Tgstation.Server.Api/Models/TestMerge.cs b/src/Tgstation.Server.Api/Models/TestMerge.cs
index 7d8623f1b4..5ab802f96f 100644
--- a/src/Tgstation.Server.Api/Models/TestMerge.cs
+++ b/src/Tgstation.Server.Api/Models/TestMerge.cs
@@ -8,6 +8,6 @@ namespace Tgstation.Server.Api.Models
///
/// The of the user who created the .
///
- public NamedEntity? MergedBy { get; set; }
+ public UserName? MergedBy { get; set; }
}
}
diff --git a/src/Tgstation.Server.Api/Models/UserName.cs b/src/Tgstation.Server.Api/Models/UserName.cs
new file mode 100644
index 0000000000..8044489a2d
--- /dev/null
+++ b/src/Tgstation.Server.Api/Models/UserName.cs
@@ -0,0 +1,33 @@
+namespace Tgstation.Server.Api.Models
+{
+ ///
+ /// Base class for user names.
+ ///
+ public class UserName : NamedEntity
+ {
+ ///
+ [RequestOptions(FieldPresence.Optional)]
+ public override string? Name
+ {
+ get => base.Name;
+ set => base.Name = value;
+ }
+
+ ///
+ /// Create a copy of the as a given .
+ ///
+ /// The child of to create.
+ /// A new copied from .
+ protected virtual TResultType CreateUserName() where TResultType : UserName, new() => new TResultType
+ {
+ Id = Id,
+ Name = Name
+ };
+
+ ///
+ /// Create a copy of the .
+ ///
+ /// A new copied from .
+ public UserName CreateUserName() => CreateUserName();
+ }
+}
diff --git a/src/Tgstation.Server.Client/Components/IRepositoryClient.cs b/src/Tgstation.Server.Client/Components/IRepositoryClient.cs
index 3a8d2274d1..7d1b25911b 100644
--- a/src/Tgstation.Server.Client/Components/IRepositoryClient.cs
+++ b/src/Tgstation.Server.Client/Components/IRepositoryClient.cs
@@ -27,10 +27,10 @@ namespace Tgstation.Server.Client.Components
///
/// Clones a
///
- /// The .
+ /// The .
/// The for the operation
/// A resulting in the /
- Task Clone(RepositoryCloneRequest repository, CancellationToken cancellationToken);
+ Task Clone(RepositoryCreateRequest repository, CancellationToken cancellationToken);
///
/// Deletes the repository.
diff --git a/src/Tgstation.Server.Client/Components/RepositoryClient.cs b/src/Tgstation.Server.Client/Components/RepositoryClient.cs
index c62312298c..25c807d2c9 100644
--- a/src/Tgstation.Server.Client/Components/RepositoryClient.cs
+++ b/src/Tgstation.Server.Client/Components/RepositoryClient.cs
@@ -31,7 +31,7 @@ namespace Tgstation.Server.Client.Components
}
///
- public Task Clone(RepositoryCloneRequest repository, CancellationToken cancellationToken) => apiClient.Create(Routes.Repository, repository, instance.Id!.Value, cancellationToken);
+ public Task Clone(RepositoryCreateRequest repository, CancellationToken cancellationToken) => apiClient.Create(Routes.Repository, repository, instance.Id!.Value, cancellationToken);
///
public Task Delete(CancellationToken cancellationToken) => apiClient.Delete(Routes.Repository, instance.Id!.Value, cancellationToken);
diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs
index 4b60ad50fb..a1dcb0b409 100644
--- a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs
@@ -80,7 +80,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
IJobManager jobManager,
IAssemblyInformationProvider assemblyInformationProvider,
ILogger logger,
- Models.ChatBot chatBot)
+ ChatBot chatBot)
: base(jobManager, logger, chatBot)
{
this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
diff --git a/src/Tgstation.Server.Host/Controllers/BridgeController.cs b/src/Tgstation.Server.Host/Controllers/BridgeController.cs
index 8bb613ff4d..d9397874b4 100644
--- a/src/Tgstation.Server.Host/Controllers/BridgeController.cs
+++ b/src/Tgstation.Server.Host/Controllers/BridgeController.cs
@@ -19,6 +19,7 @@ namespace Tgstation.Server.Host.Controllers
[Route("/Bridge")]
[Produces(MediaTypeNames.Application.Json)]
[ApiController]
+ [ApiExplorerSettings(IgnoreApi = true)]
public class BridgeController : Controller
{
///
diff --git a/src/Tgstation.Server.Host/Controllers/ChatController.cs b/src/Tgstation.Server.Host/Controllers/ChatController.cs
index 0c981f2395..18501e9b36 100644
--- a/src/Tgstation.Server.Host/Controllers/ChatController.cs
+++ b/src/Tgstation.Server.Host/Controllers/ChatController.cs
@@ -11,6 +11,7 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Tgstation.Server.Api;
using Tgstation.Server.Api.Models;
+using Tgstation.Server.Api.Models.Internal;
using Tgstation.Server.Api.Rights;
using Tgstation.Server.Host.Components;
using Tgstation.Server.Host.Database;
@@ -65,14 +66,14 @@ namespace Tgstation.Server.Host.Controllers
///
/// Create a new chat bot .
///
- /// The .
+ /// The .
/// The for the operation.
/// A resulting in the for the operation.
/// Created successfully.
[HttpPut]
[TgsAuthorize(ChatBotRights.Create)]
[ProducesResponseType(typeof(ChatBotResponse), 201)]
- public async Task Create([FromBody] ChatBotUpdateRequest model, CancellationToken cancellationToken)
+ public async Task Create([FromBody] ChatBotCreateRequest model, CancellationToken cancellationToken)
{
if (model == null)
throw new ArgumentNullException(nameof(model));
@@ -278,7 +279,7 @@ namespace Tgstation.Server.Host.Controllers
bool anySettingsModified = false;
- bool CheckModified(Expression> expression, ChatBotRights requiredRight)
+ bool CheckModified(Expression> expression, ChatBotRights requiredRight)
{
var memberSelectorExpression = (MemberExpression)expression.Body;
var property = (PropertyInfo)memberSelectorExpression.Member;
@@ -350,10 +351,10 @@ namespace Tgstation.Server.Host.Controllers
///
/// Perform some basic validation of a given .
///
- /// The to validate.
+ /// The to validate.
/// If the is being created.
/// An to respond with or .
- private IActionResult StandardModelChecks(ChatBotUpdateRequest model, bool forCreation)
+ private IActionResult StandardModelChecks(ChatBotApiBase model, bool forCreation)
{
if (model.ReconnectionInterval == 0)
throw new InvalidOperationException("RecconnectionInterval cannot be zero!");
diff --git a/src/Tgstation.Server.Host/Controllers/ControlPanelController.cs b/src/Tgstation.Server.Host/Controllers/ControlPanelController.cs
index 71de870e2c..b632f7e690 100644
--- a/src/Tgstation.Server.Host/Controllers/ControlPanelController.cs
+++ b/src/Tgstation.Server.Host/Controllers/ControlPanelController.cs
@@ -17,6 +17,7 @@ namespace Tgstation.Server.Host.Controllers
/// Controller for the web control panel.
///
[Route(Application.ControlPanelRoute)]
+ [ApiExplorerSettings(IgnoreApi = true)]
public class ControlPanelController : Controller
{
///
diff --git a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs
index a2e30f82dc..08c95e2e07 100644
--- a/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs
+++ b/src/Tgstation.Server.Host/Controllers/DreamDaemonController.cs
@@ -67,17 +67,17 @@ namespace Tgstation.Server.Host.Controllers
///
/// The for the operation.
/// A resulting in the of the operation.
- /// to launch the watchdog started successfully.
+ /// to launch the watchdog started successfully.
[HttpPut]
[TgsAuthorize(DreamDaemonRights.Start)]
- [ProducesResponseType(typeof(Api.Models.JobResponse), 202)]
+ [ProducesResponseType(typeof(JobResponse), 202)]
public Task Create(CancellationToken cancellationToken)
=> WithComponentInstance(async instance =>
{
if (instance.Watchdog.Status != WatchdogStatus.Offline)
return Conflict(new ErrorMessageResponse(ErrorCode.WatchdogRunning));
- var job = new Models.Job
+ var job = new Job
{
Description = "Launch DreamDaemon",
CancelRight = (ulong)DreamDaemonRights.Shutdown,
@@ -298,18 +298,18 @@ namespace Tgstation.Server.Host.Controllers
#pragma warning restore CA1502
///
- /// Creates a to restart the Watchdog. It will not start if it wasn't already running.
+ /// Creates a to restart the Watchdog. It will not start if it wasn't already running.
///
/// The for the operation
/// A resulting in the of the request
- /// Restart started successfully.
+ /// Restart started successfully.
[HttpPatch]
[TgsAuthorize(DreamDaemonRights.Restart)]
- [ProducesResponseType(typeof(Api.Models.JobResponse), 202)]
+ [ProducesResponseType(typeof(JobResponse), 202)]
public Task Restart(CancellationToken cancellationToken)
=> WithComponentInstance(async instance =>
{
- var job = new Models.Job
+ var job = new Job
{
Instance = Instance,
CancelRightsType = RightsType.DreamDaemon,
@@ -332,18 +332,18 @@ namespace Tgstation.Server.Host.Controllers
});
///
- /// Creates a to generate a DreamDaemon process dump.
+ /// Creates a to generate a DreamDaemon process dump.
///
/// The for the operation
/// A resulting in the of the request
- /// Dump started successfully.
+ /// Dump started successfully.
[HttpPatch(Routes.Diagnostics)]
[TgsAuthorize(DreamDaemonRights.CreateDump)]
- [ProducesResponseType(typeof(Api.Models.JobResponse), 202)]
+ [ProducesResponseType(typeof(JobResponse), 202)]
public Task CreateDump(CancellationToken cancellationToken)
=> WithComponentInstance(async instance =>
{
- var job = new Models.Job
+ var job = new Job
{
Instance = Instance,
CancelRightsType = RightsType.DreamDaemon,
diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs
index c420f5dd9c..05721a4957 100644
--- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs
+++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs
@@ -116,7 +116,7 @@ namespace Tgstation.Server.Host.Controllers
swarmConfiguration = swarmConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(swarmConfigurationOptions));
}
- async Task CreateDefaultInstance(InstanceUpdateRequest initialSettings, CancellationToken cancellationToken)
+ async Task CreateDefaultInstance(InstanceCreateRequest initialSettings, CancellationToken cancellationToken)
{
var ddPort = await portAllocator.GetAvailablePort(1, false, cancellationToken).ConfigureAwait(false);
if (!ddPort.HasValue)
@@ -222,7 +222,7 @@ namespace Tgstation.Server.Host.Controllers
///
/// Create or attach an .
///
- /// The settings.
+ /// The .
/// The for the operation.
/// A resulting in the of the request.
/// Instance attached successfully.
@@ -231,7 +231,7 @@ namespace Tgstation.Server.Host.Controllers
[TgsAuthorize(InstanceManagerRights.Create)]
[ProducesResponseType(typeof(InstanceResponse), 200)]
[ProducesResponseType(typeof(InstanceResponse), 201)]
- public async Task Create([FromBody] InstanceUpdateRequest model, CancellationToken cancellationToken)
+ public async Task Create([FromBody] InstanceCreateRequest model, CancellationToken cancellationToken)
{
if (model == null)
throw new ArgumentNullException(nameof(model));
diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs
index 5360ab1d2b..9108bd3aa8 100644
--- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs
+++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs
@@ -130,7 +130,7 @@ namespace Tgstation.Server.Host.Controllers
///
/// Begin cloning the repository if it doesn't exist.
///
- /// The .
+ /// The .
/// The for the operation.
/// A resulting in the of the request.
/// The repository was created successfully and the to clone it has begun.
@@ -139,7 +139,7 @@ namespace Tgstation.Server.Host.Controllers
[TgsAuthorize(RepositoryRights.SetOrigin)]
[ProducesResponseType(typeof(RepositoryResponse), 201)]
[ProducesResponseType(typeof(ErrorMessageResponse), 410)]
- public async Task Create([FromBody] RepositoryCloneRequest model, CancellationToken cancellationToken)
+ public async Task Create([FromBody] RepositoryCreateRequest model, CancellationToken cancellationToken)
{
if (model == null)
throw new ArgumentNullException(nameof(model));
diff --git a/src/Tgstation.Server.Host/Controllers/SwarmController.cs b/src/Tgstation.Server.Host/Controllers/SwarmController.cs
index 473df47a38..18659b6972 100644
--- a/src/Tgstation.Server.Host/Controllers/SwarmController.cs
+++ b/src/Tgstation.Server.Host/Controllers/SwarmController.cs
@@ -21,6 +21,7 @@ namespace Tgstation.Server.Host.Controllers
[Route(SwarmConstants.ControllerRoute)]
[Produces(MediaTypeNames.Application.Json)]
[ApiController]
+ [ApiExplorerSettings(IgnoreApi = true)]
public sealed class SwarmController : Controller
{
///
diff --git a/src/Tgstation.Server.Host/Controllers/UserController.cs b/src/Tgstation.Server.Host/Controllers/UserController.cs
index 1dba52295f..50e16681ee 100644
--- a/src/Tgstation.Server.Host/Controllers/UserController.cs
+++ b/src/Tgstation.Server.Host/Controllers/UserController.cs
@@ -66,7 +66,7 @@ namespace Tgstation.Server.Host.Controllers
}
///
- /// Check if a given has a valid specified.
+ /// Check if a given has a valid specified.
///
/// The to check.
/// If this is a new .
@@ -105,7 +105,7 @@ namespace Tgstation.Server.Host.Controllers
///
/// Create a new .
///
- /// The to create.
+ /// The .
/// The for the operation.
/// A resulting in the of the operation.
/// created successfully.
@@ -114,7 +114,7 @@ namespace Tgstation.Server.Host.Controllers
[TgsAuthorize(AdministrationRights.WriteUsers)]
[ProducesResponseType(typeof(UserResponse), 201)]
#pragma warning disable CA1502, CA1506
- public async Task Create([FromBody] UserUpdateRequest model, CancellationToken cancellationToken)
+ public async Task Create([FromBody] UserCreateRequest model, CancellationToken cancellationToken)
{
if (model == null)
throw new ArgumentNullException(nameof(model));
@@ -180,7 +180,7 @@ namespace Tgstation.Server.Host.Controllers
Logger.LogInformation("Created new user {0} ({1})", dbUser.Name, dbUser.Id);
- return Created(dbUser.ToApi(true));
+ return Created(dbUser.ToApi());
}
#pragma warning restore CA1502, CA1506
@@ -191,11 +191,13 @@ namespace Tgstation.Server.Host.Controllers
/// The for the operation.
/// A resulting in the of the operation.
/// updated successfully.
+ /// updated successfully. Not returned due to lack of permissions.
/// Requested does not exist.
/// Requested does not exist.
[HttpPost]
[TgsAuthorize(AdministrationRights.WriteUsers | AdministrationRights.EditOwnPassword | AdministrationRights.EditOwnOAuthConnections)]
[ProducesResponseType(typeof(UserResponse), 200)]
+ [ProducesResponseType(204)]
[ProducesResponseType(typeof(ErrorMessageResponse), 404)]
[ProducesResponseType(typeof(ErrorMessageResponse), 410)]
#pragma warning disable CA1502 // TODO: Decomplexify
@@ -334,14 +336,11 @@ namespace Tgstation.Server.Host.Controllers
Logger.LogInformation("Updated user {0} ({1})", originalUser.Name, originalUser.Id);
// return id only if not a self update and cannot read users
- return Json(
- AuthenticationContext.User.Id == originalUser.Id
- || callerAdministrationRights.HasFlag(AdministrationRights.ReadUsers)
- ? originalUser.ToApi(true)
- : new UserResponse
- {
- Id = originalUser.Id
- });
+ var canReadBack = AuthenticationContext.User.Id == originalUser.Id
+ || callerAdministrationRights.HasFlag(AdministrationRights.ReadUsers);
+ return canReadBack
+ ? Json(originalUser.ToApi())
+ : NoContent();
}
#pragma warning restore CA1506
#pragma warning restore CA1502
@@ -354,7 +353,7 @@ namespace Tgstation.Server.Host.Controllers
[HttpGet]
[TgsAuthorize]
[ProducesResponseType(typeof(UserResponse), 200)]
- public IActionResult Read() => Json(AuthenticationContext.User.ToApi(true));
+ public IActionResult Read() => Json(AuthenticationContext.User.ToApi());
///
/// List all s in the server.
@@ -421,7 +420,7 @@ namespace Tgstation.Server.Host.Controllers
if (user.CanonicalName == Models.User.CanonicalizeName(Models.User.TgsSystemUserName))
return Forbid();
- return Json(user.ToApi(true));
+ return Json(user.ToApi());
}
///
diff --git a/src/Tgstation.Server.Host/Core/SwaggerConfiguration.cs b/src/Tgstation.Server.Host/Core/SwaggerConfiguration.cs
index e1a68eab5e..2f2c9518cc 100644
--- a/src/Tgstation.Server.Host/Core/SwaggerConfiguration.cs
+++ b/src/Tgstation.Server.Host/Core/SwaggerConfiguration.cs
@@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Mime;
+using System.Reflection;
using Tgstation.Server.Api;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Controllers;
@@ -120,12 +121,15 @@ namespace Tgstation.Server.Host.Core
});
}
+ ///
+ /// Generates the OpenAPI schema ID for a given .
+ ///
+ /// The to generate a schema ID for.
+ /// The generated schema ID for .
static string GenerateSchemaId(Type type)
{
- if (type == typeof(NamedEntity))
+ if (type == typeof(UserName))
return "ShallowUserResponse";
- if (type == typeof(Api.Models.Internal.UserGroup))
- return "ShallowUserGroupResponse";
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(PaginatedResponse<>))
return $"Paginated{type.GenericTypeArguments.First().Name}";
@@ -378,24 +382,12 @@ namespace Tgstation.Server.Host.Core
Schema = productHeaderSchema
});
- var pathsToRemove = new List();
- var filteredControllers = new string[]
- {
- nameof(BridgeController),
- nameof(ControlPanelController),
- nameof(SwarmController),
- };
-
+ var allSchemas = context
+ .SchemaRepository
+ .Schemas;
foreach (var path in swaggerDoc.Paths)
foreach (var operation in path.Value.Operations.Select(x => x.Value))
{
- if (filteredControllers.Any(
- x => operation.OperationId.StartsWith(x, StringComparison.Ordinal)))
- {
- pathsToRemove.Add(path.Key);
- continue;
- }
-
operation.Parameters.Insert(0, new OpenApiParameter
{
Reference = new OpenApiReference
@@ -415,12 +407,120 @@ namespace Tgstation.Server.Host.Core
});
}
- foreach (var filteredPath in pathsToRemove)
- swaggerDoc.Paths.Remove(filteredPath);
-
AddDefaultResponses(swaggerDoc);
}
+ ///
+ /// Applies the , , and to of a given .
+ ///
+ /// The root .
+ /// The current .
+ static void ApplyAttributesForRootSchema(OpenApiSchema rootSchema, SchemaFilterContext context)
+ {
+ // tune up the descendants
+ rootSchema.Nullable = false;
+ var rootSchemaId = GenerateSchemaId(context.Type);
+ var rootRequestSchema = rootSchemaId.EndsWith("Request", StringComparison.Ordinal);
+ var rootResponseSchema = rootSchemaId.EndsWith("Response", StringComparison.Ordinal);
+ var isPutRequest = rootRequestSchema && rootSchemaId.EndsWith("CreateRequest", StringComparison.Ordinal);
+
+ Tuple> GetTypeFromKvp(Type currentType, KeyValuePair kvp, IDictionary schemaDictionary)
+ {
+ var propertyInfo = currentType
+ .GetProperties()
+ .Single(x => x.Name.Equals(kvp.Key, StringComparison.OrdinalIgnoreCase));
+
+ return Tuple.Create(
+ propertyInfo,
+ kvp.Key,
+ kvp.Value,
+ schemaDictionary);
+ }
+
+ var subSchemaStack = new Stack>>(
+ rootSchema.Properties.Select(x => GetTypeFromKvp(context.Type, x, rootSchema.Properties)));
+
+ while (subSchemaStack.Count > 0)
+ {
+ var tuple = subSchemaStack.Pop();
+ var subSchema = tuple.Item3;
+
+ if (subSchema.Reference != null)
+ // can't mangle references per request
+ continue;
+
+ var subSchemaPropertyInfo = tuple.Item1;
+
+ if (subSchema.Properties != null
+ && !subSchemaPropertyInfo
+ .PropertyType
+ .GetInterfaces()
+ .Any(x => x == typeof(IEnumerable)))
+ foreach (var kvp in subSchema.Properties)
+ subSchemaStack.Push(GetTypeFromKvp(subSchemaPropertyInfo.PropertyType, kvp, subSchema.Properties));
+
+ var attributes = subSchemaPropertyInfo
+ .GetCustomAttributes();
+ var responsePresence = attributes
+ .OfType()
+ .FirstOrDefault()
+ ?.Presence
+ ?? FieldPresence.Required;
+ var requestOptions = attributes
+ .OfType()
+ .OrderBy(x => x.PutOnly) // Process PUTs last
+ .ToList();
+
+ if (requestOptions.Any() && requestOptions.All(x => x.Presence == FieldPresence.Ignored && !x.PutOnly))
+ subSchema.ReadOnly = true;
+
+ var subSchemaId = tuple.Item2;
+ var subSchemaOwningDictionary = tuple.Item4;
+ if (rootResponseSchema)
+ {
+ subSchema.Nullable = responsePresence == FieldPresence.Optional;
+ if (responsePresence == FieldPresence.Ignored)
+ subSchemaOwningDictionary.Remove(subSchemaId);
+ }
+ else if (rootRequestSchema)
+ {
+ subSchema.Nullable = true;
+ var lastOptionWasIgnored = false;
+ foreach (var requestOption in requestOptions)
+ {
+ var validForThisRequest = !requestOption.PutOnly || isPutRequest;
+ if (!validForThisRequest)
+ continue;
+
+ lastOptionWasIgnored = false;
+ switch (requestOption.Presence)
+ {
+ case FieldPresence.Ignored:
+ lastOptionWasIgnored = true;
+ break;
+ case FieldPresence.Optional:
+ subSchema.Nullable = true;
+ break;
+ case FieldPresence.Required:
+ subSchema.Nullable = false;
+ break;
+ default:
+ throw new InvalidOperationException($"Invalid FieldPresence: {requestOption.Presence}!");
+ }
+ }
+
+ if (lastOptionWasIgnored)
+ subSchemaOwningDictionary.Remove(subSchemaId);
+ }
+ else if (responsePresence == FieldPresence.Required
+ && requestOptions.All(x => x.Presence == FieldPresence.Required && !x.PutOnly))
+ subSchema.Nullable = false;
+
+ // otherwise, we have to assume it's a shared schema
+ // use what Swagger thinks the nullability is by default
+ }
+ }
+
///
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
@@ -432,53 +532,18 @@ namespace Tgstation.Server.Host.Core
// Nothing is required
schema.Required.Clear();
- // Could be nullable type, make sure to get the right one
- Type nonNullableType = context.Type.IsConstructedGenericType
- ? context.Type.GenericTypeArguments.First()
- : context.Type;
-
- if (context.MemberInfo != null)
- {
- var schemaId = GenerateSchemaId(context.MemberInfo.DeclaringType);
- var responseOptions = context.MemberInfo.GetCustomAttributes(typeof(ResponseOptionsAttribute), true).OfType().FirstOrDefault();
- var requestOptions = context.MemberInfo.GetCustomAttributes(typeof(RequestOptionsAttribute), true).OfType().FirstOrDefault();
-
- if (schemaId.EndsWith("Response", StringComparison.Ordinal))
- if (responseOptions?.Presence == FieldPresence.Optional
- || (nonNullableType != context.Type
- && !context.Type.GetInterfaces().Any(x => x == typeof(IEnumerable))))
- schema.Nullable = true;
- else if (responseOptions?.Presence == FieldPresence.Ignored)
- schema.WriteOnly = true;
- else
- schema.Nullable = false;
- else if (requestOptions != null)
- if (schemaId.EndsWith("Request", StringComparison.Ordinal))
- {
- var isPutRequest = schemaId.Contains("Create", StringComparison.OrdinalIgnoreCase);
- if (!(requestOptions.PutOnly && !isPutRequest))
- if (requestOptions.Presence == FieldPresence.Required)
- context.SchemaRepository.Schemas.Remove(schemaId);
- else if (requestOptions.Presence == FieldPresence.Ignored)
- schema.ReadOnly = true;
- }
- else if (responseOptions?.Presence != FieldPresence.Optional && requestOptions.Presence == FieldPresence.Required)
- // if something has request required and no optional response, it's always required
- schema.Nullable = false;
- else
- schema.Nullable = true;
- else
- {
- if (responseOptions?.Presence == FieldPresence.Ignored)
- context.SchemaRepository.Schemas.Remove(schemaId);
- schema.Nullable = true;
- }
- }
+ if (context.MemberInfo == null)
+ ApplyAttributesForRootSchema(schema, context);
if (!schema.Enum?.Any() ?? false)
return;
- OpenApiEnumVarNamesExtension.Apply(schema, nonNullableType);
+ // Could be nullable type, make sure to get the right one
+ Type firstGenericArgumentOrType = context.Type.IsConstructedGenericType
+ ? context.Type.GenericTypeArguments.First()
+ : context.Type;
+
+ OpenApiEnumVarNamesExtension.Apply(schema, firstGenericArgumentOrType);
}
///
diff --git a/src/Tgstation.Server.Host/Models/Job.cs b/src/Tgstation.Server.Host/Models/Job.cs
index e5b22b2e86..ec58dcc866 100644
--- a/src/Tgstation.Server.Host/Models/Job.cs
+++ b/src/Tgstation.Server.Host/Models/Job.cs
@@ -31,13 +31,13 @@ namespace Tgstation.Server.Host.Models
StartedAt = StartedAt,
StoppedAt = StoppedAt,
Cancelled = Cancelled,
- CancelledBy = CancelledBy?.ToApi(false),
+ CancelledBy = CancelledBy?.CreateUserName(),
CancelRight = CancelRight,
CancelRightsType = CancelRightsType,
Description = Description,
ExceptionDetails = ExceptionDetails,
ErrorCode = ErrorCode,
- StartedBy = StartedBy.ToApi(false)
+ StartedBy = StartedBy.CreateUserName()
};
}
}
diff --git a/src/Tgstation.Server.Host/Models/TestMerge.cs b/src/Tgstation.Server.Host/Models/TestMerge.cs
index e1286c6900..0ee060f536 100644
--- a/src/Tgstation.Server.Host/Models/TestMerge.cs
+++ b/src/Tgstation.Server.Host/Models/TestMerge.cs
@@ -37,7 +37,7 @@ namespace Tgstation.Server.Host.Models
TitleAtMerge = TitleAtMerge,
Comment = Comment,
Id = Id,
- MergedBy = MergedBy.ToApi(false),
+ MergedBy = MergedBy.CreateUserName(),
Number = Number,
TargetCommitSha = TargetCommitSha,
Url = Url
diff --git a/src/Tgstation.Server.Host/Models/User.cs b/src/Tgstation.Server.Host/Models/User.cs
index 1af92e0efb..620ec5d748 100644
--- a/src/Tgstation.Server.Host/Models/User.cs
+++ b/src/Tgstation.Server.Host/Models/User.cs
@@ -40,7 +40,7 @@ namespace Tgstation.Server.Host.Models
public PermissionSet PermissionSet { get; set; }
///
- /// The uppercase invariant of
+ /// The uppercase invariant of
///
[Required]
[StringLength(Limits.MaximumIndexableStringLength, MinimumLength = 1)]
@@ -67,43 +67,35 @@ namespace Tgstation.Server.Host.Models
public ICollection OAuthConnections { get; set; }
///
- /// Change a into a .
+ /// Change a into a .
///
- /// The .
+ /// The .
/// The .
public static string CanonicalizeName(string name) => name?.ToUpperInvariant() ?? throw new ArgumentNullException(nameof(name));
///
- /// See
+ /// Generate a from .
///
- /// If we should recurse on
- /// If rights and system identifier should be shown
+ /// If we should recurse on .
/// A new .
- UserResponse ToApi(bool recursive, bool showDetails) => new UserResponse
+ UserResponse CreateUserResponse(bool recursive)
{
- CreatedAt = showDetails ? CreatedAt : null,
- CreatedBy = showDetails && recursive ? CreatedBy?.ToApi(false, false) : null,
- Enabled = showDetails ? Enabled : null,
- Id = Id,
- Name = Name,
- SystemIdentifier = showDetails ? SystemIdentifier : null,
- OAuthConnections = showDetails
- ? OAuthConnections
- ?.Select(x => x.ToApi())
- .ToList()
- : null,
- Group = showDetails ? Group?.ToApi(false) : null,
- PermissionSet = showDetails ? PermissionSet?.ToApi() : null,
- };
+ var result = CreateUserName();
+ if (recursive)
+ result.CreatedBy = CreatedBy?.CreateUserName();
- ///
- /// Convert the to it's API form
- ///
- /// If system identifier, oauth connections, and group/permission set should be shown.
- /// A new
- public UserResponse ToApi(bool showDetails) => ToApi(true, showDetails);
+ result.CreatedAt = CreatedAt;
+ result.Enabled = Enabled;
+ result.SystemIdentifier = SystemIdentifier;
+ result.OAuthConnections = OAuthConnections
+ ?.Select(x => x.ToApi())
+ .ToList();
+ result.Group = Group?.ToApi(false);
+ result.PermissionSet = PermissionSet?.ToApi();
+ return result;
+ }
///
- public UserResponse ToApi() => ToApi(true);
+ public UserResponse ToApi() => CreateUserResponse(true);
}
}
diff --git a/src/Tgstation.Server.Host/Models/UserGroup.cs b/src/Tgstation.Server.Host/Models/UserGroup.cs
index 2bda19ea99..07d89939c3 100644
--- a/src/Tgstation.Server.Host/Models/UserGroup.cs
+++ b/src/Tgstation.Server.Host/Models/UserGroup.cs
@@ -31,10 +31,9 @@ namespace Tgstation.Server.Host.Models
PermissionSet = PermissionSet.ToApi(),
Users = showUsers
? Users
- ?.Select(x => x.ToApi(false))
- .OfType()
+ ?.Select(x => x.CreateUserName())
.ToList()
- ?? new List()
+ ?? new List()
: null,
};
diff --git a/tests/Tgstation.Server.Tests/Instance/RepositoryTest.cs b/tests/Tgstation.Server.Tests/Instance/RepositoryTest.cs
index a03b221cc5..9acf03d40e 100644
--- a/tests/Tgstation.Server.Tests/Instance/RepositoryTest.cs
+++ b/tests/Tgstation.Server.Tests/Instance/RepositoryTest.cs
@@ -45,7 +45,7 @@ namespace Tgstation.Server.Tests.Instance
Assert.IsNull(initalRepo.ActiveJob);
const string Origin = "https://github.com/tgstation/tgstation-server";
- var cloneRequest = new RepositoryCloneRequest
+ var cloneRequest = new RepositoryCreateRequest
{
Origin = new Uri(Origin),
Reference = workingBranch,
diff --git a/tests/Tgstation.Server.Tests/InstanceManagerTest.cs b/tests/Tgstation.Server.Tests/InstanceManagerTest.cs
index ef4e3355d2..a1ec5d7040 100644
--- a/tests/Tgstation.Server.Tests/InstanceManagerTest.cs
+++ b/tests/Tgstation.Server.Tests/InstanceManagerTest.cs
@@ -34,7 +34,7 @@ namespace Tgstation.Server.Tests
ChatBotLimit = 2
}, cancellationToken);
- static InstanceCreateRequest FromResponse(InstanceResponse response) => new InstanceCreateRequest
+ static TRequestType FromResponse(InstanceResponse response) where TRequestType : Api.Models.Instance, new() => new TRequestType
{
Id = response.Id,
Path = response.Path,
@@ -75,7 +75,7 @@ namespace Tgstation.Server.Tests
Name = "NonEmptyTest"
}, cancellationToken).ConfigureAwait(false);
- await Assert.ThrowsExceptionAsync(() => instanceManagerClient.CreateOrAttach(FromResponse(firstTest), cancellationToken)).ConfigureAwait(false);
+ await Assert.ThrowsExceptionAsync(() => instanceManagerClient.CreateOrAttach(FromResponse(firstTest), cancellationToken)).ConfigureAwait(false);
Assert.IsTrue(Directory.Exists(firstTest.Path));
//can't create instances in installation directory
@@ -135,7 +135,7 @@ namespace Tgstation.Server.Tests
//online it for real for component tests
firstTest.Online = true;
firstTest.ConfigurationType = ConfigurationType.HostWrite;
- firstTest = await instanceManagerClient.Update(FromResponse(firstTest), cancellationToken).ConfigureAwait(false);
+ firstTest = await instanceManagerClient.Update(FromResponse(firstTest), cancellationToken).ConfigureAwait(false);
Assert.AreEqual(true, firstTest.Online);
Assert.AreEqual(ConfigurationType.HostWrite, firstTest.ConfigurationType);
Assert.IsTrue(Directory.Exists(firstTest.Path));
@@ -175,7 +175,7 @@ namespace Tgstation.Server.Tests
await ApiAssert.ThrowsException(() => instanceManagerClient.Detach(firstTest, cancellationToken), ErrorCode.InstanceDetachOnline).ConfigureAwait(false);
firstTest.Online = false;
- firstTest = await instanceManagerClient.Update(FromResponse(firstTest), cancellationToken).ConfigureAwait(false);
+ firstTest = await instanceManagerClient.Update(FromResponse(firstTest), cancellationToken).ConfigureAwait(false);
await instanceManagerClient.Detach(firstTest, cancellationToken).ConfigureAwait(false);
@@ -183,7 +183,7 @@ namespace Tgstation.Server.Tests
Assert.IsTrue(File.Exists(attachPath));
//can recreate detached instance
- firstTest = await instanceManagerClient.CreateOrAttach(FromResponse(firstTest), cancellationToken).ConfigureAwait(false);
+ firstTest = await instanceManagerClient.CreateOrAttach(FromResponse(firstTest), cancellationToken).ConfigureAwait(false);
// Test updating only with SetChatBotLimit works
var current = await usersClient.Read(cancellationToken);
@@ -210,7 +210,7 @@ namespace Tgstation.Server.Tests
//but only if the attach file exists
await instanceManagerClient.Detach(firstTest, cancellationToken).ConfigureAwait(false);
File.Delete(attachPath);
- await ApiAssert.ThrowsException(() => instanceManagerClient.CreateOrAttach(FromResponse(firstTest), cancellationToken), ErrorCode.InstanceAtExistingPath).ConfigureAwait(false);
+ await ApiAssert.ThrowsException(() => instanceManagerClient.CreateOrAttach(FromResponse(firstTest), cancellationToken), ErrorCode.InstanceAtExistingPath).ConfigureAwait(false);
}
}
}