JsonNodeClaimTest.java

package com.auth0.jwt.impl;

import com.auth0.jwt.UserPojo;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.Claim;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.databind.node.MissingNode;
import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.hamcrest.collection.IsMapContaining;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.ArgumentMatchers;

import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import static com.auth0.jwt.impl.JWTParser.getDefaultObjectMapper;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.arrayContaining;
import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.hasKey;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.hamcrest.core.IsNull.nullValue;
import static org.junit.Assert.assertNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

public class JsonNodeClaimTest {

    private ObjectMapper mapper;

    @Rule
    public ExpectedException exception = ExpectedException.none();

    @Before
    public void setUp() {
        mapper = getDefaultObjectMapper();
    }

    @Test
    public void shouldGetBooleanValue() {
        JsonNode value = mapper.valueToTree(true);
        Claim claim = claimFromNode(value);

        assertThat(claim.asBoolean(), is(notNullValue()));
        assertThat(claim.asBoolean(), is(true));
    }

    private Claim claimFromNode(JsonNode value) {
        return JsonNodeClaim.claimFromNode(value, mapper);
    }

    @Test
    public void shouldGetNullBooleanIfNotBooleanValue() {
        JsonNode objectValue = mapper.valueToTree(new Object());
        assertThat(claimFromNode(objectValue).asBoolean(), is(nullValue()));
        JsonNode stringValue = mapper.valueToTree("boolean");
        assertThat(claimFromNode(stringValue).asBoolean(), is(nullValue()));
    }

    @Test
    public void shouldGetIntValue() {
        JsonNode value = mapper.valueToTree(123);
        Claim claim = claimFromNode(value);

        assertThat(claim.asInt(), is(notNullValue()));
        assertThat(claim.asInt(), is(123));
    }

    @Test
    public void shouldGetNullIntIfNotIntValue() {
        JsonNode objectValue = mapper.valueToTree(new Object());
        assertThat(claimFromNode(objectValue).asInt(), is(nullValue()));
        JsonNode stringValue = mapper.valueToTree("123");
        assertThat(claimFromNode(stringValue).asInt(), is(nullValue()));
    }

    @Test
    public void shouldGetLongValue() {
        JsonNode value = mapper.valueToTree(Long.MAX_VALUE);
        Claim claim = claimFromNode(value);

        assertThat(claim.asLong(), is(notNullValue()));
        assertThat(claim.asLong(), is(Long.MAX_VALUE));
    }

    @Test
    public void shouldGetNullLongIfNotIntValue() {
        JsonNode objectValue = mapper.valueToTree(new Object());
        assertThat(claimFromNode(objectValue).asLong(), is(nullValue()));
        JsonNode stringValue = mapper.valueToTree("" + Long.MAX_VALUE);
        assertThat(claimFromNode(stringValue).asLong(), is(nullValue()));
    }

    @Test
    public void shouldGetDoubleValue() {
        JsonNode value = mapper.valueToTree(1.5);
        Claim claim = claimFromNode(value);

        assertThat(claim.asDouble(), is(notNullValue()));
        assertThat(claim.asDouble(), is(1.5));
    }

    @Test
    public void shouldGetNullDoubleIfNotDoubleValue() {
        JsonNode objectValue = mapper.valueToTree(new Object());
        assertThat(claimFromNode(objectValue).asDouble(), is(nullValue()));
        JsonNode stringValue = mapper.valueToTree("123.23");
        assertThat(claimFromNode(stringValue).asDouble(), is(nullValue()));
    }

    @Test
    public void shouldGetNumericDateValue() {
        long seconds = 1476824844L;
        JsonNode value = mapper.valueToTree(seconds);
        Claim claim = claimFromNode(value);

        assertThat(claim.asDate(), is(new Date(seconds * 1000)));
        assertThat(claim.asInstant(), is(Instant.ofEpochSecond(seconds)));
    }

    @Test
    public void shouldGetNullIfNotNumericDateValue() {
        JsonNode objectValue = mapper.valueToTree(new Object());
        assertThat(claimFromNode(objectValue).asDate(), is(nullValue()));
        assertThat(claimFromNode(objectValue).asInstant(), is(nullValue()));
        JsonNode stringValue = mapper.valueToTree("1476824844");
        assertThat(claimFromNode(stringValue).asDate(), is(nullValue()));
        assertThat(claimFromNode(stringValue).asInstant(), is(nullValue()));
    }

    @Test
    public void shouldGetStringValue() {
        JsonNode value = mapper.valueToTree("string");
        Claim claim = claimFromNode(value);

        assertThat(claim.asString(), is(notNullValue()));
        assertThat(claim.asString(), is("string"));
    }

