Skip to content

Commit

Permalink
Validate non-owned resource groups.
Browse files Browse the repository at this point in the history
When initialising `service` or `test` state, we should validate all resource groups associated with a service(s), not just owned resources, as discrepancies cause bugs.
  • Loading branch information
big-andy-coates committed Dec 23, 2023
1 parent 5d1bbdb commit 00925bf
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -122,14 +122,17 @@ public static ResourceInitializer resourceInitializer(final Callbacks callbacks)
* @param components components to search for resources.
*/
public void init(final Collection<? extends ComponentDescriptor> components) {
components.forEach(componentValidator::validate);

LOGGER.debug(
"Initializing resources",
log -> log.with("stage", "init").with("components", componentNames(components)));

ensureResources(
groupById(
components,
resGroup -> resGroup.stream().anyMatch(ResourceDescriptor::isShared)));
resGroup -> resGroup.stream().anyMatch(ResourceDescriptor::isShared),
false));
}

/**
Expand All @@ -140,14 +143,17 @@ public void init(final Collection<? extends ComponentDescriptor> components) {
* @param components components to search for resources.
*/
public void service(final Collection<? extends ComponentDescriptor> components) {
components.forEach(componentValidator::validate);

LOGGER.debug(
"Initializing resources",
log -> log.with("stage", "service").with("components", componentNames(components)));

ensureResources(
groupById(
components,
resGroup -> resGroup.stream().anyMatch(ResourceDescriptor::isOwned)));
resGroup -> resGroup.stream().anyMatch(ResourceDescriptor::isOwned),
true));
}

