/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file is distributed
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */

package com.amazonaws.xray;

import com.amazonaws.xray.contexts.LambdaSegmentContext;
import com.amazonaws.xray.contexts.LambdaSegmentContextResolver;
import com.amazonaws.xray.emitters.Emitter;
import com.amazonaws.xray.entities.Segment;
import com.amazonaws.xray.entities.Subsegment;
import com.amazonaws.xray.entities.TraceHeader;
import com.amazonaws.xray.entities.TraceID;
import com.amazonaws.xray.exceptions.AlreadyEmittedException;
import com.amazonaws.xray.exceptions.SegmentNotFoundException;
import com.amazonaws.xray.plugins.EC2Plugin;
import com.amazonaws.xray.plugins.ECSPlugin;
import com.amazonaws.xray.plugins.EKSPlugin;
import com.amazonaws.xray.plugins.ElasticBeanstalkPlugin;
import com.amazonaws.xray.plugins.Plugin;
import com.amazonaws.xray.strategy.ContextMissingStrategy;
import com.amazonaws.xray.strategy.IgnoreErrorContextMissingStrategy;
import com.amazonaws.xray.strategy.LogErrorContextMissingStrategy;
import com.amazonaws.xray.strategy.RuntimeErrorContextMissingStrategy;
import com.amazonaws.xray.strategy.sampling.LocalizedSamplingStrategy;
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import org.json.JSONException;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.FixMethodOrder;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.EnvironmentVariables;
import org.junit.contrib.java.lang.system.RestoreSystemProperties;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.skyscreamer.jsonassert.JSONAssert;
import org.skyscreamer.jsonassert.JSONCompareMode;

@FixMethodOrder(MethodSorters.JVM)
@PrepareForTest({LambdaSegmentContext.class, LambdaSegmentContextResolver.class})
@RunWith(PowerMockRunner.class)
@PowerMockIgnore("javax.net.ssl.*")
public class AWSXRayRecorderTest {

    private static final String TRACE_HEADER = "Root=1-57ff426a-80c11c39b0c928905eb0828d;Parent=1234abcd1234abcd;Sampled=1";

    private static ExecutorService threadExecutor;

    @Rule
    public EnvironmentVariables environmentVariables = new EnvironmentVariables();
    @Rule
    public RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties();

    @BeforeClass
    public static void startExecutor() {
        threadExecutor = Executors.newSingleThreadExecutor();
    }

    @AfterClass
    public static void stopExecutor() {
        threadExecutor.shutdown();
    }

    @Before
    public void setupAWSXRay() {
        Emitter blankEmitter = Mockito.mock(Emitter.class);
        LocalizedSamplingStrategy defaultSamplingStrategy = new LocalizedSamplingStrategy();
        Mockito.doReturn(true).when(blankEmitter).sendSegment(Mockito.anyObject());
        Mockito.doReturn(true).when(blankEmitter).sendSubsegment(Mockito.anyObject());
        AWSXRay.setGlobalRecorder(AWSXRayRecorderBuilder
                                      .standard()
                                      .withEmitter(blankEmitter)
                                      .withSamplingStrategy(defaultSamplingStrategy)
                                      .build());
        AWSXRay.clearTraceEntity();
    }

    @Test
    public void testGetThreadLocalReturnsCurrentSegment() {
        Segment segment = AWSXRay.beginSegment("test");
        Assert.assertEquals(segment, AWSXRay.getTraceEntity());
        AWSXRay.endSegment();
    }

    @Test
    public void testGetTraceEntityReturnsCurrentSegment() {
        Segment segment = AWSXRay.beginSegment("test");
        Assert.assertEquals(segment, AWSXRay.getTraceEntity());
        AWSXRay.endSegment();
    }

    @Test
    public void testGetThreadLocalReturnsCurrentSubsegment() {
        AWSXRay.beginSegment("test");
        Subsegment subsegment = AWSXRay.beginSubsegment("test");
        Assert.assertEquals(subsegment, AWSXRay.getTraceEntity());
        AWSXRay.endSubsegment();
        AWSXRay.endSegment();
    }