    @Test
    public void shouldGetNullStringIfNotStringValue() {
        JsonNode objectValue = mapper.valueToTree(new Object());
        assertThat(claimFromNode(objectValue).asString(), is(nullValue()));
        JsonNode intValue = mapper.valueToTree(12345);
        assertThat(claimFromNode(intValue).asString(), is(nullValue()));
    }

    @Test
    public void shouldGetArrayValueOfCustomClass() {
        JsonNode value = mapper.valueToTree(new UserPojo[]{new UserPojo("George", 1), new UserPojo("Mark", 2)});
        Claim claim = claimFromNode(value);

        assertThat(claim.asArray(UserPojo.class), is(notNullValue()));
        assertThat(claim.asArray(UserPojo.class), is(arrayContaining(new UserPojo("George", 1), new UserPojo("Mark", 2))));
    }

    @Test
    public void shouldGetArrayValue() {
        JsonNode value = mapper.valueToTree(new String[]{"string1", "string2"});
        Claim claim = claimFromNode(value);

        assertThat(claim.asArray(String.class), is(notNullValue()));
        assertThat(claim.asArray(String.class), is(arrayContaining("string1", "string2")));
    }

    @Test
    public void shouldGetNullArrayIfNullValue() {
        JsonNode value = mapper.valueToTree(null);
        Claim claim = claimFromNode(value);

        assertThat(claim.asArray(String.class), is(nullValue()));
    }

    @Test
    public void shouldGetNullArrayIfNonArrayValue() {
        JsonNode value = mapper.valueToTree(1);
        Claim claim = claimFromNode(value);

        assertThat(claim.asArray(String.class), is(nullValue()));
    }

    @Test
    public void shouldThrowIfArrayClassMismatch() {
        JsonNode value = mapper.valueToTree(new String[]{"keys", "values"});
        Claim claim = claimFromNode(value);

        exception.expect(JWTDecodeException.class);
        claim.asArray(UserPojo.class);
    }

    @Test
    public void shouldGetListValueOfCustomClass() {
        JsonNode value = mapper.valueToTree(Arrays.asList(new UserPojo("George", 1), new UserPojo("Mark", 2)));
        Claim claim = claimFromNode(value);

        assertThat(claim.asList(UserPojo.class), is(notNullValue()));
        assertThat(claim.asList(UserPojo.class), is(hasItems(new UserPojo("George", 1), new UserPojo("Mark", 2))));
    }

    @Test
    public void shouldGetListValue() {
        JsonNode value = mapper.valueToTree(Arrays.asList("string1", "string2"));
        Claim claim = claimFromNode(value);

        assertThat(claim.asList(String.class), is(notNullValue()));
        assertThat(claim.asList(String.class), is(hasItems("string1", "string2")));
    }

    @Test
    public void shouldGetNullListIfNullValue() {
        JsonNode value = mapper.valueToTree(null);
        Claim claim = claimFromNode(value);

        assertThat(claim.asList(String.class), is(nullValue()));
    }

    @Test
    public void shouldGetNullListIfNonArrayValue() {
        JsonNode value = mapper.valueToTree(1);
        Claim claim = claimFromNode(value);

        assertThat(claim.asList(String.class), is(nullValue()));
    }

    @Test
    public void shouldThrowIfListClassMismatch() {
        JsonNode value = mapper.valueToTree(new String[]{"keys", "values"});
        Claim claim = claimFromNode(value);

        exception.expect(JWTDecodeException.class);
        claim.asList(UserPojo.class);
    }

    @Test
    public void shouldGetNullMapIfNullValue() {
        JsonNode value = mapper.valueToTree(null);
        Claim claim = claimFromNode(value);

        assertThat(claim.asMap(), is(nullValue()));
    }

    @Test
    public void shouldGetNullMapIfNonArrayValue() {
        JsonNode value = mapper.valueToTree(1);
        Claim claim = claimFromNode(value);

        assertThat(claim.asMap(), is(nullValue()));
    }

    @Test
    public void shouldGetMapValue() {
        Map<String, Object> map = new HashMap<>();
        map.put("text", "extraValue");
        map.put("number", 12);
        map.put("boolean", true);
        map.put("object", Collections.singletonMap("something", "else"));

        JsonNode value = mapper.valueToTree(map);
        Claim claim = claimFromNode(value);

        assertThat(claim, is(notNullValue()));
        Map<String, Object> backMap = claim.asMap();
        assertThat(backMap, is(notNullValue()));
        assertThat(backMap, hasEntry("text", (Object) "extraValue"));
        assertThat(backMap, hasEntry("number", (Object) 12));
        assertThat(backMap, hasEntry("boolean", (Object) true));
        assertThat(backMap, hasKey("object"));
        assertThat((Map<String, Object>) backMap.get("object"), IsMapContaining.hasEntry("something", (Object) "else"));
    }

