Skip to content

Commit

Permalink
Fix #691 Domain File Store - File upload (#711)
Browse files Browse the repository at this point in the history
* error handling for domain file store upload
  • Loading branch information
joao4all authored Jul 29, 2024
1 parent 2da90f6 commit f4341b2
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ namespace COMETwebapp.Tests.Components.EngineeringModel.FileStore

using DevExpress.Blazor;

using FluentResults;

using Microsoft.AspNetCore.Components.Forms;

using Moq;
Expand All @@ -55,10 +57,14 @@ public class FileFormTestFixture
public void SetUp()
{
this.context = new TestContext();

var domainSelectorViewModel = new Mock<IDomainOfExpertiseSelectorViewModel>();

this.viewModel = new Mock<IFileHandlerViewModel>();
this.viewModel.Setup(x => x.CurrentThing).Returns(new File());
var domainSelectorViewModel = new Mock<IDomainOfExpertiseSelectorViewModel>();
this.viewModel.Setup(x => x.DomainOfExpertiseSelectorViewModel).Returns(domainSelectorViewModel.Object);
this.viewModel.Setup(x => x.CreateOrEditFile(It.IsAny<bool>())).ReturnsAsync(new Result());

this.context.ConfigureDevExpressBlazor();

this.renderer = this.context.RenderComponent<FileForm>(parameters => { parameters.Add(p => p.ViewModel, this.viewModel.Object); });
Expand All @@ -72,14 +78,8 @@ public void Teardown()
}

[Test]
public async Task VerifyOnValidSubmitAndDelete()
public async Task VerifyFileFormDelete()
{
Assert.That(this.renderer.Instance.IsDeletePopupVisible, Is.EqualTo(false));

var form = this.renderer.FindComponent<EditForm>();
await this.renderer.InvokeAsync(form.Instance.OnValidSubmit.InvokeAsync);
this.viewModel.Verify(x => x.CreateOrEditFile(It.IsAny<bool>()), Times.Once);

var deleteFileButton = this.renderer.FindComponents<DxButton>().First(x => x.Instance.Id == "deleteFileButton");
await this.renderer.InvokeAsync(deleteFileButton.Instance.Click.InvokeAsync);
Assert.That(this.renderer.Instance.IsDeletePopupVisible, Is.EqualTo(true));
Expand All @@ -93,5 +93,31 @@ public async Task VerifyOnValidSubmitAndDelete()
this.viewModel.Verify(x => x.DeleteFile(), Times.Once);
});
}

[Test]
public async Task VerifyOnValidSubmitAndDelete()
{
Assert.That(this.renderer.Instance.IsDeletePopupVisible, Is.EqualTo(false));

var form = this.renderer.FindComponent<EditForm>();
await this.renderer.InvokeAsync(form.Instance.OnValidSubmit.InvokeAsync);

Assert.Multiple(() =>
{
this.viewModel.Verify(x => x.CreateOrEditFile(It.IsAny<bool>()), Times.Once);
Assert.That(this.renderer.Instance.ErrorMessage, Is.Null.Or.Empty);
});

var failingResult = new Result();
failingResult.Reasons.Add(new ExceptionalError(new Exception("Invalid")));
this.viewModel.Setup(x => x.CreateOrEditFile(It.IsAny<bool>())).ReturnsAsync(failingResult);
await this.renderer.InvokeAsync(form.Instance.OnValidSubmit.InvokeAsync);

Assert.Multiple(() =>
{
this.viewModel.Verify(x => x.CreateOrEditFile(It.IsAny<bool>()), Times.Exactly(2));
Assert.That(this.renderer.Instance.ErrorMessage, Is.Not.Null.Or.Empty);
});
}
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="FileHandlerViewModelTestFixture.cs" company="Starion Group S.A.">
// Copyright (c) 2023-2024 Starion Group S.A.
// Copyright (c) 2024 Starion Group S.A.
//
// Authors: Sam Gerené, Alex Vorobiev, Alexander van Delft, Jaime Bernar, Antoine Théate, João Rua
// Authors: Sam Gerené, Alex Vorobiev, Alexander van Delft, Jaime Bernar, Théate Antoine, João Rua
//
// This file is part of CDP4-COMET WEB Community Edition
// The CDP4-COMET WEB Community Edition is the Starion Web Application implementation of ECSS-E-TM-10-25 Annex A and Annex C.
// This file is part of COMET WEB Community Edition
// The COMET WEB Community Edition is the Starion Group Web Application implementation of ECSS-E-TM-10-25 Annex A and Annex C.
//
// The CDP4-COMET WEB Community Edition is free software; you can redistribute it and/or
// The COMET WEB Community Edition is free software; you can redistribute it and/or
// modify it under the terms of the GNU Affero General Public
// License as published by the Free Software Foundation; either
// version 3 of the License, or (at your option) any later version.
//
// The CDP4-COMET WEB Community Edition is distributed in the hope that it will be useful,
// The COMET WEB Community Edition is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Affero General Public License for more details.
Expand All @@ -35,6 +35,8 @@ namespace COMETwebapp.Tests.ViewModels.Components.EngineeringModel.FileStore
using COMETwebapp.ViewModels.Components.EngineeringModel.FileStore.FileHandler;
using COMETwebapp.ViewModels.Components.EngineeringModel.FileStore.FileRevisionHandler;

