/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.struts2.dispatcher;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.springframework.mock.web.MockHttpServletRequest;

import junit.framework.TestCase;

import com.mockobjects.constraint.Constraint;
import com.mockobjects.constraint.IsAnything;
import com.mockobjects.constraint.IsEqual;
import com.mockobjects.dynamic.Mock;
import java.util.AbstractMap;


/**
 */
public class SessionMapTest extends TestCase {

    private Mock requestMock;
    private Mock sessionMock;


    public void testClearInvalidatesTheSession() throws Exception {
        List<String> attributeNames = new ArrayList<>();
        attributeNames.add("test");
        attributeNames.add("anotherTest");
        Enumeration<String> attributeNamesEnum = Collections.enumeration(attributeNames);

        MockSessionMap sessionMap = new MockSessionMap((HttpServletRequest) requestMock.proxy());
        // Note: getAttribute() calls no longer expected after fix to ensure descendant (not ancestor) calls are made for
        //   the SessionMap Map methods (i.e. the overrides are called, as expected).
        sessionMock.expect("setAttribute",
                new Constraint[] {
                    new IsEqual("test"), new IsEqual("test value")
                });
        sessionMock.expect("setAttribute",
                new Constraint[] {
                    new IsEqual("anotherTest"), new IsEqual("another test value")
                });
        sessionMock.expectAndReturn("getAttributeNames", attributeNamesEnum);
        sessionMock.expect("removeAttribute",
                new Constraint[]{
                    new IsEqual("test")
                });
        sessionMock.expect("removeAttribute",
                new Constraint[]{
                    new IsEqual("anotherTest")
                });
        sessionMap.put("test", "test value");
        sessionMap.put("anotherTest", "another test value");
        sessionMap.clear();
        assertNull(sessionMap.get("test"));
        assertNull(sessionMap.get("anotherTest"));
        sessionMock.verify();
    }

    public void testGetOnSessionMapUsesWrappedSessionsGetAttribute() throws Exception {
        Object value = new Object();
        sessionMock.expectAndReturn("getAttribute", new Constraint[]{
                new IsEqual("KEY")
        }, value);

        SessionMap sessionMap = new SessionMap((HttpServletRequest) requestMock.proxy());
        assertEquals("Expected the get using KEY to return the value object setup in the mockSession", value, sessionMap.get("KEY"));
        sessionMock.verify();
    }

    public void testPutOnSessionMapUsesWrappedSessionsSetsAttribute() throws Exception {
        Object value = new Object();
        sessionMock.expect("getAttribute", new Constraint[]{new IsAnything()});
        sessionMock.expect("setAttribute", new Constraint[]{
                new IsEqual("KEY"), new IsEqual(value)
        });

        SessionMap sessionMap = new SessionMap((HttpServletRequest) requestMock.proxy());
        sessionMap.put("KEY", value);
        sessionMock.verify();
    }

    public void testGetObjectOnSessionMapUsesWrappedSessionsGetAttributeWithStringValue() throws Exception {
        String key = "theKey";
        Object value = new Object();
        sessionMock.expectAndReturn("getAttribute", new Constraint[]{
                new IsEqual(key)
        }, value);

        SessionMap sessionMap = new SessionMap((HttpServletRequest) requestMock.proxy());
        assertEquals("Expected the get using KEY to return the value object setup in the mockSession", value, sessionMap.get(key));
        sessionMock.verify();
    }

    public void testPutObjectOnSessionMapUsesWrappedSessionsSetsAttributeWithStringValue() throws Exception {
        String key = "theKey";
        Object value = new Object();
        sessionMock.expect("getAttribute", new Constraint[]{new IsAnything()});
        sessionMock.expect("setAttribute", new Constraint[]{
                new IsEqual(key), new IsEqual(value)
        });

        SessionMap sessionMap = new SessionMap((HttpServletRequest) requestMock.proxy());
        sessionMap.put(key, value);
        sessionMock.verify();
    }
    
    public void testContainsKeyWillFindAnObjectPutOnSessionMap() throws Exception {
    	
    	MockHttpServletRequest request = new MockHttpServletRequest();
    	
        String key = "theKey";
        Object value = new Object();
        
        SessionMap sessionMap = new SessionMap(request);
        sessionMap.put(key, value);
        assertTrue(sessionMap.containsKey(key));
    }

    public void testContainsKeyWillReturnFalseIfObjectNotFoundOnSessionMap() throws Exception {
    	
    	MockHttpServletRequest request = new MockHttpServletRequest();
    	
        String key = "theKey";
        String someOtherKey = "someOtherKey";
        Object value = new Object();
        
        SessionMap sessionMap = new SessionMap(request);
        sessionMap.put(key, value);
        
        assertFalse(sessionMap.containsKey(someOtherKey));
    }

    public void testPuttingObjectInMapReturnsNullForPreviouslyUnusedKey() throws Exception {
        Object value = new Object();
        sessionMock.expectAndReturn("getAttribute", new Constraint[]{
                new IsEqual("KEY")
        }, null);
        sessionMock.expect("setAttribute", new Constraint[]{
                new IsEqual("KEY"), new IsEqual(value)
        });

        SessionMap sessionMap = new SessionMap((HttpServletRequest) requestMock.proxy());
        assertNull("should be null, as the contract for Map says that put returns the previous value in the map for the key", sessionMap.put("KEY", value));
        sessionMock.verify();
    }