    @Test
    public void shouldThrowIfAnExtraordinaryExceptionHappensWhenParsingAsGenericMap() throws Exception {
        JsonNode value = mock(ObjectNode.class);
        when(value.getNodeType()).thenReturn(JsonNodeType.OBJECT);

        ObjectMapper mockedMapper = mock(ObjectMapper.class);

        JsonNodeClaim claim = (JsonNodeClaim) JsonNodeClaim.claimFromNode(value, mockedMapper);
        JsonNodeClaim spiedClaim = spy(claim);
        
        JsonParser mockedParser = mock(JsonParser.class);
        when(mockedMapper.treeAsTokens(value)).thenReturn(mockedParser);
        when(mockedParser.readValueAs(ArgumentMatchers.any(TypeReference.class))).thenThrow(IOException.class);

        exception.expect(JWTDecodeException.class);
        spiedClaim.asMap();
    }

    @Test
    public void shouldGetCustomClassValue() {
        JsonNode value = mapper.valueToTree(new UserPojo("john", 123));
        Claim claim = claimFromNode(value);

        assertThat(claim, is(notNullValue()));
        assertThat(claim.as(UserPojo.class).getName(), is("john"));
        assertThat(claim.as(UserPojo.class).getId(), is(123));
    }

    @Test
    public void shouldThrowIfCustomClassMismatch() {
        JsonNode value = mapper.valueToTree(new UserPojo("john", 123));
        Claim claim = claimFromNode(value);

        exception.expect(JWTDecodeException.class);
        claim.as(String.class);
    }

    @Test
    public void shouldReturnNullForMissingAndNullClaims() {
        JsonNode missingValue = MissingNode.getInstance();
        Claim missingClaim = claimFromNode(missingValue);
        assertThat(missingClaim.isMissing(), is(true));
        assertThat(missingClaim.isNull(), is(false));
        assertNull(missingClaim.as(String.class));
        assertNull(missingClaim.asString());
        assertNull(missingClaim.asBoolean());
        assertNull(missingClaim.asDate());
        assertNull(missingClaim.asDouble());
        assertNull(missingClaim.asLong());
        assertNull(missingClaim.asInt());
        assertNull(missingClaim.asInstant());
        assertNull(missingClaim.asMap());
        assertNull(missingClaim.asList(String.class));
        assertNull(missingClaim.asArray(String.class));

        JsonNode nullValue = mapper.valueToTree(null);
        Claim nullClaim = claimFromNode(nullValue);
        assertThat(nullClaim.isMissing(), is(false));
        assertThat(nullClaim.isNull(), is(true));
        assertNull(nullClaim.as(String.class));
        assertNull(nullClaim.asString());
        assertNull(nullClaim.asBoolean());
        assertNull(nullClaim.asDate());
        assertNull(nullClaim.asDouble());
        assertNull(nullClaim.asLong());
        assertNull(nullClaim.asInt());
        assertNull(nullClaim.asInstant());
        assertNull(nullClaim.asMap());
        assertNull(nullClaim.asList(String.class));
        assertNull(nullClaim.asArray(String.class));
    }

    @Test
    public void shouldReturnNullForInvalidArrayValue() {
        JsonNode value = mapper.valueToTree(new UserPojo("john", 123));
        Claim claim = claimFromNode(value);
        assertNull(claim.asArray(String.class));
    }

    @SuppressWarnings({"unchecked", "RedundantCast"})
    @Test
    public void shouldGetAsMapValue() {
        JsonNode value = mapper.valueToTree(Collections.singletonMap("key", new UserPojo("john", 123)));
        Claim claim = claimFromNode(value);

        assertThat(claim, is(notNullValue()));
        Map map = claim.as(Map.class);
        assertThat(((Map<String, Object>) map.get("key")), hasEntry("name", (Object) "john"));
        assertThat(((Map<String, Object>) map.get("key")), hasEntry("id", (Object) 123));
    }

    @Test
    public void shouldReturnBaseClaimWhenParsingMissingNode() {
        JsonNode value = MissingNode.getInstance();
        Claim claim = claimFromNode(value);

        assertThat(claim, is(notNullValue()));
        assertThat(claim.isMissing(), is(true));
        assertThat(claim.isNull(), is(false));
    }

    @Test
    public void shouldReturnBaseClaimWhenParsingNullNode() {
        JsonNode value = NullNode.getInstance();
        Claim claim = claimFromNode(value);

        assertThat(claim, is(notNullValue()));
        assertThat(claim.isNull(), is(true));
        assertThat(claim.isMissing(), is(false));
    }

    @Test
    public void shouldReturnBaseClaimWhenParsingNullValue() {
        JsonNode value = mapper.valueToTree(null);
        Claim claim = claimFromNode(value);

        assertThat(claim, is(notNullValue()));
        assertThat(claim.isNull(), is(true));
        assertThat(claim.isMissing(), is(false));
    }