using FluentResults;

using Microsoft.Extensions.Logging;

using Moq;
Expand Down Expand Up @@ -63,44 +65,44 @@ public void Setup()
this.iteration = new Iteration();
var person = new Person();

var siteDirectory = new SiteDirectory()
var siteDirectory = new SiteDirectory
{
Domain =
{
new DomainOfExpertise()
new DomainOfExpertise
{
ShortName = "doe",
Name = "Domain Of Expertise"
}
}
};

var engineeringModelSetup = new EngineeringModelSetup()
var engineeringModelSetup = new EngineeringModelSetup
{
Participant = { new Participant { Person = person } }
};

var folder1 = new Folder()
var folder1 = new Folder
{
Iid = Guid.NewGuid(),
Name = "folder 1"
};

var file1 = new File()
var file1 = new File
{
Iid = Guid.NewGuid(),
Iid = Guid.NewGuid(),
CurrentContainingFolder = folder1
};

var file2 = new File()
var file2 = new File
{
Iid = Guid.NewGuid(),
CurrentContainingFolder = folder1
};

file1.FileRevision.Add(new FileRevision());

this.commonFileStore = new CommonFileStore()
this.commonFileStore = new CommonFileStore
{
Name = "CFS",
Folder = { folder1 },
Expand All @@ -111,6 +113,8 @@ public void Setup()

this.sessionService.Setup(x => x.GetSiteDirectory()).Returns(siteDirectory);
this.sessionService.Setup(x => x.Session.ActivePerson).Returns(person);
this.sessionService.Setup(x => x.CreateOrUpdateThings(It.IsAny<Thing>(), It.IsAny<List<Thing>>(), It.IsAny<List<string>>())).ReturnsAsync(new Result());

this.viewModel = new FileHandlerViewModel(this.sessionService.Object, this.messageBus, this.logger.Object, this.fileRevisionHandlerViewModel.Object);
}

Expand All @@ -121,30 +125,11 @@ public void TearDown()
this.messageBus.Dispose();
}

[Test]
public void VerifyInitializeViewModel()
{
this.viewModel.InitializeViewModel(this.commonFileStore, this.iteration);
Assert.That(this.viewModel.DomainOfExpertiseSelectorViewModel.AvailableDomainsOfExpertise.Count(), Is.EqualTo(1));
}

[Test]
public async Task VerifyMoveAndDeleteFile()
{
this.viewModel.InitializeViewModel(this.commonFileStore, this.iteration);
this.viewModel.CurrentThing = this.commonFileStore.File[0];

await this.viewModel.MoveFile(this.commonFileStore.File[0], this.commonFileStore.Folder[0]);
this.sessionService.Verify(x => x.CreateOrUpdateThings(It.IsAny<FileStore>(), It.IsAny<IReadOnlyCollection<Thing>>()), Times.Once);

await this.viewModel.DeleteFile();
this.sessionService.Verify(x => x.DeleteThings(It.IsAny<FileStore>(), It.IsAny<IReadOnlyCollection<Thing>>()), Times.Once);
}