    @Test
    public void testGetTraceEntityReturnsCurrentSubsegment() {
        AWSXRay.beginSegment("test");
        Subsegment subsegment = AWSXRay.beginSubsegment("test");
        Assert.assertEquals(subsegment, AWSXRay.getTraceEntity());
        AWSXRay.endSubsegment();
        AWSXRay.endSegment();
    }

    @Test
    public void testGetThreadLocalOnEmptyThreadDoesNotThrowException() {
        AWSXRay.beginSegment("test");
        AWSXRay.endSegment();
        AWSXRay.getTraceEntity();
    }

    @Test
    public void testGetTraceEntityOnEmptyThreadDoesNotThrowException() {
        AWSXRay.beginSegment("test");
        AWSXRay.endSegment();
        AWSXRay.getTraceEntity();
    }

    @Test(expected = SegmentNotFoundException.class)
    public void testBeginSubsegmentOnEmptyThreadThrowsExceptionByDefault() {
        AWSXRay.beginSubsegment("test");
    }

    @Test
    public void testBeginSubsegmentOnEmptyThreadDoesNotThrowExceptionWithLogErrorContextMissingStrategy() {
        AWSXRay.getGlobalRecorder().setContextMissingStrategy(new LogErrorContextMissingStrategy());
        AWSXRay.beginSubsegment("test");
    }

    @Test
    public void testBeginSubsegmentOnEmptyThreadDoesNotThrowExceptionWithIgnoreErrorContextMissingStrategy() {
        AWSXRay.getGlobalRecorder().setContextMissingStrategy(new IgnoreErrorContextMissingStrategy());
        AWSXRay.beginSubsegment("test");
    }

    @Test
    public void testInjectThreadLocalInjectsCurrentSegment() throws Exception {
        Segment segment = AWSXRay.beginSegment("test");

        threadExecutor.submit(() -> {
            AWSXRay.injectThreadLocal(segment);
            Assert.assertEquals(segment, AWSXRay.getThreadLocal());
        }).get();

        AWSXRay.endSegment();
    }

    @Test
    public void testSetTraceEntityInjectsCurrentSegment() throws Exception {
        Segment segment = AWSXRay.beginSegment("test");

        threadExecutor.submit(() -> {
            AWSXRay.setTraceEntity(segment);
            Assert.assertEquals(segment, AWSXRay.getTraceEntity());
        }).get();

        AWSXRay.endSegment();
    }

    @Test
    public void testInjectThreadLocalInjectsCurrentSubsegment() throws Exception {
        AWSXRay.beginSegment("test");
        Subsegment subsegment = AWSXRay.beginSubsegment("test");

        threadExecutor.submit(() -> {
            AWSXRay.injectThreadLocal(subsegment);
            Assert.assertEquals(subsegment, AWSXRay.getThreadLocal());
        }).get();

        AWSXRay.endSubsegment();
        AWSXRay.endSegment();
    }

    @Test
    public void testSetTraceEntityInjectsCurrentSubsegment() throws Exception {
        AWSXRay.beginSegment("test");
        Subsegment subsegment = AWSXRay.beginSubsegment("test");

        threadExecutor.submit(() -> {
            AWSXRay.setTraceEntity(subsegment);
            Assert.assertEquals(subsegment, AWSXRay.getThreadLocal());
        }).get();

        AWSXRay.endSubsegment();
        AWSXRay.endSegment();
    }

    @Test
    public void testIsCurrentSegmentPresent() {
        Assert.assertFalse(AWSXRay.getCurrentSegmentOptional().isPresent());
        AWSXRay.beginSegment("test");
        Assert.assertTrue(AWSXRay.getCurrentSegmentOptional().isPresent());
        AWSXRay.endSegment();
        Assert.assertFalse(AWSXRay.getCurrentSegmentOptional().isPresent());
    }

    @Test
    public void testIsCurrentSubsegmentPresent() {
        Assert.assertFalse(AWSXRay.getCurrentSubsegmentOptional().isPresent());

        AWSXRay.beginSegment("test");
        Assert.assertFalse(AWSXRay.getCurrentSubsegmentOptional().isPresent());

        AWSXRay.beginSubsegment("test");
        Assert.assertTrue(AWSXRay.getCurrentSubsegmentOptional().isPresent());

        AWSXRay.endSubsegment();
        Assert.assertFalse(AWSXRay.getCurrentSubsegmentOptional().isPresent());

        AWSXRay.endSegment();
        Assert.assertFalse(AWSXRay.getCurrentSubsegmentOptional().isPresent());
    }