/**
Expand All @@ -163,6 +169,8 @@ public void service(final Collection<? extends ComponentDescriptor> components)
public void test(
final Collection<? extends ComponentDescriptor> componentsUnderTest,
final Collection<? extends ComponentDescriptor> otherComponents) {
componentsUnderTest.forEach(componentValidator::validate);
otherComponents.forEach(componentValidator::validate);

LOGGER.debug(
"Initializing resources",
Expand All @@ -177,12 +185,14 @@ public void test(
resGroup ->
resGroup.stream().anyMatch(ResourceDescriptor::isUnowned)
&& resGroup.stream()
.noneMatch(ResourceDescriptor::isOwned))
.noneMatch(ResourceDescriptor::isOwned),
true)
.collect(Collectors.toMap(group -> group.get(0).id(), Function.identity()));

groupById(
otherComponents,
resGroup -> resGroup.stream().anyMatch(r -> unowned.containsKey(r.id())))
resGroup -> resGroup.stream().anyMatch(r -> unowned.containsKey(r.id())),
false)
.forEach(resGroup -> unowned.get(resGroup.get(0).id()).addAll(resGroup));

ensureResources(unowned.values().stream());
Expand Down Expand Up @@ -211,18 +221,21 @@ private ResourceDescriptor creatableDescriptor(final List<ResourceDescriptor> re

private Stream<List<ResourceDescriptor>> groupById(
final Collection<? extends ComponentDescriptor> components,
final Predicate<List<ResourceDescriptor>> groupPredicate) {
final Predicate<List<ResourceDescriptor>> groupPredicate,
final boolean validateNonMatchingResGroups) {
final Map<URI, List<ResourceDescriptor>> grouped =
components.stream()
.flatMap(this::getResources)
.flatMap(ComponentDescriptor::resources)
.collect(groupingBy(ResourceDescriptor::id));

return grouped.values().stream().filter(groupPredicate);
}
final Map<Boolean, List<List<ResourceDescriptor>>> partitioned =
grouped.values().stream().collect(groupingBy(groupPredicate::test));

if (validateNonMatchingResGroups) {
partitioned.getOrDefault(false, List.of()).forEach(this::validateResourceGroup);
}

private Stream<ResourceDescriptor> getResources(final ComponentDescriptor component) {
componentValidator.validate(component);
return component.resources();
return partitioned.getOrDefault(true, List.of()).stream();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,8 @@ void shouldThrowIfComponentValidationFails() {
@Test
void shouldThrowIfResourceGroupContainsSharedAndNonShared() {
// Given:
when(component0.resources()).thenReturn(Stream.of(sharedResource1));
when(component1.resources()).thenReturn(Stream.of(unownedResource1));
when(component0.resources()).thenAnswer(inv -> Stream.of(sharedResource1));
when(component1.resources()).thenAnswer(inv -> Stream.of(unownedResource1));

// When:
final Exception e =
Expand All @@ -155,8 +155,8 @@ void shouldThrowIfResourceGroupContainsSharedAndNonShared() {
@Test
void shouldThrowIfResourceGroupContainsUnmanagedAndNonManaged() {
// Given:
when(component0.resources()).thenReturn(Stream.of(unmanagedResource1));
when(component1.resources()).thenReturn(Stream.of(sharedResource1));
when(component0.resources()).thenAnswer(inv -> Stream.of(unmanagedResource1));
when(component1.resources()).thenAnswer(inv -> Stream.of(sharedResource1));

// When:
final Exception e =
Expand All @@ -179,8 +179,8 @@ void shouldThrowIfResourceGroupContainsUnmanagedAndNonManaged() {
@Test
void shouldThrowIfResourceGroupContainsOwnedAndOther() {
// Given:
when(component0.resources()).thenReturn(Stream.of(ownedResource1));
when(component1.resources()).thenReturn(Stream.of(sharedResource1));
when(component0.resources()).thenAnswer(inv -> Stream.of(ownedResource1));
when(component1.resources()).thenAnswer(inv -> Stream.of(sharedResource1));

// When:
final Exception e =
Expand All @@ -203,8 +203,8 @@ void shouldThrowIfResourceGroupContainsOwnedAndOther() {
@Test
void shouldThrowIfResourceGroupContainsUnownedAndOther() {
// Given:
when(component0.resources()).thenReturn(Stream.of(unownedResource1));
when(component1.resources()).thenReturn(Stream.of(sharedResource1));
when(component0.resources()).thenAnswer(inv -> Stream.of(unownedResource1));
when(component1.resources()).thenAnswer(inv -> Stream.of(sharedResource1));

// When:
final Exception e =
Expand All @@ -229,8 +229,8 @@ void shouldThrowIfResourceGroupContainsUnownedAndOther() {
void shouldCallbackValidateEachResGroupOnInit() {
// Given:
final ResourceA sharedResource2 = resourceA(1, SharedResource.class);
when(component0.resources()).thenReturn(Stream.of(sharedResource1));
when(component1.resources()).thenReturn(Stream.of(sharedResource2));
when(component0.resources()).thenAnswer(inv -> Stream.of(sharedResource1));
when(component1.resources()).thenAnswer(inv -> Stream.of(sharedResource2));

// When:
initializer.init(List.of(component0, component1));
Expand All @@ -246,13 +246,20 @@ void shouldCallbackValidateEachResGroupOnInit() {
@Test
void shouldCallbackValidateEachResGroupOnService() {
// Given:
when(component0.resources()).thenReturn(Stream.of(ownedResource1));
when(component1.resources()).thenReturn(Stream.of(unownedResource1));
when(unmanagedResource1.id()).thenReturn(URI.create("a://diff"));
when(component0.resources())
.thenAnswer(inv -> Stream.of(ownedResource1, unmanagedResource1));
when(component1.resources()).thenAnswer(inv -> Stream.of(unownedResource1));

// When:
initializer.service(List.of(component0, component1));

// Then:
verify(callbacks)
.validate(
(Class<ResourceA>) unmanagedResource1.getClass(),
List.of(unmanagedResource1));

verify(callbacks)
.validate(
(Class<ResourceA>) ownedResource1.getClass(),
Expand All @@ -263,13 +270,20 @@ void shouldCallbackValidateEachResGroupOnService() {
@Test
void shouldCallbackValidateEachResGroupOnTest() {
// Given:
when(component0.resources()).thenReturn(Stream.of(unownedResource1));
when(component1.resources()).thenReturn(Stream.of(ownedResource1));
when(unmanagedResource1.id()).thenReturn(URI.create("a://diff"));
when(component0.resources())
.thenAnswer(inv -> Stream.of(unownedResource1, unmanagedResource1));
when(component1.resources()).thenAnswer(inv -> Stream.of(ownedResource1));

// When:
initializer.test(List.of(component0), List.of(component1));

// Then:
verify(callbacks)
.validate(
(Class<ResourceA>) unmanagedResource1.getClass(),
List.of(unmanagedResource1));

verify(callbacks)
.validate(
(Class<ResourceA>) unownedResource1.getClass(),
Expand All @@ -279,8 +293,8 @@ void shouldCallbackValidateEachResGroupOnTest() {
@Test
void shouldThrowIfValidateCallbackThrows() {
// Given:
when(component0.resources()).thenReturn(Stream.of(ownedResource1));
when(component1.resources()).thenReturn(Stream.of(unownedResource1));
when(component0.resources()).thenAnswer(inv -> Stream.of(ownedResource1));
when(component1.resources()).thenAnswer(inv -> Stream.of(unownedResource1));
final RuntimeException expected = new RuntimeException("BIG BADA BOOM");
doThrow(expected).when(callbacks).validate(any(), any());

Expand All @@ -297,8 +311,9 @@ void shouldThrowIfValidateCallbackThrows() {
@Test
void shouldNotInitializeAnyResourceOnInitIfNoSharedResources() {
// Given:
when(component0.resources()).thenReturn(Stream.of(ownedResource1, unmanagedResource1));
when(component1.resources()).thenReturn(Stream.of(unownedResource1));
when(component0.resources())
.thenAnswer(inv -> Stream.of(ownedResource1, unmanagedResource1));
when(component1.resources()).thenAnswer(inv -> Stream.of(unownedResource1));

// When:
initializer.init(List.of(component0, component1));
Expand All @@ -312,8 +327,8 @@ void shouldNotInitializeAnyResourcesOnServiceIfNoOwnedResources() {
// Given:
final ResourceA shared = resourceA(2, SharedResource.class);
final ResourceA unowned = resourceA(3, UnownedResource.class);
when(component0.resources()).thenReturn(Stream.of(shared, unmanagedResource1));
when(component1.resources()).thenReturn(Stream.of(unowned));
when(component0.resources()).thenAnswer(inv -> Stream.of(shared, unmanagedResource1));
when(component1.resources()).thenAnswer(inv -> Stream.of(unowned));

// When:
initializer.service(List.of(component0, component1));
Expand All @@ -328,9 +343,9 @@ void shouldNotInitializeAnyResourcesOnTestIfNoUnownedResources() {
final ResourceA shared = resourceA(2, SharedResource.class);
final ResourceA unowned = resourceA(3, UnownedResource.class);
final ResourceA owned = resourceA(4, OwnedResource.class);
when(component0.resources()).thenReturn(Stream.of(unmanagedResource1, shared));
when(component0.resources()).thenAnswer(inv -> Stream.of(unmanagedResource1, shared));
when(component1.resources())
.thenReturn(Stream.of(unmanagedResource1, shared, unowned, owned));
.thenAnswer(inv -> Stream.of(unmanagedResource1, shared, unowned, owned));

// When:
initializer.test(List.of(component0), List.of(component1));
Expand All @@ -343,8 +358,8 @@ void shouldNotInitializeAnyResourcesOnTestIfNoUnownedResources() {
void
shouldNotInitializeAnyResourcesOnTestIfUnownedResourcesHaveOwnedDescriptorInComponentsUnderTest() {
// Given:
when(component0.resources()).thenReturn(Stream.of(unownedResource1));
when(component1.resources()).thenReturn(Stream.of(ownedResource1));
when(component0.resources()).thenAnswer(inv -> Stream.of(unownedResource1));
when(component1.resources()).thenAnswer(inv -> Stream.of(ownedResource1));

// When:
initializer.test(List.of(component0, component1), List.of());
Expand All @@ -359,8 +374,8 @@ void shouldNotInitializeUnmanagedGroups() {
final ResourceDescriptor unmanagedResource1b = mock(ResourceDescriptor.class);
when(unmanagedResource1b.id()).thenReturn(A1_ID);

when(component0.resources()).thenReturn(Stream.of(unmanagedResource1));
when(component1.resources()).thenReturn(Stream.of(unmanagedResource1b));
when(component0.resources()).thenAnswer(inv -> Stream.of(unmanagedResource1));
when(component1.resources()).thenAnswer(inv -> Stream.of(unmanagedResource1b));

// When:
initializer.init(List.of(component0, component1));
Expand All @@ -372,8 +387,9 @@ void shouldNotInitializeUnmanagedGroups() {
@Test
void shouldThrowOnUncreatableResource() {
// Given:
when(component0.resources()).thenReturn(Stream.of(unownedResource1));
when(component1.resources()).thenReturn(Stream.of()); // Missing owned resource descriptor
when(component0.resources()).thenAnswer(inv -> Stream.of(unownedResource1));
when(component1.resources())
.thenAnswer(inv -> Stream.of()); // Missing owned resource descriptor

// When:
final Exception e =
Expand All @@ -395,8 +411,8 @@ void shouldThrowOnUncreatableResource() {
void shouldEnsureSharedResource() {
// Given:
final ResourceA sharedResource2 = resourceA(2, SharedResource.class);
when(component0.resources()).thenReturn(Stream.of(sharedResource1));
when(component1.resources()).thenReturn(Stream.of(sharedResource2, sharedResource1));
when(component0.resources()).thenAnswer(inv -> Stream.of(sharedResource1));
when(component1.resources()).thenAnswer(inv -> Stream.of(sharedResource2, sharedResource1));

// When:
initializer.init(List.of(component0, component1));
Expand All @@ -413,8 +429,8 @@ void shouldEnsureSharedResource() {
void shouldEnsureOwnedResource() {
// Given:
final ResourceA ownedResource2 = resourceA(2, OwnedResource.class);
when(component0.resources()).thenReturn(Stream.of(ownedResource1));
when(component1.resources()).thenReturn(Stream.of(ownedResource2, unownedResource1));
when(component0.resources()).thenAnswer(inv -> Stream.of(ownedResource1));
when(component1.resources()).thenAnswer(inv -> Stream.of(ownedResource2, unownedResource1));

// When:
initializer.service(List.of(component0, component1));
Expand All @@ -432,8 +448,8 @@ void shouldEnsureUnownedResource() {
// Given:
final ResourceA ownedResource2 = resourceA(2, OwnedResource.class);
final ResourceA unownedResource2 = resourceA(2, UnownedResource.class);
when(component0.resources()).thenReturn(Stream.of(unownedResource2));
when(component1.resources()).thenReturn(Stream.of(ownedResource2, unownedResource1));
when(component0.resources()).thenAnswer(inv -> Stream.of(unownedResource2));
when(component1.resources()).thenAnswer(inv -> Stream.of(ownedResource2, unownedResource1));

// When:
initializer.test(List.of(component0), List.of(component1));
Expand All @@ -447,7 +463,7 @@ void shouldThrowIfEnsureThrows() {
// Given:
final RuntimeException expected = new RuntimeException("boom");
doThrow(expected).when(callbacks).ensure(any(), any());
when(component0.resources()).thenReturn(Stream.of(ownedResource1));
when(component0.resources()).thenAnswer(inv -> Stream.of(ownedResource1));

// When:
final Exception e =
Expand All @@ -463,7 +479,7 @@ void shouldThrowIfEnsureThrows() {
void shouldEnsureGroupingByHandler() {
// Given
final ResourceB ownedResourceB = resourceB(OwnedResource.class);
when(component0.resources()).thenReturn(Stream.of(ownedResource1, ownedResourceB));
when(component0.resources()).thenAnswer(inv -> Stream.of(ownedResource1, ownedResourceB));

// When:
initializer.service(List.of(component0));
Expand All @@ -478,7 +494,7 @@ void shouldThrowOnUnknownResourceType() {
// Given:
final NullPointerException expected = new NullPointerException("unknown");
doThrow(expected).when(callbacks).ensure(any(), any());
when(component0.resources()).thenReturn(Stream.of(sharedResource1));
when(component0.resources()).thenAnswer(inv -> Stream.of(sharedResource1));

// When:
final Exception e =
Expand Down

0 comments on commit 00925bf

Please sign in to comment.