/*
 * Copyright 1999-2018 Alibaba Group Holding Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License 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.alibaba.csp.sentinel.adapter.sofa.rpc;

import com.alibaba.csp.sentinel.Constants;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.context.Context;
import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.node.ClusterNode;
import com.alibaba.csp.sentinel.node.DefaultNode;
import com.alibaba.csp.sentinel.node.Node;
import com.alibaba.csp.sentinel.node.StatisticNode;
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
import com.alipay.sofa.rpc.common.RpcConstants;
import com.alipay.sofa.rpc.core.request.SofaRequest;
import com.alipay.sofa.rpc.core.response.SofaResponse;
import com.alipay.sofa.rpc.filter.FilterInvoker;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import java.util.Map;
import java.util.Set;

import static org.junit.Assert.*;
import static org.mockito.Mockito.*;

/**
 * Test cases for {@link SentinelSofaRpcConsumerFilter}.
 *
 * @author cdfive
 */
public class SentinelSofaRpcConsumerFilterTest extends BaseTest {

    @Before
    public void setUp() {
        cleanUpAll();
    }

    @After
    public void cleanUp() {
        cleanUpAll();
    }

    @Test
    public void testInvokeSentinelWorks() {
        SentinelSofaRpcConsumerFilter filter = new SentinelSofaRpcConsumerFilter();

        final String interfaceResourceName = "com.alibaba.csp.sentinel.adapter.sofa.rpc.service.DemoService";
        final String methodResourceName = "com.alibaba.csp.sentinel.adapter.sofa.rpc.service.DemoService#sayHello(java.lang.String,int)";

        SofaRequest request = mock(SofaRequest.class);
        when(request.getInvokeType()).thenReturn(RpcConstants.INVOKER_TYPE_SYNC);
        when(request.getInterfaceName()).thenReturn(interfaceResourceName);
        when(request.getMethodName()).thenReturn("sayHello");
        when(request.getMethodArgSigs()).thenReturn(new String[]{"java.lang.String", "int"});
        when(request.getMethodArgs()).thenReturn(new Object[]{"Sentinel", 2020});

        FilterInvoker filterInvoker = mock(FilterInvoker.class);
        when(filterInvoker.invoke(request)).thenAnswer(new Answer<SofaResponse>() {
            @Override
            public SofaResponse answer(InvocationOnMock invocationOnMock) throws Throwable {
                verifyInvocationStructure(interfaceResourceName, methodResourceName);
                SofaResponse response = new SofaResponse();
                response.setAppResponse("Hello Sentinel 2020");
                return response;
            }
        });

        // Before invoke
        assertNull(ContextUtil.getContext());

        // Do invoke
        SofaResponse response = filter.invoke(filterInvoker, request);
        assertEquals("Hello Sentinel 2020", response.getAppResponse());
        verify(filterInvoker).invoke(request);

        // After invoke, make sure exit context
        assertNull(ContextUtil.getContext());
    }

    /**
     * Verify Sentinel invocation structure in memory:
     * EntranceNode(defaultContextName)
     * --InterfaceNode(interfaceName)
     * ----MethodNode(resourceName)
     */
    private void verifyInvocationStructure(String interfaceResourceName, String methodResourceName) {
        Context context = ContextUtil.getContext();
        assertNotNull(context);

        // As not call ContextUtil.enter(methodResourceName, applicationName) in SentinelSofaRpcConsumerFilter, use default context
        // In actual project, a consumer is usually also a provider, the context will be created by SentinelSofaRpcProviderFilter
        // If consumer is on the top of SOFARPC invocation chain, use default context
        assertEquals(Constants.CONTEXT_DEFAULT_NAME, context.getName());
        assertEquals("", context.getOrigin());

        DefaultNode entranceNode = context.getEntranceNode();
        ResourceWrapper entranceResource = entranceNode.getId();
        assertEquals(Constants.CONTEXT_DEFAULT_NAME, entranceResource.getName());
        assertSame(EntryType.IN, entranceResource.getEntryType());

        // As SphU.entry(interfaceResourceName, EntryType.OUT);
        Set<Node> childList = entranceNode.getChildList();
        assertEquals(1, childList.size());
        DefaultNode interfaceNode = (DefaultNode) childList.iterator().next();
        ResourceWrapper interfaceResource = interfaceNode.getId();
        assertEquals(interfaceResourceName, interfaceResource.getName());
        assertSame(EntryType.OUT, interfaceResource.getEntryType());

        // As SphU.entry(methodResourceName, EntryType.OUT);
        childList = interfaceNode.getChildList();
        assertEquals(1, childList.size());
        DefaultNode methodNode = (DefaultNode) childList.iterator().next();
        ResourceWrapper methodResource = methodNode.getId();
        assertEquals(methodResourceName, methodResource.getName());
        assertSame(EntryType.OUT, methodResource.getEntryType());

        // Verify curEntry
        Entry curEntry = context.getCurEntry();
        assertSame(methodNode, curEntry.getCurNode());
        assertSame(interfaceNode, curEntry.getLastNode());
        // As context origin is not "", no originNode should be created in curEntry
        assertNull(curEntry.getOriginNode());

        // Verify clusterNode
        ClusterNode methodClusterNode = methodNode.getClusterNode();
        ClusterNode interfaceClusterNode = interfaceNode.getClusterNode();
        // Different resource->Different ProcessorSlot->Different ClusterNode
        assertNotSame(methodClusterNode, interfaceClusterNode);

        // As context origin is "", the StatisticNode should not be created in originCountMap of ClusterNode
        Map<String, StatisticNode> methodOriginCountMap = methodClusterNode.getOriginCountMap();
        assertEquals(0, methodOriginCountMap.size());

        Map<String, StatisticNode> interfaceOriginCountMap = interfaceClusterNode.getOriginCountMap();
        assertEquals(0, interfaceOriginCountMap.size());
    }
}
