Skip to content

Commit ae95329

Browse files
author
Guy Fankam
committed
Add dependency between API and API operation policy to ensure proper order.
1 parent db93076 commit ae95329

File tree

2 files changed

+130
-1
lines changed

2 files changed

+130
-1
lines changed

src/publisher.tests/Relationships.cs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,108 @@ await Assert.That(relationships.Predecessors[childKey])
584584
});
585585
}
586586

587+
[Test]
588+
public async Task Adds_relationship_between_api_and_api_operation_policy()
589+
{
590+
var gen = from apiKey in Generator.GenerateResourceKey(ApiResource.Instance)
591+
from operationKey in from operationName in Generator.ResourceName
592+
select new ResourceKey
593+
{
594+
Resource = ApiOperationResource.Instance,
595+
Name = operationName,
596+
Parents = ParentChain.From([.. apiKey.Parents, (apiKey.Resource, apiKey.Name)])
597+
}
598+
let policyKey = new ResourceKey
599+
{
600+
Resource = ApiOperationPolicyResource.Instance,
601+
Name = ResourceName.From("policy").IfErrorThrow(),
602+
Parents = ParentChain.From([.. operationKey.Parents, (operationKey.Resource, operationKey.Name)])
603+
}
604+
from fixture in Fixture.Generate()
605+
let policyFile = new FileInfo("operationPolicy.xml")
606+
let fileOperations = Common.NoOpFileOperations with
607+
{
608+
EnumerateServiceDirectoryFiles = () => [policyFile]
609+
}
610+
select (apiKey, policyKey, fileOperations, fixture with
611+
{
612+
IsValidationStrict = () => false,
613+
ParseResourceFile = async (file, _, _) =>
614+
{
615+
await ValueTask.CompletedTask;
616+
617+
return file.FullName == policyFile.FullName
618+
? policyKey
619+
: Option.None;
620+
}
621+
});
622+
623+
await gen.SampleAsync(async tuple =>
624+
{
625+
// Arrange
626+
var (apiKey, policyKey, fileOperations, fixture) = tuple;
627+
var getRelationships = fixture.Resolve();
628+
629+
// Act
630+
var relationships = await getRelationships(fileOperations, CancellationToken);
631+
632+
// Assert that the API is a predecessor of the policy
633+
await Assert.That(relationships.Predecessors[policyKey])
634+
.Contains(apiKey);
635+
});
636+
}
637+
638+
[Test]
639+
public async Task Adds_relationship_between_workspace_api_and_workspace_api_operation_policy()
640+
{
641+
var gen = from apiKey in Generator.GenerateResourceKey(WorkspaceApiResource.Instance)
642+
from operationKey in from operationName in Generator.ResourceName
643+
select new ResourceKey
644+
{
645+
Resource = WorkspaceApiOperationResource.Instance,
646+
Name = operationName,
647+
Parents = ParentChain.From([.. apiKey.Parents, (apiKey.Resource, apiKey.Name)])
648+
}
649+
let policyKey = new ResourceKey
650+
{
651+
Resource = WorkspaceApiOperationPolicyResource.Instance,
652+
Name = ResourceName.From("policy").IfErrorThrow(),
653+
Parents = ParentChain.From([.. operationKey.Parents, (operationKey.Resource, operationKey.Name)])
654+
}
655+
from fixture in Fixture.Generate()
656+
let policyFile = new FileInfo("operationPolicy.xml")
657+
let fileOperations = Common.NoOpFileOperations with
658+
{
659+
EnumerateServiceDirectoryFiles = () => [policyFile]
660+
}
661+
select (apiKey, policyKey, fileOperations, fixture with
662+
{
663+
IsValidationStrict = () => false,
664+
ParseResourceFile = async (file, _, _) =>
665+
{
666+
await ValueTask.CompletedTask;
667+
668+
return file.FullName == policyFile.FullName
669+
? policyKey
670+
: Option.None;
671+
}
672+
});
673+
674+
await gen.SampleAsync(async tuple =>
675+
{
676+
// Arrange
677+
var (apiKey, policyKey, fileOperations, fixture) = tuple;
678+
var getRelationships = fixture.Resolve();
679+
680+
// Act
681+
var relationships = await getRelationships(fileOperations, CancellationToken);
682+
683+
// Assert that the API is a predecessor of the policy
684+
await Assert.That(relationships.Predecessors[policyKey])
685+
.Contains(apiKey);
686+
});
687+
}
688+
587689
[Test]
588690
public async Task Returns_primary_and_secondary_relationships_for_a_composite_resource()
589691
{

src/publisher/Relationships.cs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,6 @@ await resources.Values
309309
});
310310
}
311311

312-
// Process non-root workspace API revisions
313312
if (key.Resource is WorkspaceApiResource)
314313
{
315314
ApiRevisionModule.Parse(key.Name)
@@ -326,6 +325,34 @@ await resources.Values
326325
pairs.Add((currentRevision, key));
327326
});
328327
}
328+
329+
// Add relationship between API operation policies and their parent APIs.
330+
// We don't directly parse API operations; they get created when their parent API specification is put.
331+
// This relationship enforces the order API operation => API operation policy.
332+
if (key.Resource is ApiOperationPolicyResource or WorkspaceApiOperationPolicyResource)
333+
{
334+
var (grandParentResource, grandParentName) = (key.Resource, key.Parents.SkipLast(1).LastOrDefault()) switch
335+
{
336+
(ApiOperationPolicyResource, (ApiResource apiResource, var apiName)) =>
337+
(apiResource as IResource, apiName),
338+
(WorkspaceApiOperationPolicyResource, (WorkspaceApiResource workspaceApiResource, var apiName)) =>
339+
(workspaceApiResource, apiName),
340+
(_, (null, _)) =>
341+
throw new InvalidOperationException($"Resource '{key}' is missing its grandparent."),
342+
(_, (var resource, _)) =>
343+
throw new InvalidOperationException($"Resource '{key}' has grandparent resource '{resource.GetType().Name}' instead of the expected API resource.")
344+
};
345+
346+
var grandParent = new ResourceKey
347+
{
348+
Resource = grandParentResource,
349+
Name = grandParentName,
350+
Parents = ParentChain.From(key.Parents.SkipLast(2))
351+
};
352+
353+
pairs.Add((grandParent, key));
354+
}
355+
329356
}, maxDegreeOfParallelism: Option.None, cancellationToken);
330357

331358
var relationships = Relationships.From(pairs, cancellationToken);

0 commit comments

Comments
 (0)