[Test]
public async Task VerifyCreateOrEditFolder()
{
this.viewModel.InitializeViewModel(this.commonFileStore, this.iteration);
this.viewModel.CurrentThing = this.commonFileStore.File.First();

var domain = new DomainOfExpertise();
this.viewModel.DomainOfExpertiseSelectorViewModel.SelectedDomainOfExpertise = domain;
Expand All @@ -153,20 +138,45 @@ public async Task VerifyCreateOrEditFolder()
await this.viewModel.CreateOrEditFile(false);
this.sessionService.Verify(x => x.CreateOrUpdateThings(It.IsAny<FileStore>(), It.Is<IReadOnlyCollection<Thing>>(c => !c.OfType<FileStore>().Any()), It.IsAny<IReadOnlyCollection<string>>()), Times.Once);

this.viewModel.SelectedFileRevisions = [new FileRevision(){ LocalPath = "/localpath" }];
this.viewModel.SelectedFileRevisions = [new FileRevision { LocalPath = "/localpath" }];
this.viewModel.IsLocked = true;

var failingResult = new Result();
failingResult.Reasons.Add(new ExceptionalError(new Exception("Invalid")));
this.sessionService.Setup(x => x.CreateOrUpdateThings(It.IsAny<Thing>(), It.IsAny<List<Thing>>(), It.IsAny<List<string>>())).ReturnsAsync(failingResult);

await this.viewModel.CreateOrEditFile(true);

Assert.Multiple(() =>
{
this.sessionService.Verify(x => x.CreateOrUpdateThings(
It.IsAny<FileStore>(),
It.Is<IReadOnlyCollection<Thing>>(c => c.OfType<FileStore>().Any()),
It.Is<IReadOnlyCollection<string>>(c => c.Contains("/localpath")))
, Times.Once);
It.IsAny<FileStore>(),
It.Is<IReadOnlyCollection<Thing>>(c => c.OfType<FileStore>().Any()),
It.Is<IReadOnlyCollection<string>>(c => c.Contains("/localpath")))
, Times.Once);

Assert.That(this.viewModel.CurrentThing.LockedBy, Is.Not.Null);
});
}

[Test]
public void VerifyInitializeViewModel()
{
this.viewModel.InitializeViewModel(this.commonFileStore, this.iteration);
Assert.That(this.viewModel.DomainOfExpertiseSelectorViewModel.AvailableDomainsOfExpertise.Count(), Is.EqualTo(1));
}

[Test]
public async Task VerifyMoveAndDeleteFile()
{
this.viewModel.InitializeViewModel(this.commonFileStore, this.iteration);
this.viewModel.CurrentThing = this.commonFileStore.File[0];

await this.viewModel.MoveFile(this.commonFileStore.File[0], this.commonFileStore.Folder[0]);
this.sessionService.Verify(x => x.CreateOrUpdateThings(It.IsAny<FileStore>(), It.IsAny<IReadOnlyCollection<Thing>>()), Times.Once);

await this.viewModel.DeleteFile();
this.sessionService.Verify(x => x.DeleteThings(It.IsAny<FileStore>(), It.IsAny<IReadOnlyCollection<Thing>>()), Times.Once);
}
}
}
10 changes: 0 additions & 10 deletions COMETwebapp/Components/Common/FormButtons.razor.css

This file was deleted.

16 changes: 15 additions & 1 deletion COMETwebapp/Components/EngineeringModel/FileStore/FileForm.razor
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,21 @@ Copyright (c) 2023-2024 Starion Group S.A.

</DxFormLayout>
<div class="pt-3"></div>
<ValidationSummary />

<div class="validation-container mt-3">
<ul class="validation-errors">
<ValidationSummary/>
@if (!this.ViewModel.SelectedFileRevisions.Any())
{
<li class="validation-message">At least one file revision should exist</li>
}
@if (!string.IsNullOrWhiteSpace(this.ErrorMessage))
{
<li class="validation-message">@(this.ErrorMessage)</li>
}
</ul>
</div>