    @Test(expected = SegmentNotFoundException.class)
    public void testSubsegmentBeginWithoutSegmentContextThrowsException() {
        AWSXRay.beginSubsegment("test");
    }

    @Test
    public void testNotSendingUnsampledSegment() {
        Emitter mockEmitter = Mockito.mock(Emitter.class);
        AWSXRayRecorder recorder = AWSXRayRecorderBuilder.standard().withEmitter(mockEmitter).build();

        Segment segment = recorder.beginSegment("test");
        segment.setSampled(false);
        recorder.endSegment();

        Mockito.verify(mockEmitter, Mockito.times(0)).sendSegment(Mockito.any());
    }

    @Test
    public void testSegmentEmitted() {
        Emitter mockEmitter = Mockito.mock(Emitter.class);
        AWSXRayRecorder recorder = AWSXRayRecorderBuilder.standard().withEmitter(mockEmitter).build();

        recorder.beginSegment("test");
        recorder.beginSubsegment("test");
        recorder.endSubsegment();
        recorder.endSegment();

        Mockito.verify(mockEmitter, Mockito.times(1)).sendSegment(Mockito.any());
    }

    @Test
    public void testExplicitSubsegmentEmitted() {
        Emitter mockEmitter = Mockito.mock(Emitter.class);
        AWSXRayRecorder recorder = AWSXRayRecorderBuilder.standard().withEmitter(mockEmitter).build();

        recorder.beginSegment("test");
        Subsegment subsegment = recorder.beginSubsegment("test");
        recorder.endSubsegment(subsegment);
        recorder.endSegment();

        Mockito.verify(mockEmitter, Mockito.times(1)).sendSegment(Mockito.any());
    }

    @Test
    public void testDummySegmentNotEmitted() {
        Emitter mockEmitter = Mockito.mock(Emitter.class);
        AWSXRayRecorder recorder = AWSXRayRecorderBuilder.standard().withEmitter(mockEmitter).build();

        recorder.beginDummySegment();
        recorder.beginSubsegment("test");
        recorder.endSubsegment();
        recorder.endSegment();

        Mockito.verify(mockEmitter, Mockito.times(0)).sendSegment(Mockito.any());
    }

    @Test
    public void testSubsegmentEmittedInLambdaContext() throws JSONException {
        TraceHeader header = TraceHeader.fromString(TRACE_HEADER);

        PowerMockito.stub(PowerMockito.method(LambdaSegmentContext.class, "getTraceHeaderFromEnvironment")).toReturn(header);
        PowerMockito.stub(PowerMockito.method(LambdaSegmentContextResolver.class, "getLambdaTaskRoot")).toReturn("/var/task");

        Emitter mockEmitter = Mockito.mock(Emitter.class);
        AWSXRayRecorder recorder = AWSXRayRecorderBuilder.standard().withEmitter(mockEmitter).build();
        recorder.createSubsegment("test", () -> {
        });

        ArgumentCaptor<Subsegment> emittedSubsegment = ArgumentCaptor.forClass(Subsegment.class);
        Mockito.verify(mockEmitter, Mockito.times(1)).sendSubsegment(emittedSubsegment.capture());

        Subsegment captured = emittedSubsegment.getValue();

        JSONAssert.assertEquals(expectedLambdaSubsegment(
            header.getRootTraceId(), header.getParentId(), captured.getId(), captured.getStartTime(),
            captured.getEndTime()).toString(), captured.streamSerialize(), JSONCompareMode.NON_EXTENSIBLE);
    }

    private ObjectNode expectedLambdaSubsegment(
        TraceID traceId, String segmentId, String subsegmentId, double startTime, double endTime) {
        ObjectNode expected = JsonNodeFactory.instance.objectNode();
        expected.put("name", "test");
        expected.put("type", "subsegment");
        expected.put("start_time", startTime);
        expected.put("end_time", endTime);
        expected.put("trace_id", traceId.toString());
        expected.put("parent_id", segmentId);
        expected.put("id", subsegmentId);
        return expected;
    }

