Skip to content

Commit

Permalink
Test algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
simpat-adam committed Jul 22, 2024
1 parent 7258861 commit 902c366
Show file tree
Hide file tree
Showing 3 changed files with 120 additions and 53 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public void Setup()
[Test]
public void It_should_calculate_dependencies()
{
var dependencies = _dependencyCalculator!.GetDependencies();
var dependencies = _dependencyCalculator!.GetDependenciesFromResourceSchema();
dependencies.Should().NotBeEmpty();
dependencies.Count.Should().Be(1);

Expand Down Expand Up @@ -194,7 +194,7 @@ public void Setup()
[Test]
public void It_should_calculate_dependencies()
{
var dependencies = _dependencyCalculator!.GetDependencies();
var dependencies = _dependencyCalculator!.GetDependenciesFromResourceSchema();
dependencies.Should().NotBeEmpty();

var expectedDependencies = JsonNode.Parse(_expectedDescriptor)!.AsArray();
Expand Down Expand Up @@ -309,7 +309,7 @@ public void Setup()
[Test]
public void It_should_calculate_dependencies()
{
var dependencies = _dependencyCalculator!.GetDependencies();
var dependencies = _dependencyCalculator!.GetDependenciesFromResourceSchema();
dependencies.Should().NotBeEmpty();

var expectedDependencies = JsonNode.Parse(_expectedDescriptor)!.AsArray();
Expand All @@ -318,6 +318,7 @@ public void It_should_calculate_dependencies()
.IgnoringCyclicReferences());
}
}

[TestFixture]
public class Given_A_Sample_ApiSchema_Missing_ProjectSchemas() : DependencyCalculatorTests
{
Expand All @@ -340,9 +341,68 @@ public void Setup()
[Test]
public void It_should_throw_invalid_operation()
{
Action act = () => _dependencyCalculator!.GetDependencies();
Action act = () => _dependencyCalculator!.GetDependenciesFromResourceSchema();
act.Should().Throw<InvalidOperationException>();
}
}