<div class="dxbl-grid-edit-form-buttons">
<DxButton Id="deleteFileButton"
Click="@(() => this.IsDeletePopupVisible = true)"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ namespace COMETwebapp.Components.EngineeringModel.FileStore

using System.ComponentModel.DataAnnotations;

using FluentResults;

/// <summary>
/// Support class for the <see cref="FileForm"/>
/// </summary>
Expand All @@ -47,14 +49,25 @@ public partial class FileForm : SelectedDataItemForm
/// </summary>
public bool IsDeletePopupVisible { get; private set; }

/// <summary>
/// Gets the error message from the <see cref="IFileHandlerViewModel.CreateOrEditFile"/>, if any
/// </summary>
public string ErrorMessage { get; private set; } = string.Empty;

/// <summary>
/// Method that is executed when there is a valid submit
/// </summary>
/// <returns>A <see cref="Task"/></returns>
protected override async Task OnValidSubmit()
{
await this.ViewModel.CreateOrEditFile(this.ShouldCreate);
await base.OnValidSubmit();
this.ErrorMessage = string.Empty;
var result = await this.ViewModel.CreateOrEditFile(this.ShouldCreate);
this.ErrorMessage = string.Join(", ", result.Reasons.OfType<IExceptionalError>().Select(x => x.Exception.Message));

if (string.IsNullOrWhiteSpace(this.ErrorMessage))
{
await base.OnValidSubmit();
}
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ namespace COMETwebapp.ViewModels.Components.EngineeringModel.FileStore.FileHandl

using COMETwebapp.ViewModels.Components.EngineeringModel.FileStore.FileRevisionHandler;

using FluentResults;

using Microsoft.AspNetCore.Components;

/// <summary>
Expand Down Expand Up @@ -149,8 +151,8 @@ public async Task MoveFile(File file, Folder targetFolder)
/// Creates or edits a file
/// </summary>
/// <param name="shouldCreate">The condition to check if the file should be created</param>
/// <returns>A <see cref="Task" /></returns>
public async Task CreateOrEditFile(bool shouldCreate)
/// <returns>A <see cref="Task"/> containing a <see cref="Result"/></returns>
public async Task<Result> CreateOrEditFile(bool shouldCreate)
{
this.IsLoading = true;
this.logger.LogInformation("Creating or editing file");
Expand Down Expand Up @@ -193,11 +195,19 @@ public async Task CreateOrEditFile(bool shouldCreate)
}

thingsToUpdate.Add(this.CurrentThing);
var result = await this.SessionService.CreateOrUpdateThings(fileStoreClone, thingsToUpdate, newFileRevisions.Select(x => x.LocalPath).ToList());

await this.SessionService.CreateOrUpdateThings(fileStoreClone, thingsToUpdate, newFileRevisions.Select(x => x.LocalPath).ToList());
if (result.IsSuccess)
{
this.logger.LogInformation("File with iid {iid} updated successfully", this.CurrentThing.Iid);
}
else
{
this.logger.LogWarning("File could not be created. {warning}", string.Join(", ", result.Reasons.Select(x => x.Message)));
}

this.logger.LogInformation("File with iid {iid} updated successfully", this.CurrentThing.Iid);
this.IsLoading = false;
return result;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ namespace COMETwebapp.ViewModels.Components.EngineeringModel.FileStore.FileHandl

using COMETwebapp.ViewModels.Components.EngineeringModel.FileStore.FileRevisionHandler;

using FluentResults;

/// <summary>
/// View model used to manage the files in Filestores
/// </summary>
Expand Down Expand Up @@ -96,8 +98,8 @@ public interface IFileHandlerViewModel : IApplicationBaseViewModel
/// Creates a file into a target folder
/// </summary>
/// <param name="shouldCreate"></param>
/// <returns>A <see cref="Task"/></returns>
Task CreateOrEditFile(bool shouldCreate);
/// <returns>A <see cref="Task"/> containing a <see cref="Result"/></returns>
Task<Result> CreateOrEditFile(bool shouldCreate);

/// <summary>
/// Deletes the current file
Expand Down
Loading

0 comments on commit f4341b2

Please sign in to comment.