    @Test
    public void testSubsegmentNotEmittedWithoutExceptionInLambdaInitContext() {
        PowerMockito.stub(PowerMockito.method(LambdaSegmentContext.class, "getTraceHeaderFromEnvironment"))
                    .toReturn(TraceHeader.fromString(null));
        PowerMockito.stub(PowerMockito.method(LambdaSegmentContextResolver.class, "getLambdaTaskRoot")).toReturn("/var/task");

        Emitter mockEmitter = Mockito.mock(Emitter.class);
        AWSXRayRecorder recorder = AWSXRayRecorderBuilder.standard().withEmitter(mockEmitter).build();
        recorder.createSubsegment("test", () -> {
        });

        Mockito.verify(mockEmitter, Mockito.times(0)).sendSubsegment(Mockito.any());
    }

    @Test
    public void testSubsegmentWithChildEmittedTogetherInLambdaContext() {
        TraceHeader header = TraceHeader.fromString(TRACE_HEADER);

        PowerMockito.stub(PowerMockito.method(LambdaSegmentContext.class, "getTraceHeaderFromEnvironment")).toReturn(header);
        PowerMockito.stub(PowerMockito.method(LambdaSegmentContextResolver.class, "getLambdaTaskRoot")).toReturn("/var/task");

        Emitter mockEmitter = Mockito.mock(Emitter.class);
        AWSXRayRecorder recorder = AWSXRayRecorderBuilder.standard().withEmitter(mockEmitter).build();

        recorder.createSubsegment("testTogether", () -> {
            recorder.createSubsegment("testTogether2", () -> {
            });
        });

        ArgumentCaptor<Subsegment> emittedSubsegment = ArgumentCaptor.forClass(Subsegment.class);
        Mockito.verify(mockEmitter, Mockito.times(1)).sendSubsegment(emittedSubsegment.capture());

        Subsegment captured = emittedSubsegment.getValue();

        Assert.assertEquals(1, captured.getSubsegments().size());
    }

    @Test
    public void testSubsequentSubsegmentBranchesEmittedInLambdaContext() {
        TraceHeader header = TraceHeader.fromString(TRACE_HEADER);

        PowerMockito.stub(PowerMockito.method(LambdaSegmentContext.class, "getTraceHeaderFromEnvironment")).toReturn(header);
        PowerMockito.stub(PowerMockito.method(LambdaSegmentContextResolver.class, "getLambdaTaskRoot")).toReturn("/var/task");

        Emitter mockEmitter = Mockito.mock(Emitter.class);
        AWSXRayRecorder recorder = AWSXRayRecorderBuilder.standard().withEmitter(mockEmitter).build();

        recorder.createSubsegment("testTogether", () -> {
            recorder.createSubsegment("testTogether2", () -> {
            });
        });

        recorder.createSubsegment("testTogether3", () -> {
            recorder.createSubsegment("testTogether4", () -> {
            });
        });

        ArgumentCaptor<Subsegment> emittedSubsegments = ArgumentCaptor.forClass(Subsegment.class);

        Mockito.verify(mockEmitter, Mockito.times(2)).sendSubsegment(emittedSubsegments.capture());

        List<Subsegment> captured = emittedSubsegments.getAllValues();

        captured.forEach((capturedSubsegment) -> {
            Assert.assertEquals(1, capturedSubsegment.getSubsegments().size());
        });
    }

    @Test
    public void testContextMissingStrategyOverrideEnvironmentVariable() {
        environmentVariables.set(ContextMissingStrategy.CONTEXT_MISSING_STRATEGY_ENVIRONMENT_VARIABLE_OVERRIDE_KEY, "log_error");
        AWSXRayRecorder recorder = AWSXRayRecorderBuilder.standard().withContextMissingStrategy(
            new RuntimeErrorContextMissingStrategy()).build();
        Assert.assertTrue(recorder.getContextMissingStrategy() instanceof LogErrorContextMissingStrategy);
        environmentVariables.set(ContextMissingStrategy.CONTEXT_MISSING_STRATEGY_ENVIRONMENT_VARIABLE_OVERRIDE_KEY, null);
    }

