ExtensionTest.java

/**
 * Copyright (c) 2017, RTE (http://www.rte-france.com)
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 * SPDX-License-Identifier: MPL-2.0
 */
package com.powsybl.commons.extensions;

import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.ser.DefaultSerializerProvider;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.powsybl.commons.PowsyblException;
import com.powsybl.commons.json.JsonUtil;
import com.powsybl.commons.test.AbstractSerDeTest;
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import org.mockito.Mockito;

import java.io.IOException;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author Mathieu Bague {@literal <mathieu.bague at rte-france.com>}
 */
class ExtensionTest extends AbstractSerDeTest {

    private static final Supplier<ExtensionProviders<ExtensionJsonSerializer>> SUPPLIER =
            Suppliers.memoize(() -> ExtensionProviders.createProvider(ExtensionJsonSerializer.class, "test"));

    @Test
    void testExtendable() {
        Foo foo = new Foo();
        FooExt fooExt = new FooExt(true);
        BarExt barExt = new BarExt(false);

        foo.addExtension(FooExt.class, fooExt);
        foo.addExtension(BarExt.class, barExt);
        assertEquals(2, foo.getExtensions().size());

        assertSame(fooExt, foo.getExtension(FooExt.class));
        assertSame(fooExt, foo.getExtensionByName("FooExt"));

        ExtensionProvider provider = SUPPLIER.get().findProvider("FooExt");
        assertNotNull(provider);
        assertSame(fooExt, foo.getExtension(provider.getExtensionClass()));
        assertSame(fooExt, foo.getExtensionByName(provider.getExtensionName()));

        assertSame(barExt, foo.getExtension(BarExt.class));
        assertSame(barExt, foo.getExtensionByName("BarExt"));

        assertTrue(foo.removeExtension(FooExt.class));
        assertNull(foo.getExtension(FooExt.class));
        assertEquals(1, foo.getExtensions().size());

        assertTrue(foo.removeExtension(BarExt.class));
        assertNull(foo.getExtension(BarExt.class));
        assertEquals(0, foo.getExtensions().size());
    }

    @Test
    void testExtensionSupplier() {
        assertNotNull(SUPPLIER.get().findProvider("FooExt"));
        assertNotNull(SUPPLIER.get().findProviderOrThrowException("FooExt"));
        assertNotNull(SUPPLIER.get().findProvider("BarExt"));

        ExtensionProviders<? extends ExtensionJsonSerializer> supplier = ExtensionProviders.createProvider(ExtensionJsonSerializer.class, "test", Collections.singleton("FooExt"));
        assertNotNull(supplier);
        assertNotNull(supplier.findProviderOrThrowException("FooExt"));
        assertNull(supplier.findProvider("BarExt"));
        try {
            supplier.findProviderOrThrowException("BarExt");
            fail();
        } catch (PowsyblException e) {
            // Nothing to do
        }
    }

    @Test
    void testReadJson() throws IOException {
        Foo foo = FooDeserializer.read(getClass().getResourceAsStream("/extensions.json"));
        assertEquals(1, foo.getExtensions().size());
        assertNotNull(foo.getExtension(FooExt.class));
        assertNull(foo.getExtension(BarExt.class));
    }

    private void assertBadExtensionJsonThrows(Executable runnable) {
        PowsyblException exception = assertThrows(PowsyblException.class, runnable);
        assertTrue(exception.getMessage().contains("\"extensions\""),
                "Exception should be about bad field exceptions, but got: " + exception.getMessage());
    }

    @Test
    void testBadReadJson() throws IOException {
        assertBadExtensionJsonThrows(() -> {
            Foo foo = FooDeserializer.read(getClass().getResourceAsStream("/BadExtensions.json"));
        });
    }

    @Test
    void testWriteJson() throws IOException {
        Files.createFile(tmpDir.resolve("extensions.json"));
        ExtensionProviders<? extends ExtensionJsonSerializer> supplier = ExtensionProviders.createProvider(ExtensionJsonSerializer.class, "test", Collections.singleton("FooExt"));
        Foo foo = new Foo();
        FooExt fooExt = new FooExt(true);
        BarExt barExt = new BarExt(false);
        foo.addExtension(FooExt.class, fooExt);
        foo.addExtension(BarExt.class, barExt);

        try (JsonGenerator jsonGen = new JsonFactory().createGenerator(Files.newOutputStream(tmpDir.resolve("extensions.json")), JsonEncoding.UTF8)) {
            jsonGen.writeStartObject();
            Set<String> notFound = JsonUtil.writeExtensions(foo, jsonGen, new DefaultSerializerProvider.Impl(), supplier);
            jsonGen.writeEndObject();
            assertNotNull(notFound);
            assertFalse(notFound.isEmpty());
            assertEquals(1, notFound.size());
            assertTrue(notFound.contains("BarExt"));
        }
    }

    @Test
    void testUpdateAndDeserialize() throws IOException {
        Foo foo = new Foo();
        FooExt fooExt = new FooExt(false, "Hello");
        foo.addExtension(FooExt.class, fooExt);
        FooDeserializer.update(getClass().getResourceAsStream("/extensionsUpdate.json"), foo);
        assertTrue(foo.getExtension(FooExt.class).getValue());
        assertEquals("Hello", foo.getExtension(FooExt.class).getValue2());
    }

    @Test
    void testBadUpdateAndDeserialize() throws IOException {
        assertBadExtensionJsonThrows(() -> {
            Foo foo = new Foo();
            FooDeserializer.update(getClass().getResourceAsStream("/BadExtensions.json"), foo);
        });
    }

    @Test
    void testUpdateWith2Extensions() throws IOException {
        Foo foo = new Foo();
        FooExt fooExt = new FooExt(false, "Hello");
        BarExt barExt = new BarExt(true);
        foo.addExtension(FooExt.class, fooExt);
        foo.addExtension(BarExt.class, barExt);
        FooDeserializer.update(getClass().getResourceAsStream("/extensions.json"), foo);
        assertTrue(foo.getExtension(FooExt.class).getValue());
        assertEquals("Hello", foo.getExtension(FooExt.class).getValue2());
        assertFalse(foo.getExtension(BarExt.class).getValue());
    }

    @Test
    void testProviderConflict() {
        ExtensionSerDe<?, ?> mock1 = Mockito.mock(ExtensionSerDe.class);
        Mockito.when(mock1.getExtensionName()).thenReturn("mock");
        ExtensionSerDe<?, ?> mock2 = Mockito.mock(ExtensionSerDe.class);
        Mockito.when(mock2.getExtensionName()).thenReturn("mock");

        ExtensionSerDe<?, ?>[] mocks = {mock1, mock2};

        assertThrows(IllegalStateException.class, () -> Arrays.stream(mocks).collect(Collectors.toMap(ExtensionSerDe::getExtensionName, e -> e)));
    }
}