diff --git a/documentation/docs/developer-guide/api/readme.md b/documentation/docs/developer-guide/api/readme.md
index 49ab8d9dab..6ff5a7e660 100644
--- a/documentation/docs/developer-guide/api/readme.md
+++ b/documentation/docs/developer-guide/api/readme.md
@@ -9,7 +9,7 @@ This document is auto generated
| Path | Type | Description |
|---------------------------------------------------|-------|---------------------------------------------------------------------------------|
-| __/api/account/status__ | GET | Check the account status of the current logged in user |
+| __/api/account/status__ | GET | Check the account status of the current logged-in user |
| __/account/login__ | GET | Login form page (HTML) |
| __/api/account/login__ | POST | Login the current HttpContext in |
| __/api/account/logout__ | POST | Logout the current HttpContext out |
@@ -28,7 +28,7 @@ This document is auto generated
| __/api/cache/list__ | GET | Get Database Cache (only the cache) |
| __/api/remove-cache__ | GET | Delete Database Cache (only the cache) |
| __/api/remove-cache__ | POST | Delete Database Cache (only the cache) |
-| __/api/delete__ | DELETE| Remove files from the disk, but the file must contain the !delete! (TrashKeyw...|
+| __/api/delete__ | DELETE| Remove files from the disk, but the file must contain the !delete!(TrashKeywo...|
| _Parameters: f (subPaths, separated by dot comma), collections (true is to update files with the same name before _ |
| _ the extenstion) _ |
| __/api/desktop-editor/open__ | POST | Open a file in the default editor or a specific editor on the desktop |
@@ -101,7 +101,7 @@ This document is auto generated
| __/api/suggest/inflate__ | GET | To fill the cache with the data (only if cache is not already filled) |
| __/api/synchronize__ | POST | Faster API to Check if directory is changed (not recursive) |
| __/api/synchronize__ | GET | Faster API to Check if directory is changed (not recursive) |
-| __/api/thumbnail/small/\{f\}__ | GET | Get thumbnail for index pages (300 px or 150px or 1000px (based on whats there))|
+| __/api/thumbnail/small/\{f\}__ | GET | Get thumbnail for index pages (300 px or 150px or 1000px (based on what's the...|
| __/api/thumbnail/list-sizes/\{f\}__ | GET | Get overview of what exists by name |
| __/api/thumbnail/\{f\}__ | GET | Get thumbnail with fallback to original source image.Return source image when...|
| _Parameters: f (one single fileHash (NOT path)), filePath (fallback FilePath), isSingleItem (true = load original), _ |
diff --git a/documentation/docs/developer-guide/testing/_category_.json b/documentation/docs/developer-guide/testing/_category_.json
new file mode 100644
index 0000000000..e3317f05f3
--- /dev/null
+++ b/documentation/docs/developer-guide/testing/_category_.json
@@ -0,0 +1,8 @@
+{
+ "label": "Testing",
+ "position": 12,
+ "link": {
+ "type": "generated-index",
+ "description": "Overview of Testing in the Platform"
+ }
+}
diff --git a/documentation/docs/developer-guide/testing/architecture-test.md b/documentation/docs/developer-guide/testing/architecture-test.md
new file mode 100644
index 0000000000..3537c0bbc8
--- /dev/null
+++ b/documentation/docs/developer-guide/testing/architecture-test.md
@@ -0,0 +1,82 @@
+# Architecture Test Documentation
+
+## Overview
+
+The `ArchitectureTest.cs` file contains automated tests to ensure that the project's architecture
+adheres to specific guidelines. These guidelines are based on the Sitecore Helix principles, which
+help maintain a clean and scalable architecture over time.
+
+## Key Concepts
+
+### Fitness Functions
+
+Fitness functions are tests that confirm whether a particular characteristic of the solution's
+architecture is maintained over time. These tests help ensure that the architecture remains
+consistent and adheres to predefined rules.
+
+### Helix Principles
+
+The Helix principles define a set of guidelines for structuring projects within a solution. The key
+rules enforced by these tests are:
+
+- **Foundation projects** should only reference other Foundation projects.
+- **Feature projects** should only reference Foundation projects.
+- **Project projects** should only reference Foundation or Feature projects.
+
+## Test Class: `TheSolutionShould`
+
+### Purpose
+
+The `TheSolutionShould` class contains tests that validate the project reference structure within
+the solution. It ensures that the inter-project references comply with the Helix principles.
+
+### Key Methods
+
+#### `ContainNoHelixInvalidProjectReferences`
+
+This test method verifies that there are no invalid project references within the solution. It
+performs the following steps:
+
+1. **Arrange**: Retrieves a list of projects and their references.
+2. **Act**: Reviews the project list for invalid references.
+3. **Assert**: Checks if any invalid references are found and asserts that there should be none.
+
+#### `GetProjectsWithReferences`
+
+This method retrieves a dictionary of projects and their references from the solution file.
+
+#### `GetSolutionFilePath`
+
+This method locates the solution file path based on the base directory.
+
+#### `GetProjectsFromSolutionFileContents`
+
+This method extracts project references from the solution file contents using a regular expression.
+
+#### `GetReferencedProjects`
+
+This method retrieves the referenced projects from a given project file.
+
+#### `GetInvalidReferences`
+
+This method identifies invalid references for a given project based on the specified invalid layers.
+
+#### `AssertLayerReferences`
+
+This method asserts that there are no invalid references for a given layer.
+
+## Known Issues
+
+- The `starsky.feature.trash` project references the `starsky.feature.metaupdate` project, which is
+ a known issue and should be refactored in the future.
+
+## References
+
+- [Microservices and Evolutionary Architecture](https://www.thoughtworks.com/insights/blog/microservices-evolutionary-architecture)
+- [Building Evolutionary Architectures](http://shop.oreilly.com/product/0636920080237.do)
+- [Sitecore Helix Documentation](http://helix.sitecore.net/introduction/index.html)
+
+---
+
+This documentation provides an overview of the purpose and functionality of the
+`ArchitectureTest.cs` file, along with explanations of key concepts and methods.
\ No newline at end of file
diff --git a/documentation/docs/developer-guide/testing/index.md b/documentation/docs/developer-guide/testing/index.md
new file mode 100644
index 0000000000..438c7872cc
--- /dev/null
+++ b/documentation/docs/developer-guide/testing/index.md
@@ -0,0 +1,38 @@
+# Unit tests
+
+In this project, we use unit tests to verify the correctness of individual components
+in.
+
+- Language: The unit tests are written in C#.
+- Framework: The tests use a testing framework such as MSTest.
+- Structure: The tests are organized to cover various layers and components of the application,
+ensuring comprehensive coverage.
+- Test Cases: Each test case is designed to validate specific functionality, edge cases, and error
+conditions.
+- Automation: The tests can be run automatically as part of a continuous integration (CI) pipeline to
+ensure that new changes do not introduce regressions.
+
+## Here are some best practices for writing unit tests:
+
+1. **Write Independent Tests**: Each test should be independent and not rely on the outcome of other
+ tests.
+2. **Use Descriptive Names**: Test method names should clearly describe what is being tested and the
+ expected outcome.
+3. **Follow the AAA Pattern**: Arrange, Act, Assert. First, set up the test data and environment (
+ Arrange), then execute the code being tested (Act), and finally verify the result (Assert).
+4. **Test One Thing at a Time**: Each test should focus on a single piece of functionality or
+ behavior.
+5. **Keep Tests Small and Focused**: Small, focused tests are easier to understand, maintain, and
+ debug.
+6. **Use Mocks and Stubs**: We manually mock services, at the moment we don't use a library for this.
+7. **Ensure Tests are Repeatable**: Tests should produce the same results every time they are run,
+ regardless of the environment or order of execution.
+8. **Run Tests Frequently**: Integrate tests into the development process and run them frequently to
+ catch issues early.
+9. **Test Edge Cases and Error Conditions**: Ensure that tests cover edge cases and potential error
+ conditions.
+10. **Maintain Test Code Quality**: Apply the same standards to test code as you do to production
+ code, including readability, maintainability, and adherence to coding standards.
+
+By following these best practices, you can create effective and reliable unit tests that help
+maintain the quality of your codebase.
\ No newline at end of file
diff --git a/documentation/scripts/prestart.js b/documentation/scripts/prestart.js
index cf93779af8..a43caf5c30 100644
--- a/documentation/scripts/prestart.js
+++ b/documentation/scripts/prestart.js
@@ -2,52 +2,52 @@
const fs = require("fs");
const path = require("path");
-const { parseAndWrite } = require("./openapi");
+const {parseAndWrite} = require("./openapi");
const parentDirectory = path.join(__dirname, "..", "..");
const docsDirectory = path.join(__dirname, "..", "docs"); // /git/starsky/documentation/docs
function copyFileSync(input, to) {
- const inputPath = path.join(parentDirectory, input);
+ const inputPath = path.join(parentDirectory, input);
- const relativeParentInputFolder = path.dirname(to);
+ const relativeParentInputFolder = path.dirname(to);
- if (relativeParentInputFolder != ".") {
- const relativeParentInputFolderPath = path.join(
- docsDirectory,
- relativeParentInputFolder
- );
- fs.mkdirSync(relativeParentInputFolderPath, { recursive: true });
- }
+ if (relativeParentInputFolder !== ".") {
+ const relativeParentInputFolderPath = path.join(
+ docsDirectory,
+ relativeParentInputFolder
+ );
+ fs.mkdirSync(relativeParentInputFolderPath, {recursive: true});
+ }
- fs.copyFileSync(inputPath, path.join(docsDirectory, to));
+ fs.copyFileSync(inputPath, path.join(docsDirectory, to));
}
function touchSync(to) {
- const filename = path.join(docsDirectory, to);
-
- const relativeParentInputFolder = path.dirname(to);
-
- if (relativeParentInputFolder !== ".") {
- const relativeParentInputFolderPath = path.join(
- docsDirectory,
- relativeParentInputFolder
- );
- fs.mkdirSync(relativeParentInputFolderPath, { recursive: true });
- }
-
- const time = new Date();
- try {
- fs.utimesSync(filename, time, time);
- } catch (e) {
- let fd = fs.openSync(filename, "a");
- fs.closeSync(fd);
- }
+ const filename = path.join(docsDirectory, to);
+
+ const relativeParentInputFolder = path.dirname(to);
+
+ if (relativeParentInputFolder !== ".") {
+ const relativeParentInputFolderPath = path.join(
+ docsDirectory,
+ relativeParentInputFolder
+ );
+ fs.mkdirSync(relativeParentInputFolderPath, {recursive: true});
+ }
+
+ const time = new Date();
+ try {
+ fs.utimesSync(filename, time, time);
+ } catch (e) {
+ let fd = fs.openSync(filename, "a");
+ fs.closeSync(fd);
+ }
}
function writeFile(to, content) {
- const filename = path.join(docsDirectory, to);
- fs.writeFileSync(filename, content);
+ const filename = path.join(docsDirectory, to);
+ fs.writeFileSync(filename, content);
}
copyFileSync("history.md", "advanced-options/history.md");
@@ -58,67 +58,67 @@ touchSync("advanced-options/__do_not_edit_history_md");
touchSync("advanced-options/starsky/__do_not_edit_this__folder");
writeFile(
- "advanced-options/starsky/_category_.json",
- JSON.stringify({
- label: "Applications Guide",
- position: 2,
- link: {
- type: "generated-index",
- description: "CLI tools/ Applications",
- },
- })
+ "advanced-options/starsky/_category_.json",
+ JSON.stringify({
+ label: "Applications Guide",
+ position: 2,
+ link: {
+ type: "generated-index",
+ description: "CLI tools/ Applications",
+ },
+ })
);
copyFileSync("starsky/readme.md", "advanced-options/starsky/readme.md");
copyFileSync("starsky/starsky/readme.md", "advanced-options/starsky/starsky/readme.md");
copyFileSync(
- "starsky/starsky/readme-docker-hub.md",
- "advanced-options/starsky/starsky/readme-docker-hub.md"
+ "starsky/starsky/readme-docker-hub.md",
+ "advanced-options/starsky/starsky/readme-docker-hub.md"
);
copyFileSync(
- "starsky/starsky/readme-docker-development.md",
- "advanced-options/starsky/starsky/readme-docker-development.md"
+ "starsky/starsky/readme-docker-development.md",
+ "advanced-options/starsky/starsky/readme-docker-development.md"
);
copyFileSync(
- "starsky/starsky/clientapp/readme.md",
- "advanced-options/starsky/starsky/clientapp/readme.md"
+ "starsky/starsky/clientapp/readme.md",
+ "advanced-options/starsky/starsky/clientapp/readme.md"
);
copyFileSync(
- "starsky/starskyimportercli/readme.md",
- "advanced-options/starsky/starskyimportercli/readme.md"
+ "starsky/starskyimportercli/readme.md",
+ "advanced-options/starsky/starskyimportercli/readme.md"
);
copyFileSync(
- "starsky/starskygeocli/readme.md",
- "advanced-options/starsky/starskygeocli/readme.md"
+ "starsky/starskygeocli/readme.md",
+ "advanced-options/starsky/starskygeocli/readme.md"
);
copyFileSync(
- "starsky/starskywebhtmlcli/readme.md",
- "advanced-options/starsky/starskywebhtmlcli/readme.md"
+ "starsky/starskywebhtmlcli/readme.md",
+ "advanced-options/starsky/starskywebhtmlcli/readme.md"
);
copyFileSync(
- "starsky/starskywebftpcli/readme.md",
- "advanced-options/starsky/starskywebftpcli/readme.md"
+ "starsky/starskywebftpcli/readme.md",
+ "advanced-options/starsky/starskywebftpcli/readme.md"
);
copyFileSync(
- "starsky/starskyadmincli/readme.md",
- "advanced-options/starsky/starskyadmincli/readme.md"
+ "starsky/starskyadmincli/readme.md",
+ "advanced-options/starsky/starskyadmincli/readme.md"
);
copyFileSync(
- "starsky/starskysynchronizecli/readme.md",
- "advanced-options/starsky/starskysynchronizecli/readme.md"
+ "starsky/starskysynchronizecli/readme.md",
+ "advanced-options/starsky/starskysynchronizecli/readme.md"
);
copyFileSync(
- "starsky/starskythumbnailcli/readme.md",
- "advanced-options/starsky/starskythumbnailcli/readme.md"
+ "starsky/starskythumbnailcli/readme.md",
+ "advanced-options/starsky/starskythumbnailcli/readme.md"
);
copyFileSync(
- "starsky/starskybusinesslogic/readme.md",
- "advanced-options/starsky/starskybusinesslogic/readme.md"
+ "starsky/starskybusinesslogic/readme.md",
+ "advanced-options/starsky/starskybusinesslogic/readme.md"
);
copyFileSync("starskydesktop/readme.md", "advanced-options/starskydesktop/readme.md");
@@ -126,13 +126,13 @@ copyFileSync("starskydesktop/readme.md", "advanced-options/starskydesktop/readme
touchSync("advanced-options/starskydesktop/__do_not_edit_this__folder");
copyFileSync(
- "starskydesktop/docs-assets/starskyapp-mac-gatekeeper.jpg",
- "advanced-options/starskydesktop/docs-assets/starskyapp-mac-gatekeeper.jpg"
+ "starskydesktop/docs-assets/starskyapp-mac-gatekeeper.jpg",
+ "advanced-options/starskydesktop/docs-assets/starskyapp-mac-gatekeeper.jpg"
);
copyFileSync(
- "starskydesktop/docs-assets/starskyapp-remote-options-v040.jpg",
- "advanced-options/starskydesktop/docs-assets/starskyapp-remote-options-v040.jpg"
+ "starskydesktop/docs-assets/starskyapp-remote-options-v040.jpg",
+ "advanced-options/starskydesktop/docs-assets/starskyapp-remote-options-v040.jpg"
);
copyFileSync("starsky/starskytest/readme.md", "advanced-options/starsky/starskytest/readme.md");
@@ -142,23 +142,23 @@ copyFileSync("starsky-tools/readme.md", "advanced-options/starsky-tools/readme.m
touchSync("advanced-options/starsky-tools/__do_not_edit_this__folder");
copyFileSync(
- "starsky-tools/build-tools/readme.md",
- "advanced-options/starsky-tools/build-tools/readme.md"
+ "starsky-tools/build-tools/readme.md",
+ "advanced-options/starsky-tools/build-tools/readme.md"
);
copyFileSync(
- "starsky-tools/dropbox-import/readme.md",
- "advanced-options/starsky-tools/dropbox-import/readme.md"
+ "starsky-tools/dropbox-import/readme.md",
+ "advanced-options/starsky-tools/dropbox-import/readme.md"
);
copyFileSync(
- "starsky-tools/end2end/readme.md",
- "advanced-options/starsky-tools/end2end/readme.md"
+ "starsky-tools/end2end/readme.md",
+ "advanced-options/starsky-tools/end2end/readme.md"
);
copyFileSync(
- "starsky-tools/localtunnel/readme.md",
- "advanced-options/starsky-tools/localtunnel/readme.md"
+ "starsky-tools/localtunnel/readme.md",
+ "advanced-options/starsky-tools/localtunnel/readme.md"
);
copyFileSync("starsky-tools/mail/readme.md", "advanced-options/starsky-tools/mail/readme.md");
@@ -167,18 +167,18 @@ copyFileSync("starsky-tools/mock/readme.md", "advanced-options/starsky-tools/moc
copyFileSync(
- "starsky-tools/thumbnail/readme.md",
- "advanced-options/starsky-tools/thumbnail/readme.md"
+ "starsky-tools/thumbnail/readme.md",
+ "advanced-options/starsky-tools/thumbnail/readme.md"
);
copyFileSync(
- "starsky-tools/release-tools/readme.md",
- "advanced-options/starsky-tools/release-tools/readme.md"
+ "starsky-tools/release-tools/readme.md",
+ "advanced-options/starsky-tools/release-tools/readme.md"
);
copyFileSync(
- "starsky-tools/slack-notification/readme.md",
- "advanced-options/starsky-tools/slack-notification/readme.md"
+ "starsky-tools/slack-notification/readme.md",
+ "advanced-options/starsky-tools/slack-notification/readme.md"
);
copyFileSync("starsky-tools/sync/readme.md", "advanced-options/starsky-tools/sync/readme.md");
@@ -187,37 +187,36 @@ copyFileSync("starsky-tools/sync/readme.md", "advanced-options/starsky-tools/syn
touchSync("developer-guide/contributing/__do_not_edit_this__folder");
writeFile(
- "developer-guide/contributing/_category_.json",
- JSON.stringify({
- label: "Contributing",
- position: 8,
- link: {
- type: "generated-index",
- description: "contributing instructions"
- },
- })
+ "developer-guide/contributing/_category_.json",
+ JSON.stringify({
+ label: "Contributing",
+ position: 8,
+ link: {
+ type: "generated-index",
+ description: "contributing instructions"
+ },
+ })
);
copyFileSync(
- "CONTRIBUTING.md",
- "developer-guide/contributing/CONTRIBUTING.md"
+ "CONTRIBUTING.md",
+ "developer-guide/contributing/CONTRIBUTING.md"
);
copyFileSync(
- "SECURITY.md",
- "developer-guide/contributing/SECURITY.md"
+ "SECURITY.md",
+ "developer-guide/contributing/SECURITY.md"
);
copyFileSync(
- "PULL_REQUEST_TEMPLATE.md",
- "developer-guide/contributing/PULL_REQUEST_TEMPLATE.md"
+ "PULL_REQUEST_TEMPLATE.md",
+ "developer-guide/contributing/PULL_REQUEST_TEMPLATE.md"
);
copyFileSync(
- "HACKING.md",
- "developer-guide/contributing/HACKING.md"
+ "HACKING.md",
+ "developer-guide/contributing/HACKING.md"
);
-
parseAndWrite();
diff --git a/starsky/starskytest/Architecture/ArchitectureTest.cs b/starsky/starskytest/Architecture/ArchitectureTest.cs
new file mode 100644
index 0000000000..18a7f92c07
--- /dev/null
+++ b/starsky/starskytest/Architecture/ArchitectureTest.cs
@@ -0,0 +1,250 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+using System.Xml.Linq;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using starsky.foundation.platform.Helpers;
+
+namespace starskytest.Architecture;
+
+///
+/// Fitness functions for software architecture are defined as a way of confirming that a
+/// particular
+/// characteristic of a solution's architecture that is considered important to maintain over time,
+/// is in fact still the case.
+/// Some of these can be confirmed via automated tests.
+/// This class defines tests that ensure the project reference structure as defined by Sitecore
+/// Helix principles are being maintained.
+///
+///
+/// References:
+/// https://www.thoughtworks.com/insights/blog/microservices-evolutionary-architecture
+/// http://shop.oreilly.com/product/0636920080237.do
+/// http://helix.sitecore.net/introduction/index.html
+///
+[TestClass]
+public partial class TheSolutionShould
+{
+ private const string SolutionName = "starsky.sln";
+
+ private const string Foundation = "Foundation";
+ private const string Feature = "Feature";
+ private const string Project = "Project";
+ private const string ProjectCli = "cli";
+ private const string MainProject = "starsky";
+ private const string MainProjectWeb = "starsky.project.web";
+
+ private const string TestProjectSuffix = "test";
+ private const string BuildProject = "_build";
+ private const string DocsProject = "documentation";
+
+ ///
+ /// Ensures that only appropriate inter-project references are in place within the solution.
+ /// Specifically:
+ /// - Foundation projects should only reference other Foundation projects
+ /// - Feature projects should only reference Foundation projects
+ /// - Project projects should only reference Foundation or Feature projects
+ ///
+ ///
+ /// Hat-tip for locating projects and references in solution:
+ /// https://stackoverflow.com/a/17571223/489433
+ ///
+ [TestMethod]
+ public void ContainNoHelixInvalidProjectReferences()
+ {
+ // Arrange - get list of projects in the solution, along with a list of references for each project
+ var projects = GetProjectsWithReferences();
+
+ // Act - review project list for invalid references
+ var invalidFoundationReferences = new List();
+ var invalidFeatureReferences = new List();
+ var invalidProjectReferences = new List();
+ foreach ( var project in projects )
+ {
+ if ( project.Key.Contains(Foundation, StringComparison.OrdinalIgnoreCase) )
+ {
+ GetInvalidReferences(project, new[] { Feature, Project },
+ invalidFoundationReferences);
+ continue;
+ }
+
+ if ( project.Key.Contains(Feature, StringComparison.OrdinalIgnoreCase) )
+ {
+ GetInvalidReferences(project, new[] { Feature, Project },
+ invalidFeatureReferences);
+ continue;
+ }
+
+ if ( project.Key.Contains(Project, StringComparison.OrdinalIgnoreCase) &&
+ project.Key != MainProjectWeb && project.Key != MainProject )
+ {
+ GetInvalidReferences(project, new[] { Project }, invalidProjectReferences);
+ continue;
+ }
+
+ if ( project.Key.Contains(ProjectCli, StringComparison.OrdinalIgnoreCase) ||
+ project.Key.Contains(TestProjectSuffix,
+ StringComparison.OrdinalIgnoreCase) || project.Key == BuildProject ||
+ project.Key == DocsProject || project.Key == MainProjectWeb ||
+ project.Key == MainProject )
+ {
+ continue;
+ }
+
+ throw new MissingFieldException($"Unknown project type {project}");
+ }
+
+ // Assert - check if we have any invalid refererences
+ AssertLayerReferences(invalidFoundationReferences, Foundation);
+ AssertLayerReferences(invalidFeatureReferences, Feature);
+ AssertLayerReferences(invalidProjectReferences, Project);
+ }
+
+ private static Dictionary> GetProjectsWithReferences()
+ {
+ var solutionFilePath = GetSolutionFilePath();
+ var solutionFileContents = File.ReadAllText(solutionFilePath);
+ var matches = GetProjectsFromSolutionFileContents(solutionFileContents);
+ return matches
+ .Select(x => x.Groups[2].Value)
+ .ToDictionary(GetProjectFileName,
+ p => GetReferencedProjects(solutionFilePath, p));
+ }
+
+ private static string GetSolutionFilePath()
+ {
+ var parent = Directory.GetParent(BaseDirectoryProjectHelper.BaseDirectoryProject)
+ ?.Parent?.Parent?.Parent?.Parent?.FullName;
+ if ( string.IsNullOrEmpty(parent) )
+ {
+ throw new DirectoryNotFoundException("Unable to locate solution file");
+ }
+
+ return Path.Combine(parent, SolutionName);
+ }
+
+ private static IList GetPathParts(string directory)
+ {
+ return [.. directory.Split(["\\"], StringSplitOptions.None)];
+ }
+
+ private static MatchCollection GetProjectsFromSolutionFileContents(
+ string solutionFileContents)
+ {
+ var regex = ProjectFileNameRegex();
+ return regex.Matches(solutionFileContents);
+ }
+
+ private static string GetProjectFileName(string value)
+ {
+ var result = GetPathParts(value).LastOrDefault()?.Replace(".csproj", string.Empty);
+ result ??= string.Empty;
+ return result;
+ }
+
+ private static List GetReferencedProjects(string solutionFilePath,
+ string projectFilePath)
+ {
+ var rootedProjectFilePath =
+ Path.Combine(solutionFilePath.Replace(SolutionName, string.Empty),
+ projectFilePath).Replace("\\", Path.DirectorySeparatorChar.ToString());
+ return GetReferencedProjects(rootedProjectFilePath);
+ }
+
+ private static List GetReferencedProjects(string rootedProjectFilePath)
+ {
+ var result = new List();
+
+ // Load the XML document
+ var xmlDoc = XDocument.Load(rootedProjectFilePath);
+
+ // Get all ProjectReference elements
+ var projectReferences = xmlDoc.Descendants("ProjectReference");
+
+ // Extract the Include attribute value from each ProjectReference element
+ foreach ( var reference in projectReferences )
+ {
+ var includeValue = reference.Attribute("Include")?.Value;
+ if ( !string.IsNullOrEmpty(includeValue) )
+ {
+ result.Add(includeValue);
+ }
+ }
+
+ return result;
+ }
+
+ private static void GetInvalidReferences(KeyValuePair> project,
+ string[] invalidLayers, List invalidReferences)
+ {
+ invalidReferences.AddRange(project.Value
+ .Where(x => ContainsInvalidReference(project.Key, x, invalidLayers))
+ .Select(x => $"{project.Key} references {x}"));
+ }
+
+ private static bool ContainsInvalidReference(string projectName,
+ string referencedProjectName, IEnumerable invalidLayers)
+ {
+ return invalidLayers
+ .Any(x => ProjectReferencesInvalidLayer(referencedProjectName, x) &&
+ !ReferencedProjectIsFromDedicatedTestProject(projectName,
+ referencedProjectName) &&
+ !ReferencedProjectIsPartOfSubFeature(projectName,
+ referencedProjectName));
+ }
+
+ private static bool ProjectReferencesInvalidLayer(string referencedProject,
+ string invalidLayer)
+ {
+ return referencedProject.Contains(invalidLayer,
+ StringComparison.InvariantCultureIgnoreCase);
+ }
+
+ private static bool ReferencedProjectIsFromDedicatedTestProject(string projectName,
+ string referencedProject)
+ {
+ return projectName == referencedProject + TestProjectSuffix;
+ }
+
+ private static bool ReferencedProjectIsPartOfSubFeature(string projectName,
+ string referencedProject)
+ {
+ // We have some features that are broken into more than one project. Via naming convention
+ // we should allow these to be valid.
+ // E.g. MySolution.Feature.FeatureName.Api references MySolution.Feature.FeatureName.Model
+ if ( projectName.Split('.').Length <= 3 ||
+ referencedProject.Split('.').Length <= 3 )
+ {
+ // Not a sub-feature
+ return false;
+ }
+
+ return projectName.Split('.')[2] == referencedProject.Split('.')[2];
+ }
+
+ private static void AssertLayerReferences(IReadOnlyCollection invalidReferences,
+ string layer)
+ {
+ if ( layer == "Feature" && invalidReferences.Any(p =>
+ p.Contains(
+ @"starsky.feature.trash references ..\starsky.feature.metaupdate\starsky.feature.metaupdate.csproj")) )
+ {
+ // This is a known issue, as the Trash feature references the MetaUpdate feature
+ // This should be refactored in the future
+
+ // when update also update docs!!!
+
+ return;
+ }
+
+ Assert.AreEqual(0, invalidReferences.Count,
+ $"There should be no Helix incompatible references in the {layer} layer. " +
+ $"{invalidReferences.Count} invalid reference{( invalidReferences.Count == 1 ? " was" : "s were" )} " +
+ $"found: {string.Join(", ", invalidReferences)}. Expected 0");
+ }
+
+ [GeneratedRegex("Project\\(\"\\{[\\w-]*\\}\"\\) = \"([\\w _]*.*)\", \"(.*\\.(cs|vcx|vb)proj)\"", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)]
+ private static partial Regex ProjectFileNameRegex();
+}