    @Test
    public void testContextMissingStrategyOverrideSystemProperty() {
        System.setProperty(ContextMissingStrategy.CONTEXT_MISSING_STRATEGY_SYSTEM_PROPERTY_OVERRIDE_KEY, "log_error");
        AWSXRayRecorder recorder = AWSXRayRecorderBuilder.standard().withContextMissingStrategy(
            new RuntimeErrorContextMissingStrategy()).build();
        Assert.assertTrue(recorder.getContextMissingStrategy() instanceof LogErrorContextMissingStrategy);
    }

    @Test
    public void testContextMissingStrategyOverrideEnvironmentVariableOverridesSystemProperty() {
        environmentVariables.set(ContextMissingStrategy.CONTEXT_MISSING_STRATEGY_ENVIRONMENT_VARIABLE_OVERRIDE_KEY, "log_error");
        System.setProperty(ContextMissingStrategy.CONTEXT_MISSING_STRATEGY_SYSTEM_PROPERTY_OVERRIDE_KEY, "runtime_error");
        AWSXRayRecorder recorder = AWSXRayRecorderBuilder.standard().withContextMissingStrategy(
            new RuntimeErrorContextMissingStrategy()).build();
        Assert.assertTrue(recorder.getContextMissingStrategy() instanceof LogErrorContextMissingStrategy);
        environmentVariables.set(ContextMissingStrategy.CONTEXT_MISSING_STRATEGY_ENVIRONMENT_VARIABLE_OVERRIDE_KEY, null);
    }

    @Test(expected = AlreadyEmittedException.class)
    public void testEmittingSegmentTwiceThrowsSegmentAlreadyEmittedException() {
        Segment s = AWSXRay.beginSegment("test");
        AWSXRay.endSegment();
        AWSXRay.injectThreadLocal(s);
        AWSXRay.endSegment();
    }

    @Test
    public void testSubsegmentFunctionExceptionWhenMissingContextIsLogged() {
        // given
        RuntimeException expectedException = new RuntimeException("To be thrown by function");
        Function<Subsegment, Void> function = (subsegment) -> {
            throw expectedException;
        };
        AWSXRayRecorder recorder = AWSXRayRecorderBuilder.standard()
                                                         .withContextMissingStrategy(new LogErrorContextMissingStrategy())
                                                         .build();

        // when
        try {
            recorder.createSubsegment("test", function);
            Assert.fail("An exception should have been thrown");
        } catch (Exception e) {
            Assert.assertEquals("Function exception was not propagated", expectedException, e);
        }
    }

    @Test
    public void testSubsegmentConsumerExceptionWhenMissingContextIsLogged() {
        // given
        RuntimeException expectedException = new RuntimeException("To be thrown by consumer");
        Consumer<Subsegment> consumer = (subsegment) -> {
            throw expectedException;
        };
        AWSXRayRecorder recorder = AWSXRayRecorderBuilder.standard()
                                                         .withContextMissingStrategy(new LogErrorContextMissingStrategy())
                                                         .build();

        // when
        try {
            recorder.createSubsegment("test", consumer);
            Assert.fail("An exception should have been thrown");
        } catch (Exception e) {
            Assert.assertEquals("Consumer exception was not propagated", expectedException, e);
        }
    }

    @Test
    public void testSubsegmentSupplierExceptionWhenMissingContextIsLogged() {
        // given
        RuntimeException expectedException = new RuntimeException("To be thrown by supplier");
        Supplier<Void> supplier = () -> {
            throw expectedException;
        };
        AWSXRayRecorder recorder = AWSXRayRecorderBuilder.standard()
                                                         .withContextMissingStrategy(new LogErrorContextMissingStrategy())
                                                         .build();

        // when
        try {
            recorder.createSubsegment("test", supplier);
            Assert.fail("An exception should have been thrown");
        } catch (Exception e) {
            Assert.assertEquals("Supplier exception was not propagated", expectedException, e);
        }
    }

    @Test
    public void testSubsegmentRunnableExceptionWhenMissingContextIsLogged() {
        // given
        RuntimeException expectedException = new RuntimeException("To be thrown by runnable");
        Runnable runnable = () -> {
            throw expectedException;
        };
        AWSXRayRecorder recorder = AWSXRayRecorderBuilder.standard()
                                                         .withContextMissingStrategy(new LogErrorContextMissingStrategy())
                                                         .build();

        // when
        try {
            recorder.createSubsegment("test", runnable);
            Assert.fail("An exception should have been thrown");
        } catch (Exception e) {
            Assert.assertEquals("Runnable exception was not propagated", expectedException, e);
        }
    }

