Newer
Older
vavr-effect / src / test / java / com / github / tonivade / vavr / effect / ResourceTest.java
/*
 * Copyright (c) 2024, Antonio Gabriel Muñoz Conejo <me at tonivade dot es>
 * Distributed under the terms of the MIT License
 */
package com.github.tonivade.vavr.effect;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import io.vavr.CheckedConsumer;
import io.vavr.Tuple2;

@ExtendWith(MockitoExtension.class)
class ResourceTest {

  private <T extends AutoCloseable> Resource<T> makeResource(IO<T> acquire) {
    return makeResource(acquire, AutoCloseable::close);
  }

  private <T> Resource<T> makeResource(IO<T> acquire, CheckedConsumer<T> release) {
    return Resource.from(acquire, release);
  }

  @Test
  void use(@Mock CheckedConsumer<String> release) throws Throwable {
    Resource<String> resource = makeResource(IO.pure("hola"), release);

    IO<String> use = resource.use(string -> IO.pure(string.toUpperCase()));

    assertEquals("HOLA", use.unsafeRunSync());
    verify(release).accept("hola");
  }

  @Test
  void map(@Mock CheckedConsumer<String> release) throws Throwable {
    Resource<String> resource = makeResource(IO.pure("hola"), release).map(String::toUpperCase);

    IO<Integer> use = resource.use(string -> IO.pure(string.length()));

    assertEquals(4, use.unsafeRunSync());
    verify(release).accept("hola");
  }

  @Test
  void flatMap(@Mock DataSource dataSource, @Mock Connection connection,
      @Mock PreparedStatement statement, @Mock ResultSet resultSet) throws SQLException {
    when(dataSource.getConnection()).thenReturn(connection);
    when(connection.prepareStatement("sql")).thenReturn(statement);
    when(statement.executeQuery()).thenReturn(resultSet);
    when(resultSet.getString(0)).thenReturn("result");

    Resource<ResultSet> flatMap = makeResource(IO.task(dataSource::getConnection))
      .flatMap(conn -> makeResource(IO.task(() -> conn.prepareStatement("sql"))))
      .flatMap(stmt -> makeResource(IO.task(() -> stmt.executeQuery())));

    IO<String> use = flatMap.use(rs -> IO.task(() -> rs.getString(0)));

    assertEquals("result", use.unsafeRunSync());
    InOrder inOrder = inOrder(resultSet, statement, connection);
    inOrder.verify(resultSet).close();
    inOrder.verify(statement).close();
    inOrder.verify(connection).close();
  }

  @Test
  void combine(@Mock CheckedConsumer<String> release1, @Mock CheckedConsumer<Integer> release2) throws Throwable {
    Resource<String> res1 = makeResource(IO.pure("hola"), release1);
    Resource<Integer> res2 = makeResource(IO.pure(5), release2);

    Resource<Tuple2<String, Integer>> combine = res1.combine(res2);

    IO<String> use = combine.use(tuple -> IO.task(tuple::toString));

    assertEquals("(hola, 5)", use.unsafeRunSync());
    verify(release1).accept("hola");
    verify(release2).accept(5);
  }
}