    public void testPuttingObjectInMapReturnsPreviousValueForKey() throws Exception {
        Object originalValue = new Object();
        Object value = new Object();
        sessionMock.expectAndReturn("getAttribute", new Constraint[]{
                new IsEqual("KEY")
        }, null);
        sessionMock.expect("setAttribute", new Constraint[]{
                new IsEqual("KEY"), new IsEqual(originalValue)
        });
        sessionMock.expectAndReturn("getAttribute", new Constraint[]{
                new IsEqual("KEY")
        }, originalValue);
        sessionMock.expect("setAttribute", new Constraint[]{
                new IsEqual("KEY"), new IsEqual(value)
        });

        SessionMap sessionMap = new SessionMap((HttpServletRequest) requestMock.proxy());
        sessionMap.put("KEY", originalValue);
        assertEquals("should be the OriginalValue, as the contract for Map says that put returns the previous value in the map for the key", originalValue, sessionMap.put("KEY", value));
        sessionMock.verify();
    }

    public void testRemovePassThroughCallToRemoveAttribute() throws Exception {
        Object value = new Object();
        sessionMock.expectAndReturn("getAttribute", new Constraint[]{
                new IsEqual("KEY")
        }, value);
        sessionMock.expect("removeAttribute", new Constraint[]{
                new IsEqual("KEY")
        });

        SessionMap sessionMap = new SessionMap((HttpServletRequest) requestMock.proxy());
        assertEquals(value, sessionMap.remove("KEY"));
        sessionMock.verify();
    }

    /** 
     * Attempt to detect any changes that would make the attribute handling for puts produce different results
     * for the SessionMap and underlying HttpSession attributes.
     * 
     * @throws Exception 
     */
    public void testPutResultInSessionAttributes() throws Exception {
        Object value = new Object();

        //HttpSession httpSessionMock = ((HttpServletRequest) requestMock.proxy()).getSession(false);
        HttpSession httpSessionMock = (HttpSession) sessionMock.proxy();
        sessionMock.expectAndReturn("getAttribute", new Constraint[]{
                new IsEqual("KEY")
        }, null);
        sessionMock.expect("setAttribute", new Constraint[]{
                new IsEqual("KEY"), new IsEqual(value)
        });
        sessionMock.expectAndReturn("getAttribute", new Constraint[]{
                new IsEqual("KEY")
        }, value);
        sessionMock.expectAndReturn("getAttribute", new Constraint[]{
                new IsEqual("KEY")
        }, value);

        SessionMap sessionMap = new SessionMap((HttpServletRequest) requestMock.proxy());
        AbstractMap<String, Object> abstractMap = (AbstractMap<String, Object>) sessionMap;
        abstractMap.put("KEY", value);
        assertEquals("Underlying HttpSession attribute does not match after SessionMap put ?", abstractMap.get("KEY"), httpSessionMock.getAttribute("KEY"));
        sessionMock.verify();
    }

    /**
     * Attempt to detect any changes that would make the attribute handling for removes produce different results
     * for the SessionMap and underlying HttpSession attributes.
     * 
     * @throws Exception 
     */
    public void testRemoveResultInSessionAttributes() throws Exception {
        Object value = new Object();
        Object removedValue;

        //HttpSession httpSessionMock = ((HttpServletRequest) requestMock.proxy()).getSession(false);
        HttpSession httpSessionMock = (HttpSession) sessionMock.proxy();
        sessionMock.expectAndReturn("getAttribute", new Constraint[]{
                new IsEqual("KEY")
        }, null);
        sessionMock.expect("setAttribute", new Constraint[]{
                new IsEqual("KEY"), new IsEqual(value)
        });
        sessionMock.expectAndReturn("getAttribute", new Constraint[]{
                new IsEqual("KEY")
        }, value);
        sessionMock.expect("removeAttribute", new Constraint[]{
                new IsEqual("KEY")
        });
        sessionMock.expectAndReturn("getAttribute", new Constraint[]{
                new IsEqual("KEY")
        }, null);
        sessionMock.expectAndReturn("getAttribute", new Constraint[]{
                new IsEqual("KEY")
        }, null);

        SessionMap sessionMap = new SessionMap((HttpServletRequest) requestMock.proxy());
        AbstractMap<String, Object> abstractMap = (AbstractMap<String, Object>) sessionMap;
        abstractMap.put("KEY", value);
        removedValue = abstractMap.remove("KEY");
        assertEquals("Removed attribute not equal to put attribute ?", value, removedValue);
        assertNull("Removed attribute still present in SessionMap ?", abstractMap.get("KEY"));
        assertNull("Removed attribute still present in HttpSessionMock ?", httpSessionMock.getAttribute("KEY"));
        sessionMock.verify();
    }

    @Override
    protected void setUp() throws Exception {
        sessionMock = new Mock(HttpSession.class);
        sessionMock.matchAndReturn("getId", "1");
        requestMock = new Mock(HttpServletRequest.class);
        requestMock.matchAndReturn("getSession", new Constraint[]{new IsEqual(Boolean.FALSE)}, sessionMock.proxy());
    }


    /**
     * class that extends session map, making the values available in a local map -- useful
     * for confirming put and get calls in the superclass. ie useful for testing that the get is done before
     * putting new data into the map.
     */
    private class MockSessionMap extends SessionMap {

        private static final long serialVersionUID = 8783604360786273764L;

        private final Map<String, Object> map;

        public MockSessionMap(HttpServletRequest request) {
            super(request);
            this.map = new HashMap<>();
        }

        @Override
        public Object get(Object key) {
            return map.get(key);
        }
        
        @Override
        public Object put(String key, Object value) {
            Object originalValue = super.put(key, value);
            map.put(key, value); //put the value into our map after putting it in the superclass map to avoid polluting the get call.

            return originalValue;
        }

        @Override
        public void clear() {
            super.clear();
            map.clear();
        }
    }
}
