< 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: 106
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 Elsa.Authorization;
 2using Bpmn.Interchange;
 3using Bpmn.Semantics;
 4using Elsa.Abstractions;
 5using Elsa.Bpmn.Interchange.Exceptions;
 6using Elsa.Bpmn.Interchange.Services;
 7using JetBrains.Annotations;
 8using Microsoft.AspNetCore.Http;
 9
 10namespace Elsa.Bpmn.Interchange.Endpoints.Bpmn.Import;
 11
 12/// <summary>
 13/// Reads a <c>.bpmn</c> document and persists it as a workflow definition whose root is the <see cref="Elsa.Bpmn.Activi
 14/// scope <c>BpmnWorkBinder</c> builds for it.
 15/// </summary>
 16/// <remarks>
 17/// A thin wrapper over <see cref="BpmnInterchangeDocumentService.ImportAsync"/>. Its remarks explain why capability
 18/// refusal is surfaced here rather than left to first execution, and why nothing beyond the source XML is persisted
 19/// alongside the bound activity graph.
 20/// </remarks>
 21[UsedImplicitly]
 1422internal sealed class Import(BpmnInterchangeDocumentService documentService) : ElsaEndpoint<Request>
 23{
 24    /// <inheritdoc />
 25    public override void Configure()
 26    {
 1127        Post("bpmn/import");
 1128        AllowFileUploads();
 1129        RequirePermission(Elsa.Bpmn.Interchange.Permissions.BpmnPermissions.Definitions, CoreVerbs.Write);
 1130    }
 31
 32    /// <inheritdoc />
 33    public override async Task HandleAsync(Request request, CancellationToken cancellationToken)
 34    {
 335        if (Files.Count != 1)
 36        {
 037            AddError("Upload exactly one .bpmn file.");
 038            await Send.ErrorsAsync(StatusCodes.Status400BadRequest, cancellationToken);
 039            return;
 40        }
 41
 342        var xml = await BpmnUploadedFileReader.ReadTextAsync(Files[0], cancellationToken);
 43
 44        BpmnDocumentImportResult result;
 45
 46        try
 47        {
 348            result = await documentService.ImportAsync(xml, request.DefinitionId, request.Name, request.ProcessId, cance
 249        }
 050        catch (BpmnInterchangeException exception)
 51        {
 052            AddError(exception.Message);
 053            await Send.ErrorsAsync(StatusCodes.Status400BadRequest, cancellationToken);
 054            return;
 55        }
 156        catch (BpmnBindingException exception)
 57        {
 158            AddError(exception.Message);
 159            await Send.ErrorsAsync(StatusCodes.Status422UnprocessableEntity, cancellationToken);
 160            return;
 61        }
 062        catch (BpmnCapabilityException exception)
 63        {
 064            AddCapabilityErrors(exception);
 065            await Send.ErrorsAsync(StatusCodes.Status422UnprocessableEntity, cancellationToken);
 066            return;
 67        }
 68
 269        if (!result.ImportResult.Succeeded)
 70        {
 071            foreach (var validationError in result.ImportResult.ValidationErrors)
 072                AddError(validationError.Message);
 73
 074            await Send.ErrorsAsync(StatusCodes.Status400BadRequest, cancellationToken);
 075            return;
 76        }
 77
 278        var definition = result.ImportResult.WorkflowDefinition;
 79
 280        await Send.OkAsync(new Response
 281        {
 282            Id = definition.Id,
 283            DefinitionId = definition.DefinitionId,
 284            Version = definition.Version,
 285            Analysis = BpmnImportAnalysisModel.From(result.Analysis)
 286        }, cancellationToken);
 387    }
 88
 89    /// <remarks>
 90    /// <see cref="BpmnCapabilityException"/> carries <see cref="BpmnCapabilityException.DrivingElementIds"/> as a
 91    /// single flat list, already unioned across every missing capability — it does not say which element drove which
 92    /// capability (unlike <c>BpmnCapabilityRequirements.DrivingElementIds</c>, which is per-capability, but that type
 93    /// is gone by the time this catch clause sees the exception). Attributing the full, unioned list to each
 94    /// capability individually would put elements next to a capability they may have nothing to do with, so this
 95    /// reports the missing capabilities together with the combined element list once, rather than repeating it.
 96    /// </remarks>
 97    private void AddCapabilityErrors(BpmnCapabilityException exception)
 98    {
 099        var missingCapabilities = string.Join(", ", BpmnInterchangeDocumentService.IndividualCapabilities.Where(capabili
 0100        var elementIds = string.Join(", ", exception.DrivingElementIds);
 101
 0102        AddError(
 0103            $"This deployment does not declare the following BPMN host capabilities the document requires: {missingCapab
 0104            + $"Offending elements (combined across all missing capabilities above, not attributable to any one of them)
 0105    }
 106}