Skip to content

Commit

Permalink
refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
mizrael committed Jan 29, 2024
1 parent cc7b245 commit 6131229
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 64 deletions.
2 changes: 1 addition & 1 deletion src/PAModelTests/DataSourceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
using Microsoft.PowerPlatform.Formulas.Tools;
using Microsoft.PowerPlatform.Formulas.Tools.Extensions;
using Microsoft.PowerPlatform.Formulas.Tools.IO;
using Microsoft.PowerPlatform.PowerApps.Persistence.MsApp;
using Microsoft.PowerPlatform.PowerApps.Persistence;

namespace PAModelTests;

Expand Down
36 changes: 19 additions & 17 deletions src/Persistence.Tests/MsApp/MsappArchiveTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,31 @@
// Licensed under the MIT License.

using System.IO.Compression;
using Microsoft.PowerPlatform.PowerApps.Persistence.MsApp;
using Microsoft.PowerPlatform.PowerApps.Persistence;
using Microsoft.PowerPlatform.PowerApps.Persistence.Extensions;
using Microsoft.PowerPlatform.PowerApps.Persistence.Utils;
using Microsoft.PowerPlatform.PowerApps.Persistence.Yaml;

namespace Persistence.Tests.MsApp;

[TestClass]
public class MsappArchiveTests
{
[DataRow(new string[] { "abc.txt" }, MsappArchive.ResourcesDirectory, 0)]
[DataRow(new string[] { "abc.txt", @$"{MsappArchive.ResourcesDirectory}\abc.txt" }, MsappArchive.ResourcesDirectory, 1)]
[DataRow(new string[] { "abc.txt", @$"{MsappArchive.ResourcesDirectory}\abc.txt" }, $@" \{MsappArchive.ResourcesDirectory}/", 1)]
[DataRow(new string[] { "abc.txt", @$"{MsappArchive.ResourcesDirectory}/abc.txt" }, $@" {MsappArchive.ResourcesDirectory}/", 1)]
[DataRow(new string[] { "abc.txt", @$"{MsappArchive.ResourcesDirectory}/abc.txt" }, $@" {MsappArchive.ResourcesDirectory}\", 1)]
[DataRow(new string[] { "abc.txt", @$"{MsappArchive.ResourcesDirectory}\abc.txt" }, "NotFound", 0)]
[DataRow(new string[] { "abc.txt" }, MsappArchive.Directories.ResourcesDirectory, 0)]
[DataRow(new string[] { "abc.txt", @$"{MsappArchive.Directories.ResourcesDirectory}\abc.txt" }, MsappArchive.Directories.ResourcesDirectory, 1)]
[DataRow(new string[] { "abc.txt", @$"{MsappArchive.Directories.ResourcesDirectory}\abc.txt" }, $@" \{MsappArchive.Directories.ResourcesDirectory}/", 1)]
[DataRow(new string[] { "abc.txt", @$"{MsappArchive.Directories.ResourcesDirectory}/abc.txt" }, $@" {MsappArchive.Directories.ResourcesDirectory}/", 1)]
[DataRow(new string[] { "abc.txt", @$"{MsappArchive.Directories.ResourcesDirectory}/abc.txt" }, $@" {MsappArchive.Directories.ResourcesDirectory}\", 1)]
[DataRow(new string[] { "abc.txt", @$"{MsappArchive.Directories.ResourcesDirectory}\abc.txt" }, "NotFound", 0)]
[DataRow(new string[] {"abc.txt",
@$"{MsappArchive.ResourcesDirectory}\abc.txt",
@$"ReSoUrCeS/efg.txt"}, MsappArchive.ResourcesDirectory, 2)]
@$"{MsappArchive.Directories.ResourcesDirectory}\abc.txt",
@$"ReSoUrCeS/efg.txt"}, MsappArchive.Directories.ResourcesDirectory, 2)]
[DataRow(new string[] {"abc.txt",
@$"{MsappArchive.ResourcesDirectory}\abc.txt",
@$"{MsappArchive.ResourcesDirectory}/efg.txt"}, "RESOURCES", 2)]
@$"{MsappArchive.Directories.ResourcesDirectory}\abc.txt",
@$"{MsappArchive.Directories.ResourcesDirectory}/efg.txt"}, "RESOURCES", 2)]
[DataRow(new string[] {"abc.txt",
@$"{MsappArchive.ResourcesDirectory}New\abc.txt",
@$"{MsappArchive.ResourcesDirectory}/efg.txt"}, MsappArchive.ResourcesDirectory, 1)]
@$"{MsappArchive.Directories.ResourcesDirectory}New\abc.txt",
@$"{MsappArchive.Directories.ResourcesDirectory}/efg.txt"}, MsappArchive.Directories.ResourcesDirectory, 1)]
[TestMethod]
public void GetDirectoryEntriesTests(string[] entries, string directoryName, int expectedDirectoryCount)
{
Expand All @@ -46,9 +48,9 @@ public void GetDirectoryEntriesTests(string[] entries, string directoryName, int
}

[DataRow(new string[] { "abc.txt" })]
[DataRow(new string[] { "abc.txt", @$"{MsappArchive.ResourcesDirectory}\abc.txt" })]
[DataRow(new string[] { "abc.txt", @$"{MsappArchive.ResourcesDirectory}\DEF.txt" })]
[DataRow(new string[] { "abc.txt", @$"{MsappArchive.ResourcesDirectory}\DEF.txt", @"\start-with-slash\test.json" })]
[DataRow(new string[] { "abc.txt", @$"{MsappArchive.Directories.ResourcesDirectory}\abc.txt" })]
[DataRow(new string[] { "abc.txt", @$"{MsappArchive.Directories.ResourcesDirectory}\DEF.txt" })]
[DataRow(new string[] { "abc.txt", @$"{MsappArchive.Directories.ResourcesDirectory}\DEF.txt", @"\start-with-slash\test.json" })]
[TestMethod]
public void AddEntryTests(string[] entries)
{
Expand All @@ -64,7 +66,7 @@ public void AddEntryTests(string[] entries)
msappArchive.CanonicalEntries.Count.Should().Be(entries.Length);
foreach (var entry in entries)
{
msappArchive.CanonicalEntries.ContainsKey(MsappArchive.NormalizePath(entry)).Should().BeTrue();
msappArchive.CanonicalEntries.ContainsKey(FileUtils.NormalizePath(entry)).Should().BeTrue();
}

// Get the required entry should throw if it doesn't exist
Expand Down
5 changes: 2 additions & 3 deletions src/Persistence.Tests/Yaml/DeserializerInvalidTests.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.PowerPlatform.PowerApps.Persistence.MsApp;
using Microsoft.PowerPlatform.PowerApps.Persistence.Yaml;

namespace Persistence.Tests.Yaml;
Expand All @@ -10,12 +9,12 @@ namespace Persistence.Tests.Yaml;
public class DeserializerInvalidTests
{
[TestMethod]
public void Deserialize_ShouldFail()
public void Deserialize_ShouldFailWhenYamlIsInvalid()
{
// Arrange
var deserializer = YamlSerializationFactory.CreateDeserializer();

var files = Directory.GetFiles(@"_TestData/InvalidYaml", $"*{MsappArchive.YamlFxFileExtension}", SearchOption.AllDirectories);
var files = Directory.GetFiles(@"_TestData/InvalidYaml", $"*{YamlUtils.YamlFxFileExtension}", SearchOption.AllDirectories);
// Uncomment to test single file
// var files = new string[] { @"_TestData/InvalidYaml/InvalidName.fx.yaml" };

Expand Down
18 changes: 18 additions & 0 deletions src/Persistence.Tests/Yaml/ValidSerializerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,22 @@ public void Serialize_ShouldCreateValidYamlForCustomControl()
var sut = serializer.Serialize(graph);
sut.Should().Be($"Control: http://localhost/#customcontrol{Environment.NewLine}Name: CustomControl1{Environment.NewLine}Properties:{Environment.NewLine} Text: I am a custom control{Environment.NewLine}");
}

[TestMethod]
public void Serialize_ShouldNotIncludeEnditorState()
{
var graph = new CustomControl("CustomControl1")
{
ControlUri = "http://localhost/#customcontrol",
EditorState = new()
{
Name = "CustomControl1",
},
};

var serializer = YamlSerializationFactory.CreateSerializer();

var sut = serializer.Serialize(graph);
sut.Should().NotContain(nameof(Control.EditorState));
}
}
26 changes: 26 additions & 0 deletions src/Persistence/Extensions/IMsappArchiveExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.IO.Compression;

namespace Microsoft.PowerPlatform.PowerApps.Persistence.Extensions;

public static class IMsappArchiveExtensions
{
/// <summary>
/// Returns the entry in the archive with the given name or throws a <see cref="FileNotFoundException"/> if it does not exist.
/// </summary>
/// <param name="archive">the <see cref="IMsappArchive"/> instance.</param>
/// <param name="entryName">the name of the entry to fetch.</param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="FileNotFoundException"></exception>
public static ZipArchiveEntry GetRequiredEntry(this IMsappArchive archive, string entryName)
{
var entry = archive.GetEntry(entryName) ??
throw new FileNotFoundException($"Entry '{entryName}' not found in msapp archive.");

return entry;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

using System.IO.Compression;

namespace Microsoft.PowerPlatform.PowerApps.Persistence.MsApp;
namespace Microsoft.PowerPlatform.PowerApps.Persistence;

/// <summary>
/// base interface for MsappArchive
Expand All @@ -26,6 +26,14 @@ public interface IMsappArchive
/// <returns>the entry or null when not found.</returns>
ZipArchiveEntry? GetEntry(string entryName);

/// <summary>
/// Returns all entries in the archive that are in the given directory.
/// </summary>
/// <param name="directoryName"></param>
/// <param name="extension"></param>
/// <returns></returns>
IEnumerable<ZipArchiveEntry> GetDirectoryEntries(string directoryName, string? extension = null);

/// <summary>
/// Provides access to the underlying zip archive.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
using System.Text.Json;
using Microsoft.Extensions.Logging;
using Microsoft.PowerPlatform.PowerApps.Persistence.Models;
using Microsoft.PowerPlatform.PowerApps.Persistence.Utils;
using Microsoft.PowerPlatform.PowerApps.Persistence.Yaml;
using YamlDotNet.Serialization;

namespace Microsoft.PowerPlatform.PowerApps.Persistence.MsApp;
namespace Microsoft.PowerPlatform.PowerApps.Persistence;


/// <summary>
/// Represents a .msapp file.
Expand All @@ -17,15 +20,6 @@ public class MsappArchive : IMsappArchive, IDisposable
{
#region Constants

public const string SrcDirectory = "Src";
public const string ControlsDirectory = "Controls";
public const string ComponentsDirectory = "Components";
public const string AppTestDirectory = "AppTests";
public const string ReferencesDirectory = "References";
public const string ResourcesDirectory = "Resources";

public const string YamlFileExtension = ".yaml";
public const string YamlFxFileExtension = ".fx.yaml";
public const string JsonFileExtension = ".json";

#endregion
Expand Down Expand Up @@ -106,7 +100,7 @@ public MsappArchive(Stream stream, ZipArchiveMode mode, bool leaveOpen, Encoding

foreach (var entry in ZipArchive.Entries)
{
if (!canonicalEntries.TryAdd(NormalizePath(entry.FullName), entry))
if (!canonicalEntries.TryAdd(FileUtils.NormalizePath(entry.FullName), entry))
_logger?.LogInformation($"Duplicate entry found in archive: {entry.FullName}");
}

Expand Down Expand Up @@ -143,17 +137,12 @@ public MsappArchive(Stream stream, ZipArchiveMode mode, bool leaveOpen, Encoding

#region Methods

/// <summary>
/// Returns all entries in the archive that are in the given directory.
/// </summary>
/// <param name="directoryName"></param>
/// <param name="extension"></param>
/// <returns></returns>
/// <inheritdoc/>
public IEnumerable<ZipArchiveEntry> GetDirectoryEntries(string directoryName, string? extension = null)
{
_ = directoryName ?? throw new ArgumentNullException(nameof(directoryName));

directoryName = NormalizePath(directoryName);
directoryName = FileUtils.NormalizePath(directoryName);

foreach (var entry in CanonicalEntries)
{
Expand All @@ -173,35 +162,20 @@ public IEnumerable<ZipArchiveEntry> GetDirectoryEntries(string directoryName, st
if (string.IsNullOrWhiteSpace(entryName))
return null;

entryName = NormalizePath(entryName);
entryName = FileUtils.NormalizePath(entryName);
if (CanonicalEntries.TryGetValue(entryName, out var entry))
return entry;

return null;
}

/// <summary>
/// Returns the entry in the archive with the given name or throws if it does not exist.
/// </summary>
/// <param name="entryName"></param>
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
/// <exception cref="FileNotFoundException"></exception>
public ZipArchiveEntry GetRequiredEntry(string entryName)
{
var entry = GetEntry(entryName) ??
throw new FileNotFoundException($"Entry '{entryName}' not found in msapp archive.");

return entry;
}

/// <inheritdoc/>
public ZipArchiveEntry CreateEntry(string entryName)
{
if (string.IsNullOrWhiteSpace(entryName))
throw new ArgumentException("Entry name cannot be null or whitespace.", nameof(entryName));

var canonicalEntryName = NormalizePath(entryName);
var canonicalEntryName = FileUtils.NormalizePath(entryName);
if (_canonicalEntries.Value.ContainsKey(canonicalEntryName))
throw new InvalidOperationException($"Entry {entryName} already exists in the archive.");

Expand All @@ -211,11 +185,6 @@ public ZipArchiveEntry CreateEntry(string entryName)
return entry;
}

public static string NormalizePath(string path)
{
return path.Trim().Replace('\\', '/').Trim('/').ToLowerInvariant();
}

#endregion

#region Private Methods
Expand All @@ -225,7 +194,7 @@ private List<Screen> LoadScreens()
_logger?.LogInformation("Loading top level screens from Yaml.");

var screens = new Dictionary<string, Screen>();
foreach (var yamlEntry in GetDirectoryEntries(Path.Combine(SrcDirectory, ControlsDirectory), YamlFileExtension))
foreach (var yamlEntry in GetDirectoryEntries(Path.Combine(Directories.SrcDirectory, Directories.ControlsDirectory), YamlUtils.YamlFileExtension))
{
using var textReader = new StreamReader(yamlEntry.Open());
try
Expand All @@ -241,7 +210,7 @@ private List<Screen> LoadScreens()

_logger?.LogInformation("Loading top level controls editor state.");
var controlEditorStates = new Dictionary<string, ControlEditorState>();
foreach (var editorStateEntry in GetDirectoryEntries(Path.Combine(ControlsDirectory), JsonFileExtension))
foreach (var editorStateEntry in GetDirectoryEntries(Path.Combine(Directories.ControlsDirectory), JsonFileExtension))
{
try
{
Expand Down Expand Up @@ -317,4 +286,15 @@ public void Dispose()
}

#endregion

public static class Directories
{
public const string SrcDirectory = "Src";
public const string ControlsDirectory = "Controls";
public const string ComponentsDirectory = "Components";
public const string AppTestDirectory = "AppTests";
public const string ReferencesDirectory = "References";
public const string ResourcesDirectory = "Resources";
}
}

17 changes: 17 additions & 0 deletions src/Persistence/Utils/FileUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace Microsoft.PowerPlatform.PowerApps.Persistence.Utils;

public static class FileUtils
{
/// <summary>
/// Converts backslashes to forward slashes, removes trailing slashes, and converts to lowercase.
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public static string NormalizePath(string path)
{
return path.Trim().Replace('\\', '/').Trim('/').ToLowerInvariant();
}
}
28 changes: 28 additions & 0 deletions src/Persistence/Yaml/YamlUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

namespace Microsoft.PowerPlatform.PowerApps.Persistence.Yaml;

public static class YamlUtils
{
#region Constants

public const string YamlFileExtension = ".yaml";
public const string YmlFileExtension = ".yml";
public const string YamlFxFileExtension = ".fx.yaml";

#endregion

/// <summary>
/// Checks if the given path is a yaml file.
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
public static bool IsYamlFile(string path)
{
return
Path.GetExtension(path).Equals(YamlFileExtension, StringComparison.OrdinalIgnoreCase) ||
Path.GetExtension(path).Equals(YmlFileExtension, StringComparison.OrdinalIgnoreCase);
}
}

0 comments on commit 6131229

Please sign in to comment.