/*
 * Copyright 2019, TeamDev. All rights reserved.
 *
 * Redistribution and use in source and/or binary forms, with or without
 * modification, must retain the above copyright notice and the following
 * disclaimer.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package io.spine.server.projection;

import com.google.protobuf.Message;
import io.spine.core.Event;
import io.spine.core.Version;
import io.spine.core.Versions;
import io.spine.server.dispatch.DispatchOutcome;
import io.spine.server.entity.Transaction;
import io.spine.server.entity.TransactionListener;
import io.spine.server.entity.TransactionTest;
import io.spine.server.entity.VersionIncrement;
import io.spine.server.entity.given.tx.Id;
import io.spine.server.entity.given.tx.ProjectionState;
import io.spine.server.entity.given.tx.ProjectionState.ProjectionType;
import io.spine.server.entity.given.tx.TxProjection;
import io.spine.server.entity.given.tx.event.TxCreated;
import io.spine.server.type.EventEnvelope;
import io.spine.server.type.given.GivenEvent;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static com.google.common.truth.Truth.assertThat;
import static io.spine.base.Time.currentTime;
import static io.spine.protobuf.AnyPacker.unpack;
import static io.spine.server.entity.given.tx.ProjectionState.ProjectionType.VERY_USEFUL;
import static java.util.Collections.singleton;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
 * Tests for {@link io.spine.server.projection.ProjectionTransaction}.
 */
@DisplayName("ProjectionTransaction should")
class ProjectionTransactionTest
        extends TransactionTest<Id,
        Projection<Id, ProjectionState, ProjectionState.Builder>,
        ProjectionState,
        ProjectionState.Builder> {

    @Override
    protected Transaction<Id,
            Projection<Id, ProjectionState, ProjectionState.Builder>,
            ProjectionState,
            ProjectionState.Builder>
    createTx(Projection<Id, ProjectionState, ProjectionState.Builder> entity) {
        return new ProjectionTransaction<>(entity);
    }

    @Override
    protected Transaction<Id,
            Projection<Id, ProjectionState, ProjectionState.Builder>,
            ProjectionState,
            ProjectionState.Builder>
    createTx(Projection<Id, ProjectionState, ProjectionState.Builder> entity,
             ProjectionState state,
             Version version) {
        return new ProjectionTransaction<>(entity, state, version);
    }

    @Override
    protected Transaction<Id,
                          Projection<Id, ProjectionState, ProjectionState.Builder>,
                          ProjectionState,
                          ProjectionState.Builder>
    createTx(Projection<Id, ProjectionState, ProjectionState.Builder> entity,
             TransactionListener<Id> listener) {
        ProjectionTransaction<Id, ProjectionState, ProjectionState.Builder> transaction =
                new ProjectionTransaction<>(entity);
        transaction.setListener(listener);
        return transaction;
    }

    @Override
    protected Projection<Id, ProjectionState, ProjectionState.Builder> createEntity() {
        return new TxProjection(id());
    }

    @Override
    protected ProjectionState newState() {
        String nameString = "The new name for the projection state in this tx";
        return ProjectionState
                .newBuilder()
                .setId(id())
                .setName(nameString)
                .setNameLength(nameString.length())
                .setType(VERY_USEFUL)
                .build();
    }


    @Override
    protected void checkEventReceived(
            Projection<Id, ProjectionState, ProjectionState.Builder> entity,
            Event event) {

        TxProjection aggregate = (TxProjection) entity;
        Message actualMessage = unpack(event.getMessage());
        assertTrue(aggregate.receivedEvents()
                            .contains(actualMessage));
    }

    @Override
    protected DispatchOutcome applyEvent(Transaction tx, Event event) {
        ProjectionTransaction cast = (ProjectionTransaction) tx;
        EventEnvelope envelope = EventEnvelope.of(event);
        return cast.play(envelope);
    }

    /**
     * Tests the version advancement strategy for the {@link Projection}s.
     *
     * <p>The versioning strategy for {@link Projection} is
     * {@link VersionIncrement.AutoIncrement}. This test case substitutes
     * {@link #advanceVersionFromEvent()}, which tested the behavior of
     * {@link VersionIncrement.IncrementFromEvent} strategy.
     */
    @SuppressWarnings({"CheckReturnValue", "ResultOfMethodCallIgnored"})
    // Can ignore value of play() in this test.
    @Test
    @DisplayName("increment version on event")
    void incrementVersionOnEvent() {
        Projection<Id, ProjectionState, ProjectionState.Builder> entity = createEntity();
        Version oldVersion = entity.version();
        Version eventVersion = Version
                .newBuilder()
                .setNumber(42)
                .setTimestamp(currentTime())
                .build();
        Event event = GivenEvent.withMessageAndVersion(createEventMessage(), eventVersion);
        Projection.playOn(entity, singleton(event));
        Version expected = Versions.increment(oldVersion);

        assertThat(entity.version()
                         .getNumber())
                .isEqualTo(expected.getNumber());
        assertThat(entity.version())
                .isNotEqualTo(event.context()
                                   .getVersion());
    }

    @Test
    @DisplayName("propagate column values to the entity state on commit")
    void propagateColumnValues() {
        TxProjection entity = (TxProjection) createEntity();
        String name = "some-projection-name";
        TxCreated txCreated = TxCreated
                .newBuilder()
                .setId(id())
                .setName(name)
                .build();
        Event event = GivenEvent.withMessage(txCreated);
        Projection.playOn(entity, singleton(event));

        int nameLength = entity.state()
                               .getNameLength();
        assertThat(nameLength).isEqualTo(entity.getNameLength());

        ProjectionType type = entity.state()
                                    .getType();
        assertThat(type).isEqualTo(entity.getType());
    }
}

@@ -2293,16 +2293,17 @@
 ayName(%22
+%60
 Projecti
@@ -2315,16 +2315,17 @@
 nsaction
+%60
  should%22
@@ -4721,32 +4721,98 @@
 ssage));%0A    %7D%0A%0A
+    @SuppressWarnings(%22rawtypes%22) // For the brevity of the test.%0A
     @Override%0A  

Clean up the `ProjectionTransactionTest`