diff --git a/src/Speckle.Sdk/Api/GraphQL/Client.cs b/src/Speckle.Sdk/Api/GraphQL/Client.cs index b503a162..abe96585 100644 --- a/src/Speckle.Sdk/Api/GraphQL/Client.cs +++ b/src/Speckle.Sdk/Api/GraphQL/Client.cs @@ -35,7 +35,7 @@ public sealed class Client : ISpeckleGraphQLClient, IClient public WorkspaceResource Workspace { get; } public ServerResource Server { get; } public FileImportResource FileImport { get; } - public IngestResource Ingest { get; } + public ModelIngestionResource Ingestion { get; } public Uri ServerUrl => new(Account.serverInfo.url); @@ -72,7 +72,7 @@ public sealed class Client : ISpeckleGraphQLClient, IClient Workspace = new(this); Server = new(this); FileImport = new(this, blobApiFactory.Create(account)); - Ingest = new(this); + Ingestion = new(this); } [AutoInterfaceIgnore] diff --git a/src/Speckle.Sdk/Api/GraphQL/Enums/FileUploadConversionStatus.cs b/src/Speckle.Sdk/Api/GraphQL/Enums/FileUploadConversionStatus.cs index 327f947e..21f0e49d 100644 --- a/src/Speckle.Sdk/Api/GraphQL/Enums/FileUploadConversionStatus.cs +++ b/src/Speckle.Sdk/Api/GraphQL/Enums/FileUploadConversionStatus.cs @@ -1,6 +1,9 @@ -namespace Speckle.Sdk.Api.GraphQL.Enums; +// ReSharper disable InconsistentNaming +namespace Speckle.Sdk.Api.GraphQL.Enums; -//This enum isn't explicitly defined in the schema, instead its usages are int typed (But represent an enum) +/// +/// This enum isn't explicitly defined in the schema, instead its usages are int typed (But represent an enum) +/// public enum FileUploadConversionStatus { Queued = 0, diff --git a/src/Speckle.Sdk/Api/GraphQL/Enums/ModelIngestionStatus.cs b/src/Speckle.Sdk/Api/GraphQL/Enums/ModelIngestionStatus.cs new file mode 100644 index 00000000..85ca92dc --- /dev/null +++ b/src/Speckle.Sdk/Api/GraphQL/Enums/ModelIngestionStatus.cs @@ -0,0 +1,14 @@ +// ReSharper disable InconsistentNaming +namespace Speckle.Sdk.Api.GraphQL.Enums; + +/// +/// string based enum +/// +public enum ModelIngestionStatus +{ + cancelled, + failed, + processing, + queued, + success, +} diff --git a/src/Speckle.Sdk/Api/GraphQL/Enums/ProjectCommentsUpdatedMessageType.cs b/src/Speckle.Sdk/Api/GraphQL/Enums/ProjectCommentsUpdatedMessageType.cs index 2500425d..8bd5937c 100644 --- a/src/Speckle.Sdk/Api/GraphQL/Enums/ProjectCommentsUpdatedMessageType.cs +++ b/src/Speckle.Sdk/Api/GraphQL/Enums/ProjectCommentsUpdatedMessageType.cs @@ -1,5 +1,8 @@ namespace Speckle.Sdk.Api.GraphQL.Enums; +/// +/// string based enum +/// public enum ProjectCommentsUpdatedMessageType { ARCHIVED, diff --git a/src/Speckle.Sdk/Api/GraphQL/Enums/ProjectFileImportUpdatedMessageType.cs b/src/Speckle.Sdk/Api/GraphQL/Enums/ProjectFileImportUpdatedMessageType.cs index b1947272..9c53eebd 100644 --- a/src/Speckle.Sdk/Api/GraphQL/Enums/ProjectFileImportUpdatedMessageType.cs +++ b/src/Speckle.Sdk/Api/GraphQL/Enums/ProjectFileImportUpdatedMessageType.cs @@ -1,5 +1,8 @@ namespace Speckle.Sdk.Api.GraphQL.Enums; +/// +/// string based enum +/// public enum ProjectFileImportUpdatedMessageType { CREATED, diff --git a/src/Speckle.Sdk/Api/GraphQL/Enums/ProjectModelIngestionUpdatedMessageType.cs b/src/Speckle.Sdk/Api/GraphQL/Enums/ProjectModelIngestionUpdatedMessageType.cs new file mode 100644 index 00000000..2ec41934 --- /dev/null +++ b/src/Speckle.Sdk/Api/GraphQL/Enums/ProjectModelIngestionUpdatedMessageType.cs @@ -0,0 +1,13 @@ +// ReSharper disable InconsistentNaming +namespace Speckle.Sdk.Api.GraphQL.Enums; + +/// +/// string based enum +/// +public enum ProjectModelIngestionUpdatedMessageType +{ + cancellationRequested, + created, + deleted, + updated, +} diff --git a/src/Speckle.Sdk/Api/GraphQL/Enums/ProjectModelsUpdatedMessageType.cs b/src/Speckle.Sdk/Api/GraphQL/Enums/ProjectModelsUpdatedMessageType.cs index b6316fb0..11dc5440 100644 --- a/src/Speckle.Sdk/Api/GraphQL/Enums/ProjectModelsUpdatedMessageType.cs +++ b/src/Speckle.Sdk/Api/GraphQL/Enums/ProjectModelsUpdatedMessageType.cs @@ -1,5 +1,8 @@ namespace Speckle.Sdk.Api.GraphQL.Enums; +/// +/// string based enum +/// public enum ProjectModelsUpdatedMessageType { CREATED, diff --git a/src/Speckle.Sdk/Api/GraphQL/Enums/ProjectPendingModelsUpdatedMessageType.cs b/src/Speckle.Sdk/Api/GraphQL/Enums/ProjectPendingModelsUpdatedMessageType.cs index 8e24a9c8..d2735ab9 100644 --- a/src/Speckle.Sdk/Api/GraphQL/Enums/ProjectPendingModelsUpdatedMessageType.cs +++ b/src/Speckle.Sdk/Api/GraphQL/Enums/ProjectPendingModelsUpdatedMessageType.cs @@ -1,5 +1,8 @@ namespace Speckle.Sdk.Api.GraphQL.Enums; +/// +/// string based enum +/// public enum ProjectPendingModelsUpdatedMessageType { CREATED, diff --git a/src/Speckle.Sdk/Api/GraphQL/Enums/ProjectUpdatedMessageType.cs b/src/Speckle.Sdk/Api/GraphQL/Enums/ProjectUpdatedMessageType.cs index a056708c..7e51e635 100644 --- a/src/Speckle.Sdk/Api/GraphQL/Enums/ProjectUpdatedMessageType.cs +++ b/src/Speckle.Sdk/Api/GraphQL/Enums/ProjectUpdatedMessageType.cs @@ -1,5 +1,8 @@ namespace Speckle.Sdk.Api.GraphQL.Enums; +/// +/// string based enum +/// public enum ProjectUpdatedMessageType { DELETED, diff --git a/src/Speckle.Sdk/Api/GraphQL/Enums/ProjectVersionsUpdatedMessageType.cs b/src/Speckle.Sdk/Api/GraphQL/Enums/ProjectVersionsUpdatedMessageType.cs index 977c93f8..2f83fbd4 100644 --- a/src/Speckle.Sdk/Api/GraphQL/Enums/ProjectVersionsUpdatedMessageType.cs +++ b/src/Speckle.Sdk/Api/GraphQL/Enums/ProjectVersionsUpdatedMessageType.cs @@ -1,5 +1,8 @@ namespace Speckle.Sdk.Api.GraphQL.Enums; +/// +/// string based enum +/// public enum ProjectVersionsUpdatedMessageType { CREATED, diff --git a/src/Speckle.Sdk/Api/GraphQL/Enums/ProjectVisibility.cs b/src/Speckle.Sdk/Api/GraphQL/Enums/ProjectVisibility.cs index b0692127..c019dd38 100644 --- a/src/Speckle.Sdk/Api/GraphQL/Enums/ProjectVisibility.cs +++ b/src/Speckle.Sdk/Api/GraphQL/Enums/ProjectVisibility.cs @@ -1,5 +1,9 @@ +// ReSharper disable InconsistentNaming namespace Speckle.Sdk.Api.GraphQL.Enums; +/// +/// string based enum +/// public enum ProjectVisibility { Private, diff --git a/src/Speckle.Sdk/Api/GraphQL/Enums/ResourceType.cs b/src/Speckle.Sdk/Api/GraphQL/Enums/ResourceType.cs index d592e308..ecb8ba29 100644 --- a/src/Speckle.Sdk/Api/GraphQL/Enums/ResourceType.cs +++ b/src/Speckle.Sdk/Api/GraphQL/Enums/ResourceType.cs @@ -1,5 +1,9 @@ -namespace Speckle.Sdk.Api.GraphQL.Enums; +// ReSharper disable InconsistentNaming +namespace Speckle.Sdk.Api.GraphQL.Enums; +/// +/// string based enum +/// public enum ResourceType { commit, diff --git a/src/Speckle.Sdk/Api/GraphQL/Enums/UserProjectsUpdatedMessageType.cs b/src/Speckle.Sdk/Api/GraphQL/Enums/UserProjectsUpdatedMessageType.cs index 1ad08ce3..4891c110 100644 --- a/src/Speckle.Sdk/Api/GraphQL/Enums/UserProjectsUpdatedMessageType.cs +++ b/src/Speckle.Sdk/Api/GraphQL/Enums/UserProjectsUpdatedMessageType.cs @@ -1,5 +1,8 @@ namespace Speckle.Sdk.Api.GraphQL.Enums; +/// +/// string based enum +/// public enum UserProjectsUpdatedMessageType { ADDED, diff --git a/src/Speckle.Sdk/Api/GraphQL/Inputs/IngestInputs.cs b/src/Speckle.Sdk/Api/GraphQL/Inputs/IngestInputs.cs index 82eaf03a..8fd9db80 100644 --- a/src/Speckle.Sdk/Api/GraphQL/Inputs/IngestInputs.cs +++ b/src/Speckle.Sdk/Api/GraphQL/Inputs/IngestInputs.cs @@ -1,4 +1,6 @@ -namespace Speckle.Sdk.Api.GraphQL.Inputs; +using Speckle.Sdk.Api.GraphQL.Enums; + +namespace Speckle.Sdk.Api.GraphQL.Inputs; public record SourceDataInput( string sourceApplicationSlug, @@ -32,3 +34,16 @@ public record ModelIngestionFailedInput( } public record ModelIngestionCancelledInput(string ingestionId, string projectId, string cancellationMessage); + +public record ProjectModelIngestionSubscriptionInput( + string projectId, + ModelIngestionReference ingestionReference, + ProjectModelIngestionUpdatedMessageType messageType +); + +/// +/// @oneOf i.e. server expects either or , but not both. +/// +/// +/// +public record ModelIngestionReference(string? ingestionId, string? modelId); diff --git a/src/Speckle.Sdk/Api/GraphQL/Models/ModelIngestion.cs b/src/Speckle.Sdk/Api/GraphQL/Models/ModelIngestion.cs index e10d91be..58d0dc67 100644 --- a/src/Speckle.Sdk/Api/GraphQL/Models/ModelIngestion.cs +++ b/src/Speckle.Sdk/Api/GraphQL/Models/ModelIngestion.cs @@ -5,5 +5,8 @@ public sealed class ModelIngestion public required string id { get; init; } public required DateTime createdAt { get; init; } public required DateTime updatedAt { get; init; } + public required string modelId { get; init; } + public required bool cancellationRequested { get; init; } + public required ModelIngestionStatusData statusData { get; init; } // public required LimitedUser user { get; init; } } diff --git a/src/Speckle.Sdk/Api/GraphQL/Models/ModelIngestionStatusData.cs b/src/Speckle.Sdk/Api/GraphQL/Models/ModelIngestionStatusData.cs new file mode 100644 index 00000000..a195bdfc --- /dev/null +++ b/src/Speckle.Sdk/Api/GraphQL/Models/ModelIngestionStatusData.cs @@ -0,0 +1,9 @@ +using Speckle.Sdk.Api.GraphQL.Enums; + +namespace Speckle.Sdk.Api.GraphQL.Models; + +public sealed class ModelIngestionStatusData +{ + public required ModelIngestionStatus status { get; init; } + public required string? progressMessage { get; init; } +} diff --git a/src/Speckle.Sdk/Api/GraphQL/Models/SubscriptionMessages.cs b/src/Speckle.Sdk/Api/GraphQL/Models/SubscriptionMessages.cs index 9f085c30..0f4ebb07 100644 --- a/src/Speckle.Sdk/Api/GraphQL/Models/SubscriptionMessages.cs +++ b/src/Speckle.Sdk/Api/GraphQL/Models/SubscriptionMessages.cs @@ -83,8 +83,11 @@ public sealed class ProjectVersionsUpdatedMessage : EventArgs public Version? version { get; init; } } -public sealed class ProjectModelIngestionCancellationRequestedMessage : EventArgs +public sealed class ProjectModelIngestionUpdatedMessage : EventArgs { [JsonRequired] - public required ModelIngestion? modelIngestion { get; init; } + public required ModelIngestion modelIngestion { get; init; } + + [JsonRequired] + public required ProjectModelIngestionUpdatedMessageType type { get; init; } } diff --git a/src/Speckle.Sdk/Api/GraphQL/Resources/ModelIngestionResource.cs b/src/Speckle.Sdk/Api/GraphQL/Resources/ModelIngestionResource.cs index 2053a7d3..ffcabb91 100644 --- a/src/Speckle.Sdk/Api/GraphQL/Resources/ModelIngestionResource.cs +++ b/src/Speckle.Sdk/Api/GraphQL/Resources/ModelIngestionResource.cs @@ -5,11 +5,11 @@ using Speckle.Sdk.Api.GraphQL.Models.Responses; namespace Speckle.Sdk.Api.GraphQL.Resources; -public sealed class Ingestionresource +public sealed class ModelIngestionResource { private readonly ISpeckleGraphQLClient _client; - internal Ingestionresource(ISpeckleGraphQLClient client) + internal ModelIngestionResource(ISpeckleGraphQLClient client) { _client = client; } @@ -32,6 +32,16 @@ public sealed class Ingestionresource id createdAt updatedAt + modelId + cancellationRequested + statusData { + ... on HasModelIngestionStatus { + status + } + ... on HasProgressMessage { + progressMessage + } + } } } } @@ -70,39 +80,14 @@ public sealed class Ingestionresource id createdAt updatedAt - } - } - } - } - """; - - GraphQLRequest request = new() { Query = QUERY, Variables = new { input } }; - - var res = await _client - .ExecuteGraphQLRequest>>>( - request, - cancellationToken - ) - .ConfigureAwait(false); - - return res.data.data.data; - } - - /// - /// - /// The version id - /// - public async Task Complete(ModelIngestionSuccessInput input, CancellationToken cancellationToken = default) - { - //language=graphql - const string QUERY = """ - mutation IngestionComplete($input: ModelIngestionSuccessInput!) { - data: projectMutations { - data: modelIngestionMutations { - data: completeWithVersion(input: $input) { - data:statusData { - ... on ModelIngestionSuccessStatus { - versionId + modelId + cancellationRequested + statusData { + ... on HasModelIngestionStatus { + status + } + ... on HasProgressMessage { + progressMessage } } } @@ -113,42 +98,6 @@ public sealed class Ingestionresource GraphQLRequest request = new() { Query = QUERY, Variables = new { input } }; - var res = await _client - .ExecuteGraphQLRequest>>>>( - request, - cancellationToken - ) - .ConfigureAwait(false); - - return res.data.data.data.data; - } - - /// - /// - /// - /// - public async Task FailWithError( - ModelIngestionFailedInput input, - CancellationToken cancellationToken = default - ) - { - //language=graphql - const string QUERY = """ - mutation IngestionFailWithError($input: ModelIngestionFailedInput!) { - data: projectMutations { - data: modelIngestionMutations { - data: failWithError(input: $input) { - id - createdAt - updatedAt - } - } - } - } - """; - - GraphQLRequest request = new() { Query = QUERY, Variables = new { input } }; - var res = await _client .ExecuteGraphQLRequest>>>( request, @@ -160,14 +109,107 @@ public sealed class Ingestionresource } /// - /// Request that the ingestion is canceled. + /// Request that the server completes the ingestion by creating a version + /// If successful, the job will be in a terminal "successful" state. + /// + /// + /// + /// + /// + /// The version id + /// + public async Task Complete(ModelIngestionSuccessInput input, CancellationToken cancellationToken = default) + { + //language=graphql + const string QUERY = """ + mutation IngestionComplete($input: ModelIngestionSuccessInput!) { + data: projectMutations { + data: modelIngestionMutations { + data: completeWithVersion(input: $input) { + data:statusData { + ... on ModelIngestionSuccessStatus { + data:versionId + } + } + } + } + } + } + """; + + GraphQLRequest request = new() { Query = QUERY, Variables = new { input } }; + + var res = await _client + .ExecuteGraphQLRequest< + RequiredResponse>>>> + >(request, cancellationToken) + .ConfigureAwait(false); + + return res.data.data.data.data.data; + } + + /// + /// Fail the job with an error. /// /// - /// Note it's up to the client to observe this cancellation request - /// via - /// and report it as canceled via `ingestion.fail_with_cancelled`. - /// - /// See "cooperative cancellation pattern" + /// For requested user cancellation, use instead + /// + /// + /// + /// + /// + /// + /// + public async Task FailWithError( + ModelIngestionFailedInput input, + CancellationToken cancellationToken = default + ) + { + //language=graphql + const string QUERY = """ + mutation IngestionFailWithError($input: ModelIngestionFailedInput!) { + data: projectMutations { + data: modelIngestionMutations { + data: failWithError(input: $input) { + id + createdAt + updatedAt + modelId + cancellationRequested + statusData { + ... on HasModelIngestionStatus { + status + } + ... on HasProgressMessage { + progressMessage + } + } + } + } + } + } + """; + + GraphQLRequest request = new() { Query = QUERY, Variables = new { input } }; + + var res = await _client + .ExecuteGraphQLRequest>>>( + request, + cancellationToken + ) + .ConfigureAwait(false); + + return res.data.data.data; + } + + /// + /// Fail the ingestion with a canceled status. + /// This should only be done if the user has explicitly requested cancellation + /// Other forms of cancellation use . + /// The ingestion should then enter a terminal "canceled" state + /// + /// + /// /// /// /// @@ -186,6 +228,74 @@ public sealed class Ingestionresource id createdAt updatedAt + modelId + cancellationRequested + statusData { + ... on HasModelIngestionStatus { + status + } + ... on HasProgressMessage { + progressMessage + } + } + } + } + } + } + """; + + GraphQLRequest request = new() { Query = QUERY, Variables = new { input } }; + + var res = await _client + .ExecuteGraphQLRequest>>>( + request, + cancellationToken + ) + .ConfigureAwait(false); + + return res.data.data.data; + } + + /// + /// Request that the is canceled. + /// + /// + /// Note simply calling this mutation does not imediatly cancel, it doesn't even guarantee it will be canceled at all. + /// It's up to the client to observe this cancellation request + /// via + /// and report it as canceled via + /// See "cooperative cancellation pattern" + /// + /// + /// + /// + /// + /// + /// + public async Task RequestCancellation( + ModelIngestionCancelledInput input, + CancellationToken cancellationToken = default + ) + { + //language=graphql + const string QUERY = """ + mutation IngestionRequestCancellation($input: ModelIngestionRequestCancellationInput!) { + data: projectMutations { + data: modelIngestionMutations { + data: requestCancellation (input: $input) { + id + createdAt + updatedAt + modelId + cancellationRequested + statusData { + ... on HasModelIngestionStatus { + status + } + ... on HasProgressMessage { + progressMessage + } + } } } } diff --git a/src/Speckle.Sdk/Api/GraphQL/Resources/SubscriptionResource.cs b/src/Speckle.Sdk/Api/GraphQL/Resources/SubscriptionResource.cs index ca9ce2ac..4b38a449 100644 --- a/src/Speckle.Sdk/Api/GraphQL/Resources/SubscriptionResource.cs +++ b/src/Speckle.Sdk/Api/GraphQL/Resources/SubscriptionResource.cs @@ -1,4 +1,5 @@ using GraphQL; +using Speckle.Sdk.Api.GraphQL.Enums; using Speckle.Sdk.Api.GraphQL.Inputs; using Speckle.Sdk.Api.GraphQL.Models; using Speckle.Sdk.Api.GraphQL.Models.Responses; @@ -215,31 +216,49 @@ public sealed class SubscriptionResource : IDisposable /// Subscribe to a cancellation request being made for a Model Ingestion /// /// - public Subscription CreateProjectModelIngestionCancellationRequestedSubscription( - string ingestionId, - string projectId + public Subscription CreateProjectModelIngestionUpdatedSubscription( + ProjectModelIngestionSubscriptionInput input ) { //language=graphql const string QUERY = """ - subscription IngestionCancellationRequested($projectId: ID!, $ingestionId: ID!){ - data:projectModelIngestionCancellationRequested(projectId: $projectId, ingestionId: $ingestionId) { - modelIngestion - { + subscription IngestionUpdated( + $input: ProjectModelIngestionSubscriptionInput! + ) { + data: projectModelIngestionUpdated(input: $input) { + modelIngestion { id createdAt updatedAt } + type } } """; - GraphQLRequest request = new() { Query = QUERY, Variables = new { projectId, ingestionId } }; + GraphQLRequest request = new() { Query = QUERY, Variables = new { input } }; - Subscription subscription = new(_client, request); + Subscription subscription = new(_client, request); _subscriptions.Add(subscription); return subscription; } + /// Subscribe to a cancellation request being made for a Model Ingestion + /// + /// + public Subscription CreateProjectModelIngestionCancellationRequestedSubscription( + string ingestionId, + string projectId + ) + { + return CreateProjectModelIngestionUpdatedSubscription( + new ProjectModelIngestionSubscriptionInput( + projectId, + new(ingestionId, null), + ProjectModelIngestionUpdatedMessageType.cancellationRequested + ) + ); + } + public void Dispose() { foreach (var subscription in _subscriptions) diff --git a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ModelIngestionResourceExceptionalTests.CancelNonExistentIngestion.verified.json b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ModelIngestionResourceExceptionalTests.CancelNonExistentIngestion.verified.json new file mode 100644 index 00000000..c6fca10c --- /dev/null +++ b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ModelIngestionResourceExceptionalTests.CancelNonExistentIngestion.verified.json @@ -0,0 +1,8 @@ +{ + "Type": "AggregateException", + "InnerException": { + "Data": {}, + "Message": "NOT_FOUND_ERROR: Model ingestion was not found", + "Type": "SpeckleGraphQLException" + } +} diff --git a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ModelIngestionResourceExceptionalTests.CreateIngestionNonExistentProject.verified.json b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ModelIngestionResourceExceptionalTests.CreateIngestionNonExistentProject.verified.json new file mode 100644 index 00000000..04a2199a --- /dev/null +++ b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ModelIngestionResourceExceptionalTests.CreateIngestionNonExistentProject.verified.json @@ -0,0 +1,8 @@ +{ + "Type": "AggregateException", + "InnerException": { + "Data": {}, + "Message": "STREAM_NOT_FOUND: Project not found", + "Type": "SpeckleGraphQLStreamNotFoundException" + } +} diff --git a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ModelIngestionResourceExceptionalTests.UpdateNonExistentNonExistent.verified.json b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ModelIngestionResourceExceptionalTests.UpdateNonExistentNonExistent.verified.json new file mode 100644 index 00000000..c6fca10c --- /dev/null +++ b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ModelIngestionResourceExceptionalTests.UpdateNonExistentNonExistent.verified.json @@ -0,0 +1,8 @@ +{ + "Type": "AggregateException", + "InnerException": { + "Data": {}, + "Message": "NOT_FOUND_ERROR: Model ingestion was not found", + "Type": "SpeckleGraphQLException" + } +} diff --git a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ModelIngestionResourceExceptionalTests.cs b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ModelIngestionResourceExceptionalTests.cs new file mode 100644 index 00000000..a548d523 --- /dev/null +++ b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ModelIngestionResourceExceptionalTests.cs @@ -0,0 +1,73 @@ +using System.Reflection; +using Speckle.Sdk.Api; +using Speckle.Sdk.Api.GraphQL.Inputs; +using Speckle.Sdk.Api.GraphQL.Models; +using Speckle.Sdk.Api.GraphQL.Resources; +using Speckle.Sdk.Host; +using Speckle.Sdk.Models; + +namespace Speckle.Sdk.Tests.Integration.API.GraphQL.Resources; + +public sealed class ModelIngestionResourceExceptionalTests : IAsyncLifetime +{ + private IClient _testUser; + private ModelIngestionResource Sut => _testUser.Ingestion; + private Project _project; + private Model _model; + + public Task DisposeAsync() => Task.CompletedTask; + + public async Task InitializeAsync() + { + TypeLoader.Reset(); + TypeLoader.Initialize(typeof(Base).Assembly, Assembly.GetExecutingAssembly()); + + _testUser = await Fixtures.SeedUserWithClient(); + _project = await _testUser.Project.Create(new("Test project", "", null)); + _model = await _testUser.Model.Create(new("Test Model 1", "", _project.id)); + } + + [Fact] + public async Task CreateIngestionNonExistentProject() + { + var createInput = new ModelIngestionCreateInput( + _model.id, + "Doesn't exist...", + "Starting processing", + new(".NET test runner", "0.0.0", null, null) + ); + + var ex = await Assert.ThrowsAsync(async () => + { + _ = await Sut.Create(createInput); + }); + await Verify(ex); + } + + [Fact] + public async Task UpdateNonExistentNonExistent() + { + var updateInput = new ModelIngestionUpdateInput("Doesn't exist", _project.id, "Can't be", 0.5); + + var ex = await Assert.ThrowsAsync(async () => + { + _ = await Sut.UpdateProgress(updateInput); + }); + await Verify(ex); + } + + [Fact] + public async Task CancelNonExistentIngestion() + { + var input = new ModelIngestionCancelledInput( + "Non-existent-ingestion", + _project.id, + cancellationMessage: "This was cancelled for testing purposes" + ); + var ex = await Assert.ThrowsAsync(async () => + { + _ = await Sut.FailWithCancel(input); + }); + await Verify(ex); + } +} diff --git a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ModelIngestionResourceTests.cs b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ModelIngestionResourceTests.cs index 6dfb2959..bbd88c6e 100644 --- a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ModelIngestionResourceTests.cs +++ b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/ModelIngestionResourceTests.cs @@ -1,6 +1,7 @@ using System.Reflection; using Microsoft.Extensions.DependencyInjection; using Speckle.Sdk.Api; +using Speckle.Sdk.Api.GraphQL.Enums; using Speckle.Sdk.Api.GraphQL.Inputs; using Speckle.Sdk.Api.GraphQL.Models; using Speckle.Sdk.Api.GraphQL.Resources; @@ -49,6 +50,34 @@ public sealed class ModelIngestionResourceTests : IAsyncLifetime Assert.Equal(ingest.id, res.id); } + [Fact] + public async Task CreateAndUpdate() + { + var createInput = new ModelIngestionCreateInput( + _model.id, + _project.id, + "Starting processing", + new(".NET test runner", "0.0.0", null, null) + ); + ModelIngestion ingest = await Sut.Create(createInput); + + await Update(null, "None"); + await Update(0.1, "0.1"); + await Update(0.5, "Whoa-oh! We're half way there!"); + await Update(1, "Finished"); + await Update(0.2, "Back to processing again"); + + async Task Update(double? progress, string message) + { + var updateInput = new ModelIngestionUpdateInput(ingest.id, _project.id, message, progress); + var res = await Sut.UpdateProgress(updateInput); + + Assert.Equal(message, res.statusData.progressMessage); + Assert.False(res.cancellationRequested); + Assert.Equal(ModelIngestionStatus.processing, res.statusData.status); + } + } + [Fact] public async Task CreateAndCancel() { @@ -103,7 +132,7 @@ public sealed class ModelIngestionResourceTests : IAsyncLifetime ModelIngestionSuccessInput finish = new(ingest.id, _project.id, sendResult.RootId); string versionId = await Sut.Complete(finish); Version version = await _testUser.Version.Get(versionId, _project.id); - Assert.Equal(versionId, version.id); + Assert.Equal(version.id, versionId); Assert.Equal(sendResult.RootId, version.referencedObject); } } diff --git a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/SubscriptionResourceTests.cs b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/SubscriptionResourceTests.cs index 7611658e..da015ec4 100644 --- a/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/SubscriptionResourceTests.cs +++ b/tests/Speckle.Sdk.Tests.Integration/Api/GraphQL/Resources/SubscriptionResourceTests.cs @@ -135,4 +135,24 @@ public class SubscriptionResourceTests : IAsyncLifetime subscriptionMessage.type.Should().Be(ProjectCommentsUpdatedMessageType.CREATED); subscriptionMessage.comment.Should().NotBeNull(); } + + [Fact(Timeout = TIMEOUT)] + public async Task ProjectModelIngestionCancellationRequested_SubscriptionIsCalled() + { + ModelIngestion ingestion = await _testUser.Ingestion.Create( + new(_testModel.id, _testProject.id, "", new(".NET test", "0.0.0", null, null)) + ); + TaskCompletionSource tcs = new(); + using var sub = Sut.CreateProjectModelIngestionCancellationRequestedSubscription(_testProject.id, ingestion.id); + sub.Listeners += (_, message) => tcs.SetResult(message); + + await Task.Delay(WAIT_PERIOD); // Give time to subscription to be setup + + await _testUser.Ingestion.RequestCancellation(new(ingestion.id, _testProject.id, "please cancel")); + + var subscriptionMessage = await tcs.Task; + + subscriptionMessage.Should().NotBeNull(); + subscriptionMessage.modelIngestion.id.Should().Be(ingestion.id); + } } diff --git a/tests/Speckle.Sdk.Tests.Integration/Fixtures.cs b/tests/Speckle.Sdk.Tests.Integration/Fixtures.cs index 3fcfd670..cb678129 100644 --- a/tests/Speckle.Sdk.Tests.Integration/Fixtures.cs +++ b/tests/Speckle.Sdk.Tests.Integration/Fixtures.cs @@ -21,7 +21,7 @@ namespace Speckle.Sdk.Tests.Integration; public static class Fixtures { - public static readonly ServerInfo Server = new() { url = "http://localhost:3000", name = "Docker Server" }; + public static readonly ServerInfo Server = new() { url = "http://localhost", name = "Docker Server" }; public static IServiceProvider ServiceProvider { get; set; }