    @Test
    public void testOriginResolutionWithAllPlugins() {
        //given
        EC2Plugin ec2Plugin = Mockito.mock(EC2Plugin.class);
        ECSPlugin ecsPlugin = Mockito.mock(ECSPlugin.class);
        ElasticBeanstalkPlugin ebPlugin = Mockito.mock(ElasticBeanstalkPlugin.class);
        EKSPlugin eksPlugin = Mockito.mock(EKSPlugin.class);

        List<Plugin> plugins = new ArrayList<>();
        plugins.add(ec2Plugin);
        plugins.add(ecsPlugin);
        plugins.add(ebPlugin);
        plugins.add(eksPlugin);

        List<String> origins = new ArrayList<>();
        origins.add(EC2Plugin.ORIGIN);
        origins.add(ECSPlugin.ORIGIN);
        origins.add(ElasticBeanstalkPlugin.ORIGIN);
        origins.add(EKSPlugin.ORIGIN);

        Map<String, Object> runtimeContext = new HashMap<>();
        runtimeContext.put("key", "value");

        for (int i = 0; i < 4; i++) {
            Mockito.doReturn(true).when(plugins.get(i)).isEnabled();
            Mockito.doReturn(runtimeContext).when(plugins.get(i)).getRuntimeContext();
            Mockito.doReturn("serviceName").when(plugins.get(i)).getServiceName();
            Mockito.doReturn(origins.get(i)).when(plugins.get(i)).getOrigin();
        }

        AWSXRayRecorder recorder = AWSXRayRecorderBuilder.standard()
                                                         .withPlugin(ec2Plugin)
                                                         .withPlugin(ecsPlugin)
                                                         .withPlugin(ebPlugin)
                                                         .withPlugin(eksPlugin)
                                                         .build();

        // when
        Assert.assertEquals(ElasticBeanstalkPlugin.ORIGIN, recorder.getOrigin());
    }

    @Test
    public void testEKSOriginResolvesOverECSOrigin() {
        //given
        ECSPlugin ecsPlugin = Mockito.mock(ECSPlugin.class);
        EKSPlugin eksPlugin = Mockito.mock(EKSPlugin.class);

        Map<String, Object> runtimeContext = new HashMap<>();
        runtimeContext.put("key", "value");

        Mockito.doReturn(true).when(ecsPlugin).isEnabled();
        Mockito.doReturn(runtimeContext).when(ecsPlugin).getRuntimeContext();
        Mockito.doReturn("ecs").when(ecsPlugin).getServiceName();
        Mockito.doReturn(ECSPlugin.ORIGIN).when(ecsPlugin).getOrigin();
        Mockito.doReturn(true).when(eksPlugin).isEnabled();
        Mockito.doReturn(runtimeContext).when(eksPlugin).getRuntimeContext();
        Mockito.doReturn("eks").when(eksPlugin).getServiceName();
        Mockito.doReturn(EKSPlugin.ORIGIN).when(eksPlugin).getOrigin();

        AWSXRayRecorder recorder = AWSXRayRecorderBuilder.standard()
                                                         .withPlugin(eksPlugin)
                                                         .withPlugin(ecsPlugin)
                                                         .build();

        // when
        Assert.assertEquals(EKSPlugin.ORIGIN, recorder.getOrigin());
    }

    @Test
    public void testPluginEquality() {
        Collection<Plugin> plugins = new HashSet<>();

        plugins.add(new EC2Plugin());
        plugins.add(new EC2Plugin()); // should be deduped
        plugins.add(new ECSPlugin());
        plugins.add(new EKSPlugin());
        plugins.add(new ElasticBeanstalkPlugin());

        Assert.assertEquals(4, plugins.size());
    }

    @Test
    public void testCurrentFormattedId() {
        TraceID traceId = TraceID.fromString("1-5759e988-bd862e3fe1be46a994272793");
        String entityId = "123456789";

        Segment seg = AWSXRay.beginSegment("test", traceId, "FakeParentId");
        seg.setId(entityId);

        Assert.assertEquals(traceId.toString() + "@" + entityId, AWSXRay.currentFormattedId());
    }
}