Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class ReportsSchedulerExtension : ResourceSharingExtension {
)
}

override fun assignResourceSharingClient(resourceSharingClient: ResourceSharingClient) {
override fun assignResourceSharingClient(resourceSharingClient: ResourceSharingClient?) {
ResourceSharingClientAccessor.getInstance()
.setResourceSharingClient(resourceSharingClient)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class ResourceSharingClientAccessor private constructor() {

private var client: ResourceSharingClient? = null

fun setResourceSharingClient(client: ResourceSharingClient) {
fun setResourceSharingClient(client: ResourceSharingClient?) {
this.client = client
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.reportsscheduler.resources

import org.junit.jupiter.api.Assertions.assertDoesNotThrow
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotNull
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.mockito.Mockito.mock
import org.opensearch.reportsscheduler.ReportsSchedulerExtension
import org.opensearch.security.spi.resources.client.ResourceSharingClient

class ResourceSharingClientAccessorTests {

private lateinit var accessor: ResourceSharingClientAccessor

@BeforeEach
fun setUp() {
accessor = ResourceSharingClientAccessor.getInstance()
// Reset state between tests
accessor.setResourceSharingClient(null)
}

@Test
fun `setResourceSharingClient accepts non-null client`() {
val mockClient = mock(ResourceSharingClient::class.java)
assertDoesNotThrow { accessor.setResourceSharingClient(mockClient) }
assertEquals(mockClient, accessor.getResourceSharingClient())
}

@Test
fun `setResourceSharingClient accepts null to clear the client`() {
val mockClient = mock(ResourceSharingClient::class.java)
accessor.setResourceSharingClient(mockClient)
assertNotNull(accessor.getResourceSharingClient())

assertDoesNotThrow { accessor.setResourceSharingClient(null) }
assertNull(accessor.getResourceSharingClient())
}

@Test
fun `assignResourceSharingClient does not throw NPE when called with null`() {
// This test validates the fix for the NPE that crashes the master election loop.
// When the resource-sharing feature flag is disabled, the security plugin calls
// assignResourceSharingClient(null) on all registered extensions. Before the fix,
// Kotlin's non-null parameter check would throw an NPE here.
val extension = ReportsSchedulerExtension()

// Simulate enable → disable cycle (exactly what the canary does)
val mockClient = mock(ResourceSharingClient::class.java)
assertDoesNotThrow { extension.assignResourceSharingClient(mockClient) }
assertDoesNotThrow { extension.assignResourceSharingClient(null) }

// After disable, client should be null
assertNull(ResourceSharingClientAccessor.getInstance().getResourceSharingClient())
}

@Test
fun `assignResourceSharingClient enables then disables without error`() {
// Simulates repeated enable/disable cycles
val extension = ReportsSchedulerExtension()
val mockClient = mock(ResourceSharingClient::class.java)

repeat(3) {
assertDoesNotThrow { extension.assignResourceSharingClient(mockClient) }
assertEquals(mockClient, ResourceSharingClientAccessor.getInstance().getResourceSharingClient())

assertDoesNotThrow { extension.assignResourceSharingClient(null) }
assertNull(ResourceSharingClientAccessor.getInstance().getResourceSharingClient())
}
}
}
Loading