< Summary

Information
Class: Elsa.Bpmn.Interchange.Endpoints.Bpmn.Import.Import
Assembly: Elsa.Bpmn.Interchange
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Bpmn.Interchange/Endpoints/Bpmn/Import/Endpoint.cs
Line coverage
52%
Covered lines: 23
Uncovered lines: 21
Coverable lines: 44
Total lines: 105
Line coverage: 52.2%
Branch coverage
40%
Covered branches: 4
Total branches: 10
Branch coverage: 40%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
Configure()100%11100%
HandleAsync()40%191054.54%
AddCapabilityErrors(...)100%210%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Bpmn.Interchange/Endpoints/Bpmn/Import/Endpoint.cs

#LineLine coverage
 1using Bpmn.Interchange;
 2using Bpmn.Semantics;
 3using Elsa.Abstractions;
 4using Elsa.Bpmn.Interchange.Exceptions;
 5using Elsa.Bpmn.Interchange.Services;
 6using JetBrains.Annotations;
 7using Microsoft.AspNetCore.Http;
 8
 9namespace Elsa.Bpmn.Interchange.Endpoints.Bpmn.Import;
 10
 11/// <summary>
 12/// Reads a <c>.bpmn</c> document and persists it as a workflow definition whose root is the <see cref="Elsa.Bpmn.Activi
 13/// scope <c>BpmnWorkBinder</c> builds for it.
 14/// </summary>
 15/// <remarks>
 16/// A thin wrapper over <see cref="BpmnInterchangeDocumentService.ImportAsync"/>. Its remarks explain why capability
 17/// refusal is surfaced here rather than left to first execution, and why nothing beyond the source XML is persisted
 18/// alongside the bound activity graph.
 19/// </remarks>
 20[UsedImplicitly]
 1421internal sealed class Import(BpmnInterchangeDocumentService documentService) : ElsaEndpoint<Request>
 22{
 23    /// <inheritdoc />
 24    public override void Configure()
 25    {
 1126        Post("bpmn/import");
 1127        AllowFileUploads();
 1128        ConfigurePermissions("write:workflow-definitions");
 1129    }
 30
 31    /// <inheritdoc />
 32    public override async Task HandleAsync(Request request, CancellationToken cancellationToken)
 33    {
 334        if (Files.Count != 1)
 35        {
 036            AddError("Upload exactly one .bpmn file.");
 037            await Send.ErrorsAsync(StatusCodes.Status400BadRequest, cancellationToken);
 038            return;
 39        }
 40
 341        var xml = await BpmnUploadedFileReader.ReadTextAsync(Files[0], cancellationToken);
 42
 43        BpmnDocumentImportResult result;
 44
 45        try
 46        {
 347            result = await documentService.ImportAsync(xml, request.DefinitionId, request.Name, request.ProcessId, cance
 248        }
 049        catch (BpmnInterchangeException exception)
 50        {
 051            AddError(exception.Message);
 052            await Send.ErrorsAsync(StatusCodes.Status400BadRequest, cancellationToken);
 053            return;
 54        }
 155        catch (BpmnBindingException exception)
 56        {
 157            AddError(exception.Message);
 158            await Send.ErrorsAsync(StatusCodes.Status422UnprocessableEntity, cancellationToken);
 159            return;
 60        }
 061        catch (BpmnCapabilityException exception)
 62        {
 063            AddCapabilityErrors(exception);
 064            await Send.ErrorsAsync(StatusCodes.Status422UnprocessableEntity, cancellationToken);
 065            return;
 66        }
 67
 268        if (!result.ImportResult.Succeeded)
 69        {
 070            foreach (var validationError in result.ImportResult.ValidationErrors)
 071                AddError(validationError.Message);
 72
 073            await Send.ErrorsAsync(StatusCodes.Status400BadRequest, cancellationToken);
 074            return;
 75        }
 76
 277        var definition = result.ImportResult.WorkflowDefinition;
 78
 279        await Send.OkAsync(new Response
 280        {
 281            Id = definition.Id,
 282            DefinitionId = definition.DefinitionId,
 283            Version = definition.Version,
 284            Analysis = BpmnImportAnalysisModel.From(result.Analysis)
 285        }, cancellationToken);
 386    }
 87
 88    /// <remarks>
 89    /// <see cref="BpmnCapabilityException"/> carries <see cref="BpmnCapabilityException.DrivingElementIds"/> as a
 90    /// single flat list, already unioned across every missing capability — it does not say which element drove which
 91    /// capability (unlike <c>BpmnCapabilityRequirements.DrivingElementIds</c>, which is per-capability, but that type
 92    /// is gone by the time this catch clause sees the exception). Attributing the full, unioned list to each
 93    /// capability individually would put elements next to a capability they may have nothing to do with, so this
 94    /// reports the missing capabilities together with the combined element list once, rather than repeating it.
 95    /// </remarks>
 96    private void AddCapabilityErrors(BpmnCapabilityException exception)
 97    {
 098        var missingCapabilities = string.Join(", ", BpmnInterchangeDocumentService.IndividualCapabilities.Where(capabili
 099        var elementIds = string.Join(", ", exception.DrivingElementIds);
 100
 0101        AddError(
 0102            $"This deployment does not declare the following BPMN host capabilities the document requires: {missingCapab
 0103            + $"Offending elements (combined across all missing capabilities above, not attributable to any one of them)
 0104    }
 105}