    @Test
    public void shouldReturnNonNullClaimWhenParsingObject() {
        JsonNode value = mapper.valueToTree(new Object());
        Claim claim = claimFromNode(value);

        assertThat(claim, is(notNullValue()));
        assertThat(claim, is(instanceOf(JsonNodeClaim.class)));
        assertThat(claim.isNull(), is(false));
        assertThat(claim.isMissing(), is(false));
    }

    @Test
    public void shouldReturnNonNullClaimWhenParsingArray() {
        JsonNode value = mapper.valueToTree(new String[]{});
        Claim claim = claimFromNode(value);

        assertThat(claim, is(notNullValue()));
        assertThat(claim, is(instanceOf(JsonNodeClaim.class)));
        assertThat(claim.isNull(), is(false));
        assertThat(claim.isMissing(), is(false));
    }

    @Test
    public void shouldReturnNonNullClaimWhenParsingList() {
        JsonNode value = mapper.valueToTree(new ArrayList<String>());
        Claim claim = claimFromNode(value);

        assertThat(claim, is(notNullValue()));
        assertThat(claim, is(instanceOf(JsonNodeClaim.class)));
        assertThat(claim.isNull(), is(false));
        assertThat(claim.isMissing(), is(false));
    }

    @Test
    public void shouldReturnNonNullClaimWhenParsingStringValue() {
        JsonNode value = mapper.valueToTree("");
        Claim claim = claimFromNode(value);

        assertThat(claim, is(notNullValue()));
        assertThat(claim, is(instanceOf(JsonNodeClaim.class)));
        assertThat(claim.isNull(), is(false));
        assertThat(claim.isMissing(), is(false));
    }

    @Test
    public void shouldReturnNonNullClaimWhenParsingIntValue() {
        JsonNode value = mapper.valueToTree(Integer.MAX_VALUE);
        Claim claim = claimFromNode(value);

        assertThat(claim, is(notNullValue()));
        assertThat(claim, is(instanceOf(JsonNodeClaim.class)));
        assertThat(claim.isNull(), is(false));
        assertThat(claim.isMissing(), is(false));
    }

    @Test
    public void shouldReturnNonNullClaimWhenParsingDoubleValue() {
        JsonNode value = mapper.valueToTree(Double.MAX_VALUE);
        Claim claim = claimFromNode(value);

        assertThat(claim, is(notNullValue()));
        assertThat(claim, is(instanceOf(JsonNodeClaim.class)));
        assertThat(claim.isNull(), is(false));
        assertThat(claim.isMissing(), is(false));
    }

    @Test
    public void shouldReturnNonNullClaimWhenParsingDateValue() {
        JsonNode value = mapper.valueToTree(new Date());
        Claim claim = claimFromNode(value);

        assertThat(claim, is(notNullValue()));
        assertThat(claim, is(instanceOf(JsonNodeClaim.class)));
        assertThat(claim.isNull(), is(false));
        assertThat(claim.isMissing(), is(false));
    }

    @Test
    public void shouldReturnNonNullClaimWhenParsingBooleanValue() {
        JsonNode value = mapper.valueToTree(Boolean.TRUE);
        Claim claim = claimFromNode(value);

        assertThat(claim, is(notNullValue()));
        assertThat(claim, is(instanceOf(JsonNodeClaim.class)));
        assertThat(claim.isNull(), is(false));
        assertThat(claim.isMissing(), is(false));
    }

    @Test
    public void shouldReturnNullIsTrue() {
        JsonNode value = mapper.valueToTree(null);
        Claim claim = claimFromNode(value);

        assertThat(claim, is(notNullValue()));
        assertThat(claim, is(instanceOf(JsonNodeClaim.class)));
        assertThat(claim.isNull(), is(true));
        assertThat(claim.isMissing(), is(false));
    }

    @Test
    public void shouldDelegateToJsonNodeToString() {
        JsonNode value = mapper.valueToTree(new UserPojo("john", 123));
        Claim claim = claimFromNode(value);
        assertThat(claim.toString(), is(value.toString()));
    }

    @Test
    public void shouldConvertToString() {
        JsonNode value = mapper.valueToTree(new UserPojo("john", 123));
        JsonNode nullValue = mapper.valueToTree(null);
        JsonNode missingValue = MissingNode.getInstance();

        Claim claim = claimFromNode(value);
        Claim nullClaim = claimFromNode(nullValue);
        Claim missingClaim = claimFromNode(missingValue);

        assertThat(claim.toString(), is("{\"name\":\"john\",\"id\":123}"));
        assertThat(nullClaim.isNull(), is(true));
        assertThat(nullClaim.toString(), is("Null claim"));
        assertThat(missingClaim.isMissing(), is(true));
        assertThat(missingClaim.toString(), is("Missing claim"));

    }
}