[TestFixture]
public class Given_A_Dependency_Calculator() : DependencyCalculatorTests
{
[Test]
public void It_should_return_proper_ordered_dependencies1()
{
Dictionary<string, List<string>> resources = new Dictionary<string, List<string>>
{
{ "A", ["B"] },
{ "B", [] },
{ "C", ["B"] },
};

var dependencies = DependencyCalculator.GetDependencies(resources);

dependencies["A"].Should().Be(2);
dependencies["B"].Should().Be(1);
dependencies["C"].Should().Be(2);
}

[Test]
public void It_should_return_proper_ordered_dependencies2()
{
Dictionary<string, List<string>> resources = new Dictionary<string, List<string>>
{
{ "A", ["B"] },
{ "B", ["C", "D"] },
{ "C", [] },
{ "D", [] }
};

var dependencies = DependencyCalculator.GetDependencies(resources);

dependencies["A"].Should().Be(3);
dependencies["B"].Should().Be(2);
dependencies["C"].Should().Be(1);
dependencies["D"].Should().Be(1);
}

[Test]
public void It_should_handle_circular_dependencies()
{
Dictionary<string, List<string>> resources = new Dictionary<string, List<string>>
{
{ "A", ["B"] },
{ "B", ["C", "D"] },
{ "C", [] },
{ "D", ["A"] }
};

var dependencies = DependencyCalculator.GetDependencies(resources);

dependencies["A"].Should().Be(3);
dependencies["B"].Should().Be(2);
dependencies["C"].Should().Be(1);
dependencies["D"].Should().Be(1);
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,30 @@
// The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0.
// See the LICENSE and NOTICES files in the project root for more information.

using System.Diagnostics;
using System.Text.Json.Nodes;
using Microsoft.Extensions.Logging;

namespace EdFi.DataManagementService.Core.ApiSchema;

internal class DependencyCalculator(JsonNode _apiSchemaRootNode, ILogger _logger)
{
public JsonArray GetDependencies()
public JsonArray GetDependenciesFromResourceSchema()
{
var apiSchemaDocument = new ApiSchemaDocument(_apiSchemaRootNode, _logger);
var dependenciesJsonArray = new JsonArray();
foreach (JsonNode projectSchemaNode in apiSchemaDocument.GetAllProjectSchemaNodes())
{
var resourceSchemas = projectSchemaNode["resourceSchemas"]?.AsObject().Select(x => new ResourceSchema(x.Value!)).ToList()!;

Dictionary<string, List<string>> dependencies =
Dictionary<string, List<string>> resources =
resourceSchemas
.Where(rs => !rs.IsSchoolYearEnumeration)
.ToDictionary(rs => rs.ResourceName.Value, rs => rs.DocumentPaths.Where(d => d.IsReference).Select(d => d.ResourceName.Value).ToList());
.ToDictionary(
rs => rs.ResourceName.Value,
rs => rs.DocumentPaths.Where(d => d.IsReference && d.ResourceName.Value != "SchoolYearType").Select(d => ReplaceAbstractResourceNames(d.ResourceName.Value)).ToList());

Dictionary<string, int> orderedResources = dependencies.ToDictionary(d => d.Key, _ => 0);
Dictionary<string, int> visitedResources = [];
foreach (var dependency in dependencies.OrderBy(d => d.Value.Count).ThenBy(d => d.Key).Select(d => d.Key))
{
RecursivelyDetermineDependencies(dependency, 0);
}
var orderedResources = GetDependencies(resources);

string ResourceNameMapping(string resourceName)
{
Expand Down Expand Up @@ -58,55 +56,64 @@ string ResourceNameMapping(string resourceName)

dependenciesJsonArray.Add(new { resource = $"/{projectSchemaNode!.GetPropertyName()}/{resourceName}", order = orderedResource.Value, operations = new[] { "Create", "Update" } });
}
}

int RecursivelyDetermineDependencies(string resourceName, int depth)
{
// Code Smell here:
// These resources are similar to abstract base classes, so they are not represented in the resourceSchemas
// portion of the schema document. This is a rudimentary replacement with the most specific version of the resource
if (resourceName == "EducationOrganization")
{
resourceName = "School";
}
return dependenciesJsonArray;
}

if (resourceName == "GeneralStudentProgramAssociation")
{
resourceName = "StudentProgramAssociation";
}
private static string ReplaceAbstractResourceNames(string resourceName)
{
// Code Smell here:
// These resources are similar to abstract base classes, so they are not represented in the resourceSchemas
// portion of the schema document. This is a rudimentary replacement with the most specific version of the resource
if (resourceName == "EducationOrganization")
{
resourceName = "School";
}

if (!visitedResources.ContainsKey(resourceName))
{
visitedResources.Add(resourceName, 0);
}
if (resourceName == "GeneralStudentProgramAssociation")
{
resourceName = "StudentProgramAssociation";
}

return resourceName;
}

public static Dictionary<string, int> GetDependencies(Dictionary<string, List<string>> resources)
{
Dictionary<string, int> orderedResources = resources.ToDictionary(d => d.Key, _ => 0);
Dictionary<string, int> visitedResources = [];

foreach (var dependency in resources.OrderBy(d => d.Value.Count).ThenBy(d => d.Key).Select(d => d.Key))
{
RecursivelyDetermineDependencies(dependency, 0);
}

var maxDepth = depth;
if (dependencies.ContainsKey(resourceName))
int RecursivelyDetermineDependencies(string resourceName, int depth)
{
visitedResources.TryAdd(resourceName, 0);
var maxDepth = depth;
foreach (var dependency in resources[resourceName])
{
if (visitedResources.ContainsKey(dependency))
{
foreach (var dependency in dependencies[resourceName])
if (visitedResources[dependency] > maxDepth)
{
if (visitedResources.ContainsKey(dependency))
{
if (visitedResources[dependency] > maxDepth)
{
maxDepth = visitedResources[dependency];
}
}
else
{
var level = RecursivelyDetermineDependencies(dependency, depth);
if (level > maxDepth)
maxDepth = level;
}
maxDepth = visitedResources[dependency];
}

orderedResources[resourceName] = maxDepth + 1;
visitedResources[resourceName] = maxDepth + 1;
}

return maxDepth + 1;
else
{
var level = RecursivelyDetermineDependencies(dependency, depth);
if (level > maxDepth)
maxDepth = level;
}
}
orderedResources[resourceName] = maxDepth + 1;
visitedResources[resourceName] = maxDepth + 1;
return maxDepth + 1;
}

return dependenciesJsonArray;
return orderedResources;
}
}
2 changes: 1 addition & 1 deletion src/core/EdFi.DataManagementService.Core/ApiService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,6 @@ public IList<IDataModelInfo> GetDataModelInfo()
public JsonArray GetDependencies()
{
var dependencyCalculator = new DependencyCalculator(_apiSchemaProvider.ApiSchemaRootNode, _logger);
return dependencyCalculator.GetDependencies();
return dependencyCalculator.GetDependenciesFromResourceSchema();
}
}

0 comments on commit 902c366

Please sign in to comment.