/*
 * (c) 2020 Pluto Inc.
 */
package tv.pluto;

import static com.mongodb.client.model.Projections.include;
import static com.mongodb.client.model.Sorts.ascending;
import static com.mongodb.client.model.Sorts.descending;
import static com.mongodb.client.model.Sorts.orderBy;
import static tv.pluto.ip.dal.episode.Episodes.ChannelLibraryEpisode;
import static tv.pluto.ip.dal.episode.Episodes.CreateResponse;
import static tv.pluto.ip.dal.episode.Episodes.CustomReferenceValue;
import static tv.pluto.ip.dal.episode.Episodes.Episode;
import static tv.pluto.ip.dal.episode.Episodes.GetAgeRangePopulatedEpisodeByIdRequest;
import static tv.pluto.ip.dal.episode.Episodes.GetAllPagedResponse;
import static tv.pluto.ip.dal.episode.Episodes.GetAllPagedSort;
import static tv.pluto.ip.dal.episode.Episodes.GetEpisodesByClipIdRequest;
import static tv.pluto.ip.dal.episode.Episodes.UpdateFieldsResponse;
import static tv.pluto.mongo.utils.MongoUtils.INVALID_ID_VALUE;

import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.client.model.Projections;
import com.plutotv.test.lib.LogUtils;
import com.plutotv.test.lib.ttfb.grpc.GrpcCall;
import com.plutotv.test.lib.ttfb.grpc.TTFBGrpcValidator;
import io.grpc.Status;
import io.grpc.StatusRuntimeException;
import io.qameta.allure.Description;
import io.qameta.allure.Issue;
import io.qameta.allure.TmsLink;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.bson.conversions.Bson;
import org.bson.types.ObjectId;
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.testng.asserts.SoftAssert;
import tv.pluto.core.BaseTest;
import tv.pluto.grpc.episodes.EpisodesClient;
import tv.pluto.grpc.vod.VodClient;
import tv.pluto.mongo.dto.ContentFeedQuery;
import tv.pluto.mongo.entities.channels.Channels;
import tv.pluto.mongo.entities.clips.Clips;
import tv.pluto.mongo.entities.common.Backups;
import tv.pluto.mongo.entities.common.Content;
import tv.pluto.mongo.entities.common.Image;
import tv.pluto.mongo.entities.common.NextTimeline;
import tv.pluto.mongo.entities.common.ObjectIdContentIdSet;
import tv.pluto.mongo.entities.episodes.AgeRange;
import tv.pluto.mongo.entities.episodes.AgeRangeEpisode;
import tv.pluto.mongo.entities.episodes.CustomReferences;
import tv.pluto.mongo.entities.episodes.EpisodePopulated;
import tv.pluto.mongo.entities.episodes.Episodes;
import tv.pluto.mongo.entities.episodes.Source;
import tv.pluto.mongo.entities.episodes.get_channel_library.GetChannelLibraryRequest;
import tv.pluto.mongo.entities.episodes.get_channel_library.GetChannelLibraryResponse;
import tv.pluto.mongo.entities.series.Series;
import tv.pluto.mongo.entities.vodcategoryentries.VodCategoryEntries;
import tv.pluto.mongo.utils.ChannelsUtils;
import tv.pluto.mongo.utils.ClipsUtils;
import tv.pluto.mongo.utils.MongoUtils;
import tv.pluto.mongo.utils.SeriesUtils;
import tv.pluto.mongo.utils.VodCategoryEntriesUtils;
import tv.pluto.mongo.utils.episodes.AgeRangeEpisodeUtils;
import tv.pluto.mongo.utils.episodes.AgeRangeUtils;
import tv.pluto.mongo.utils.episodes.EpisodeUtils;
import tv.pluto.mongo.utils.episodes.get_channel_library.GetChannelLibraryUtils;
import tv.pluto.mongo.utils.populated_episodes.PopulatedEpisodesUtils;
import tv.pluto.utils.CommonUtils;

public class EpisodesTestSuite extends BaseTest {
  private EpisodesClient episodesClientNoMetadata;
  private EpisodesClient episodesClient;
  private EpisodesClient episodesClientAuth;
  private EpisodeUtils episodeUtils;
  private ClipsUtils clipsUtils;
  private AgeRangeUtils ageRangeUtils;
  private AgeRangeEpisodeUtils ageRangeEpisodeUtils;
  private SeriesUtils seriesUtils;
  private ChannelsUtils channelsUtils;
  private PopulatedEpisodesUtils populatedEpisodesUtils;
  private ChannelsUtils channelUtils;
  private VodCategoryEntriesUtils vodCategoryEntriesUtils;
  private GetChannelLibraryUtils getChannelLibraryUtils;
  private VodClient vodClient;

  @BeforeClass(alwaysRun = true)
  private void setUp() {
    episodesClientNoMetadata = new EpisodesClient(grpcChannelManager.getChannel());
    episodesClient = new EpisodesClient(grpcChannelManager.getChannel(), false);
    episodesClientAuth = new EpisodesClient(grpcChannelManager.getChannel(), true);
    episodeUtils = new EpisodeUtils();
    clipsUtils = new ClipsUtils();
    ageRangeUtils = new AgeRangeUtils();
    ageRangeEpisodeUtils = new AgeRangeEpisodeUtils();
    seriesUtils = new SeriesUtils();
    channelsUtils = new ChannelsUtils();
    populatedEpisodesUtils = new PopulatedEpisodesUtils();
    channelUtils = new ChannelsUtils();
    vodCategoryEntriesUtils = new VodCategoryEntriesUtils();
    getChannelLibraryUtils = new GetChannelLibraryUtils();
    vodClient = new VodClient(grpcChannelManager.getChannel());
  }

  @Test
  @Description("Valid request - GetEpisodeById")
  @TmsLink("28500777")
  public void C28500777_TEST() {
    Episodes mongoEpisode = episodeUtils.getRandomDocument();
    String randomEpisodeId = mongoEpisode.getId();

    Episodes mappedEpisode =
        episodeUtils.mapGrpcObjectToPojo(
            episodesClient.getEpisodeById(randomEpisodeId).getDocument(), Episodes.class);

    Assert.assertEquals(mongoEpisode, mappedEpisode);
  }

  @Test
  @Description("customReference in Episode response")
  @TmsLink("28500890")
  public void C28500890_TEST() {
    BasicDBObject query = new BasicDBObject("$exists", true).append("$size", 1);

    Episodes mongoEpisode = episodeUtils.getDocumentByKeyValueObject("customReferences", query);
    String randomEpisodeId = mongoEpisode.getId();

    Episodes mappedEpisode =
        episodeUtils.mapGrpcObjectToPojo(
            episodesClient.getEpisodeById(randomEpisodeId).getDocument(), Episodes.class);

    Assert.assertEquals(
        1,
        episodesClient
            .getEpisodeById(randomEpisodeId)
            .getDocument()
            .getCustomReferencesList()
            .size());
    Assert.assertEquals(mappedEpisode.getCustomReferences().size(), 1);
    Assert.assertEquals(mongoEpisode, mappedEpisode);
  }

  @Test(priority = -1)
  @Description("UpdateFields - updated customReference")
  @TmsLink("28500891")
  public void C28500891_TEST() {
    BasicDBObject query = new BasicDBObject("$exists", true).append("$size", 1);
    Episodes mongoEpisode = episodeUtils.getDocumentByKeyValueObject("customReferences", query);
    String randomEpisodeId = mongoEpisode.getId();

    List<String> listReferences =
        List.of("sponsorshipPartner", "entitlementPartner", "deliveryPartner");
    String updatedReferenceType = CommonUtils.getRandomValueFromList(listReferences);
    String id = mongoEpisode.getCustomReferences().get(0).getId();
    String value = mongoEpisode.getCustomReferences().get(0).getValue();
    LogUtils.logInfoMessage(
        "Episode type from: "
            + mongoEpisode.getCustomReferences().get(0).getType()
            + ", to: "
            + updatedReferenceType);

    CustomReferenceValue customReference =
        Episode.newBuilder()
            .addCustomReferencesBuilder()
            .setId(id)
            .setValue(value)
            .setType(updatedReferenceType)
            .build();

    Episode episode = Episode.newBuilder().addAllCustomReferences(List.of(customReference)).build();
    tv.pluto.ip.dal.episode.Episodes.UpdateFieldsResponse updateFields =
        episodesClient.getUpdateFields(randomEpisodeId, List.of("customReferences"), episode);

    Assert.assertEquals(
        updatedReferenceType, updateFields.getDocument().getCustomReferences(0).getType());

    Episodes mappedEpisode =
        episodeUtils.mapGrpcObjectToPojo(updateFields.getDocument(), Episodes.class);

    mongoEpisode.getCustomReferences().get(0).setType(updatedReferenceType);

    mappedEpisode.setUpdatedAt(mongoEpisode.getUpdatedAt());
    Assert.assertEquals(mongoEpisode, mappedEpisode);
  }

  @Test
  @Description("UpdateFields - INTERNAL error")
  @TmsLink("28500898")
  public void C28500898_TEST() {
    boolean isTrue = false;
    try {
      Episode episode = Episode.newBuilder().setName("test").build();
      episodesClient.getUpdateFields("test", List.of("name"), episode);
    } catch (Exception e) {
      isTrue = true;
      Assert.assertTrue(e.getMessage().contains("INTERNAL: Cast to ObjectId failed for value"));
    }
    Assert.assertTrue(isTrue);
  }

  @Test(
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp =
          "UNAUTHENTICATED: app_name must be provided in request metadata")
  @Description("UpdateFields - UNAUTHENTICATED: app_name must be provided in request metadata")
  @TmsLink("28500892")
  public void C28500892_TEST() {
    BasicDBObject query = new BasicDBObject("$exists", true).append("$size", 1);

    Episodes mongoEpisode = episodeUtils.getDocumentByKeyValueObject("customReferences", query);
    String randomEpisodeId = mongoEpisode.getId();

    List<String> listReferences =
        List.of("sponsorshipPartner", "entitlementPartner", "deliveryPartner");
    String updatedReferenceType = CommonUtils.getRandomValueFromList(listReferences);
    String id = mongoEpisode.getCustomReferences().get(0).getId();
    String value = mongoEpisode.getCustomReferences().get(0).getValue();
    LogUtils.logInfoMessage(
        "Episode type from: "
            + mongoEpisode.getCustomReferences().get(0).getType()
            + ", to: "
            + updatedReferenceType);

    CustomReferenceValue customReference =
        Episode.newBuilder()
            .addCustomReferencesBuilder()
            .setId(id)
            .setValue(value)
            .setType(updatedReferenceType)
            .build();

    Episode episode = Episode.newBuilder().addAllCustomReferences(List.of(customReference)).build();
    episodesClientNoMetadata.getUpdateFields(randomEpisodeId, List.of("customReferences"), episode);
  }

  @Test(priority = -1)
  @Description("UpdateFields - updated ratingDescriptors")
  @TmsLink("28500894")
  public void C28500894_TEST() {
    BasicDBList listQueried = new BasicDBList();
    listQueried.add(new BasicDBObject("ratingDescriptors", new BasicDBObject("$size", 2)));
    listQueried.add(new BasicDBObject("activeRegion", "br"));

    Episodes mongoEpisode = episodeUtils.getDocumentByKeyValueList("$and", listQueried);
    String getRatingDescriptorsAfter = "br-linguagem-impropria";
    String randomEpisodeId = mongoEpisode.getId();
    LogUtils.logInfoMessage(
        "Updated episode: " + randomEpisodeId + ", to: " + getRatingDescriptorsAfter);

    Episode episode =
        Episode.newBuilder().addAllRatingDescriptors(List.of(getRatingDescriptorsAfter)).build();
    UpdateFieldsResponse updateFields =
        episodesClient.getUpdateFields(randomEpisodeId, List.of("ratingDescriptors"), episode);

    Assert.assertEquals(
        getRatingDescriptorsAfter, updateFields.getDocument().getRatingDescriptors(0));
    Assert.assertEquals(
        updateFields.getDocument().getRatingDescriptors(0), getRatingDescriptorsAfter);
  }

  @Test(priority = -1)
  @Description("UpdateFields - update name")
  @TmsLink("28500856")
  public void C28500856_TEST() {
    Episode createdEpisode = episodesClient.getNewEpisodeBuild();

    CreateResponse createResponse = episodesClientAuth.create(createdEpisode);
    String newEpisodeId = createResponse.getDocument().getId();

    String expectedName = createdEpisode.getName() + "a";
    Episode episode = Episode.newBuilder().setName(expectedName).build();
    UpdateFieldsResponse updateFields =
        episodesClient.getUpdateFields(newEpisodeId, List.of("name"), episode);

    Assert.assertEquals(expectedName, updateFields.getDocument().getName());
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "NOT_FOUND: Episode document for requested ID not found")
  @Description("Invalid request - GetEpisodeById")
  @TmsLink("28500778")
  public void C28500778_TEST() {
    episodesClient.getEpisodeById(MongoUtils.INVALID_ID_VALUE);
  }

  @Test
  @Description("Valid request with single episode - GetEpisodesByIds")
  @TmsLink("28500781")
  public void C28500781_TEST() {
    Episodes mongoEpisode = episodeUtils.getRandomDocument();
    List<String> randomEpisodesIds = Collections.singletonList(mongoEpisode.getId());

    List<Episodes> mongoEpisodesList = episodeUtils.getDocumentsByIds(randomEpisodesIds);
    List<?> mappedEpisodesList =
        episodeUtils.mapGrpcObjectsToPojo(
            episodesClient.getByIds(randomEpisodesIds).getDocumentsList(), Episodes.class);

    Assert.assertEquals(mongoEpisodesList, mappedEpisodesList);
  }

  @Test
  @Description("Valid request with multiple episodes - GetEpisodesByIds")
  @TmsLink("28500782")
  public void C28500782_TEST() {
    List<String> randomEpisodesIds = episodeUtils.getRandomEpisodesIds();

    List<Episodes> mongoEpisodesList = episodeUtils.getDocumentsByIds(randomEpisodesIds);
    List<?> mappedEpisodesList =
        episodeUtils.mapGrpcObjectsToPojo(
            episodesClient.getByIds(randomEpisodesIds).getDocumentsList(), Episodes.class);

    Assert.assertEquals(mongoEpisodesList, mappedEpisodesList);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "NOT_FOUND: Episode documents for requested IDs not found")
  @Description("Invalid request with single episode - GetEpisodesByIds")
  @TmsLink("28500783")
  public void C28500783_TEST() {
    List<String> randomEpisodesIds = Collections.singletonList(MongoUtils.INVALID_ID_VALUE);
    Assert.assertTrue(episodesClient.getByIds(randomEpisodesIds).getDocumentsList().isEmpty());
  }

  @Test
  @Description("Invalid request with single episode - GetEpisodesByIds")
  @TmsLink("28500784")
  public void C28500784_TEST() {
    List<String> randomEpisodesIds = episodeUtils.getRandomEpisodesIds();
    randomEpisodesIds.add(MongoUtils.INVALID_ID_VALUE);

    List<Episodes> mongoEpisodesList = episodeUtils.getDocumentsByIds(randomEpisodesIds);
    List<?> mappedEpisodesList =
        episodeUtils.mapGrpcObjectsToPojo(
            episodesClient.getByIds(randomEpisodesIds).getDocumentsList(), Episodes.class);

    Assert.assertEquals(mongoEpisodesList, mappedEpisodesList);
  }

  // check if not test data issue if the test fails
  @Test
  @Description("Valid request - GetEpisodesByIds with the new fields")
  @TmsLink("28500910")
  public void C28500910_TEST() {
    Episodes mongoEpisode = episodeUtils.getExistsAndSizeNotZero("sources");
    LogUtils.logInfoMessage("Episode id: " + mongoEpisode.getId());

    List<String> randomEpisodesIds = Collections.singletonList(mongoEpisode.getId());

    List<tv.pluto.ip.dal.episode.Episodes.EpisodeComputed> grpcEpisodes =
        episodesClient.getEpisodesByIds(randomEpisodesIds).getDocumentsList();

    List<String> listClipIds =
        mongoEpisode.getSources().stream().map(Source::getClip).collect(Collectors.toList());
    List<Clips> listClips = clipsUtils.getDocumentsByIds(listClipIds);

    List<String> listAuthorNames =
        listClips.stream()
            .map(clip -> clip.getAuthor().getName())
            .filter(c -> !c.equals("Pluto TV") && !c.equals("Pluto TV O&O"))
            .collect(Collectors.toList());

    Set<String> setAuthorNames = new HashSet<>(listAuthorNames);

    if (listClipIds.size() == 1)
      Assert.assertEquals(listClips.get(0).getAuthor().getName(), grpcEpisodes.get(0).getAuthor());
    else if (setAuthorNames.size() > 1)
      Assert.assertEquals("Multiple", grpcEpisodes.get(0).getAuthor());
    else if (listAuthorNames.size() == listClips.size())
      Assert.assertEquals(1, setAuthorNames.size());
    else Assert.assertEquals("N/A", grpcEpisodes.get(0).getAuthor());

    Assert.assertFalse(grpcEpisodes.isEmpty());
    Instant nowTime = Instant.now();

    for (tv.pluto.ip.dal.episode.Episodes.EpisodeComputed episode : grpcEpisodes) {
      boolean isReadyClip =
          episode.getSourcesList().stream()
              .allMatch(s -> s.getClip().getContentStatus().equals("ready"));
      Assert.assertEquals(isReadyClip, episode.getAllClipsReady());

      List<tv.pluto.ip.dal.episode.Episodes.Window> linearWindow =
          episode.getAvailabilityWindows().getLinearList();

      if (linearWindow.isEmpty()) Assert.assertFalse(episode.getIsAvailableNow());
      else {
        boolean isAvailable =
            linearWindow.stream()
                .anyMatch(
                    linear -> {
                      Instant startDate = Instant.parse(linear.getStartDate());
                      Instant endDate = Instant.parse(linear.getEndDate());
                      return startDate.isBefore(nowTime) && endDate.isAfter(nowTime);
                    });
        Assert.assertTrue(isAvailable);
      }

      List<tv.pluto.ip.dal.episode.Episodes.ClipDetails> listSourceClips =
          episode.getSourcesList().stream().map(e -> e.getClip()).collect(Collectors.toList());

      Set<tv.pluto.ip.dal.episode.Episodes.ClipDetails> setSourceClips =
          new HashSet<>(listSourceClips);

      List<String> collectNames =
          listSourceClips.stream()
              .map(c -> c.getAuthor().getName())
              .filter(n -> !n.isEmpty())
              .collect(Collectors.toList());
      Set<String> setNames = new HashSet<>(collectNames);

      if (listSourceClips.isEmpty()) Assert.assertFalse(episode.getHasUniqueProvider());

      // TO DO to check this condition
      else if (listSourceClips.size() > 1 && setSourceClips.size() == 1) {
        if (collectNames.size() == listSourceClips.size() && setNames.size() == 1)
          Assert.assertFalse(episode.getHasUniqueProvider());
        else Assert.assertTrue(episode.getHasUniqueProvider());
      } else Assert.assertFalse(episode.getHasUniqueProvider());

      if (episode.getIsFeed() && episode.getSourcesList().isEmpty())
        Assert.assertFalse(episode.getHasMixedProviderWith());

      Episodes mongoEpisodes = episodeUtils.getDocumentById(episode.getId());
      List<String> listClipIdsForEpisode =
          mongoEpisodes.getSources().stream().map(Source::getClip).collect(Collectors.toList());
      List<Clips> listClipsInEpisodes = clipsUtils.getDocumentsByIds(listClipIdsForEpisode);

      AtomicInteger hasClipWithEmptySources = new AtomicInteger();
      listClipsInEpisodes.forEach(
          e -> {
            int val = (int) e.getSources().stream().map(a -> a.getClip()).count();
            hasClipWithEmptySources.addAndGet(val);
          });

      if (hasClipWithEmptySources.get() == 0)
        Assert.assertTrue(episode.getHasClipWithEmptySources());
      else Assert.assertFalse(episode.getHasClipWithEmptySources());
    }
  }

  @Test(
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp = "NOT_FOUND: No Episode documents found for IDs")
  @Description("Invalid request - GetEpisodesByIds")
  @TmsLink("28500911")
  public void C28500911_TEST() {
    episodesClient.getEpisodesByIds(List.of("51c75f7eb6f26ba1cd000000"));
  }

  @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "INTERNAL.*")
  @Description("Empty request - GetEpisodesByIds")
  @TmsLink("28500912")
  public void C28500912_TEST() {
    episodesClient.getEpisodesByIds(List.of(""));
  }

  @Test(enabled = false)
  @Description("GetDocumentsUpdatedBetweenDates - Time period for which there is a episode")
  @TmsLink("28500819")
  public void C28500819_TEST() {

    ContentFeedQuery contentFeedQuery =
        ContentFeedQuery.builder()
            .updatedAtMin(episodeUtils.updatedAtMin)
            .updatedAtMax(episodeUtils.updatedAtMax)
            .limit(0)
            .projections(include("id"))
            .build();

    List<Episodes> mongoEpisodes = episodeUtils.getDocumentsForContentFeeds(contentFeedQuery);
    List<Episodes> grpcEpisodes = new ArrayList<>();

    Iterator<Episode> episodes =
        episodesClient.getDocumentsUpdatedBetweenDates(
            episodeUtils.updatedAtMinString, episodeUtils.updatedAtMaxString);

    while (episodes.hasNext()) {
      grpcEpisodes.add(episodeUtils.mapGrpcObjectToPojo(episodes.next(), Episodes.class));
    }

    mongoEpisodes.sort(Comparator.comparing(Episodes::getId));
    grpcEpisodes.sort(Comparator.comparing(Episodes::getId));

    List<String> mongoIds = mongoEpisodes.stream().map(Episodes::getId).toList();
    List<String> grpcIds = grpcEpisodes.stream().map(Episodes::getId).toList();

    Assert.assertEquals(mongoIds, grpcIds, "The IDs do not match between Mongo and gRPC episodes.");
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = NOT_FOUND_MESSAGE)
  @Description("GetDocumentsUpdatedBetweenDates - Time period without episodes")
  @TmsLink("28500820")
  public void C28500820_TEST() {
    episodesClient.getDocumentsUpdatedBetweenDates(failStartDateTime, failEndDateTime).next();
  }

  @Test(
      dataProvider = "fail-content-feeds-test-data",
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = INVALID_ARGUMENT_MESSAGE)
  @TmsLink("28500821")
  @Description(
      "GetDocumentsUpdatedBetweenDates - Invalid values for start_date_time and end_date_time")
  public void C28500821_TEST(String startDateTime, String endDateTime) {
    episodesClient.getDocumentsUpdatedBetweenDates(startDateTime, endDateTime).next();
  }

  @Test(
      dataProvider = "less-or-equal-fail-content-feeds-test-data",
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = INTERNAL_ERROR_MESSAGE)
  @Description(
      "GetDocumentsUpdatedBetweenDates - end_date_time is less than or equal to start_date_time")
  @TmsLink("28500822")
  public void C28500822_TEST(String startDateTime, String endDateTime) {
    episodesClient.getDocumentsUpdatedBetweenDates(startDateTime, endDateTime).next();
  }

  @Test
  @Description("Valid request: ID of the clip which is present in episodes - GetEpisodesByClipId")
  @TmsLink("28500791")
  public void C28500791_TEST() {
    String clipId = clipsUtils.getRandomItem(clipsUtils.getIdsOfClipsInEpisodes(10));

    GetEpisodesByClipIdRequest getEpisodesByClipIdRequest =
        GetEpisodesByClipIdRequest.newBuilder().setId(clipId).setLimit(10).build();

    List<Episodes> grpcEpisodes =
        episodeUtils.mapGrpcObjectsToPojo(
            episodesClient.getEpisodesByClipId(getEpisodesByClipIdRequest).getEpisodesList(),
            Episodes.class);

    List<Episodes> mongoEpisodes = episodeUtils.getEpisodesByClipId(clipId, 10, 0, null, null);

    grpcEpisodes.sort(Comparator.comparing(Episodes::getId));
    mongoEpisodes.sort(Comparator.comparing(Episodes::getId));

    Assert.assertEquals(mongoEpisodes, grpcEpisodes);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "NOT_FOUND: Episodes for requested clip ID not found")
  @Description(
      "Valid request: ID of the clip which is not present in episodes - GetEpisodesByClipId")
  @TmsLink("28500792")
  public void C28500792_TEST() {
    String clipId = clipsUtils.getRandomItem(clipsUtils.getIdsOfClipsIsNotInEpisodes(10));

    GetEpisodesByClipIdRequest getEpisodesByClipIdRequest =
        GetEpisodesByClipIdRequest.newBuilder().setId(clipId).setLimit(0).build();

    episodesClient.getEpisodesByClipId(getEpisodesByClipIdRequest);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "NOT_FOUND: Episodes for requested clip ID not found")
  @Description("Valid request: Invalid ID - GetEpisodesByClipId")
  @TmsLink("28500793")
  public void C28500793_TEST() {
    GetEpisodesByClipIdRequest getEpisodesByClipIdRequest =
        GetEpisodesByClipIdRequest.newBuilder().setId(INVALID_ID_VALUE).setLimit(0).build();

    episodesClient.getEpisodesByClipId(getEpisodesByClipIdRequest);
  }

  @Test
  @Description("GetAgeRangePopulatedEpisodeById - episode with age range")
  @TmsLink("28500803")
  public void C28500803_TEST() {
    String randomEpisodeId = episodeUtils.getIdsOfEpisodesWithAgeRange(1).get(0);

    AgeRangeEpisode mongoAgeRangeEpisode =
        ageRangeEpisodeUtils.getAgeRangePopulatedEpisodeById(randomEpisodeId);

    GetAgeRangePopulatedEpisodeByIdRequest getAgeRangePopulatedEpisodeByIdRequest =
        GetAgeRangePopulatedEpisodeByIdRequest.newBuilder().setId(randomEpisodeId).build();

    AgeRangeEpisode grpcAgeRangeEpisode =
        episodeUtils.mapGrpcObjectToPojo(
            episodesClient
                .getAgeRangePopulatedEpisodeById(getAgeRangePopulatedEpisodeByIdRequest)
                .getDocument(),
            AgeRangeEpisode.class);

    Assert.assertEquals(mongoAgeRangeEpisode, grpcAgeRangeEpisode);
  }

  @Test
  @Description("GetAgeRangePopulatedEpisodeById - episode without age range")
  @TmsLink("28500805")
  public void C28500805_TEST() {
    String randomEpisodeId = episodeUtils.getIdsOfEpisodesWithoutAgeRange(1).get(0);

    AgeRangeEpisode mongoAgeRangeEpisode =
        ageRangeEpisodeUtils.getAgeRangePopulatedEpisodeById(randomEpisodeId);

    GetAgeRangePopulatedEpisodeByIdRequest getAgeRangePopulatedEpisodeByIdRequest =
        GetAgeRangePopulatedEpisodeByIdRequest.newBuilder().setId(randomEpisodeId).build();

    AgeRangeEpisode grpcAgeRangeEpisode =
        episodeUtils.mapGrpcObjectToPojo(
            episodesClient
                .getAgeRangePopulatedEpisodeById(getAgeRangePopulatedEpisodeByIdRequest)
                .getDocument(),
            AgeRangeEpisode.class);

    Assert.assertEquals(mongoAgeRangeEpisode, grpcAgeRangeEpisode);
  }

  @Test
  @Description("GetAgeRangePopulatedEpisodeById - custom_references")
  @TmsLink("28500804")
  public void C28500804_TEST() {
    Episodes episodes = episodeUtils.getExistsAndSizeNotZero("customReferences");
    String randomEpisodeId = episodes.getId();
    LogUtils.logInfoMessage("Episode id: " + randomEpisodeId);
    AgeRangeEpisode mongoAgeRangeEpisode =
        ageRangeEpisodeUtils.getAgeRangePopulatedEpisodeById(randomEpisodeId);

    GetAgeRangePopulatedEpisodeByIdRequest getAgeRangePopulatedEpisodeByIdRequest =
        GetAgeRangePopulatedEpisodeByIdRequest.newBuilder().setId(randomEpisodeId).build();

    AgeRangeEpisode grpcAgeRangeEpisode =
        episodeUtils.mapGrpcObjectToPojo(
            episodesClient
                .getAgeRangePopulatedEpisodeById(getAgeRangePopulatedEpisodeByIdRequest)
                .getDocument(),
            AgeRangeEpisode.class);

    Assert.assertEquals(mongoAgeRangeEpisode, grpcAgeRangeEpisode);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "NOT_FOUND: Episode for requested ID not found")
  @Description("GetAgeRangePopulatedEpisodeById - episode with non existing ID")
  @TmsLink("28500806")
  public void C28500806_TEST() {
    GetAgeRangePopulatedEpisodeByIdRequest getAgeRangePopulatedEpisodeByIdRequest =
        GetAgeRangePopulatedEpisodeByIdRequest.newBuilder().setId(INVALID_ID_VALUE).build();

    episodesClient.getAgeRangePopulatedEpisodeById(getAgeRangePopulatedEpisodeByIdRequest);
  }

  @Test
  @Description("GetAgeRangePopulatedEpisodesByIds - single episode with age range")
  @TmsLink("28500807")
  public void C28500807_TEST() {
    List<String> randomEpisodesIds = episodeUtils.getIdsOfEpisodesWithAgeRange(1);

    List<AgeRangeEpisode> mongoAgeRangeEpisodes =
        ageRangeEpisodeUtils.getAgeRangePopulatedEpisodeByIds(randomEpisodesIds);

    List<AgeRangeEpisode> grpcAgeRangeEpisodes =
        episodeUtils.mapGrpcObjectsToPojo(
            episodesClient.getAgeRangePopulatedEpisodesByIds(randomEpisodesIds).getDocumentsList(),
            AgeRangeEpisode.class);

    grpcAgeRangeEpisodes.sort(Comparator.comparing(AgeRangeEpisode::getId));
    mongoAgeRangeEpisodes.sort(Comparator.comparing(AgeRangeEpisode::getId));

    Assert.assertEquals(mongoAgeRangeEpisodes, grpcAgeRangeEpisodes);
  }

  @Test
  @Description("GetAgeRangePopulatedEpisodesByIds - single episode without age range")
  @TmsLink("28500808")
  public void C28500808_TEST() {
    List<String> randomEpisodesIds = episodeUtils.getIdsOfEpisodesWithoutAgeRange(1);

    List<AgeRangeEpisode> mongoAgeRangeEpisodes =
        ageRangeEpisodeUtils.getAgeRangePopulatedEpisodeByIds(randomEpisodesIds);

    List<AgeRangeEpisode> grpcAgeRangeEpisodes =
        episodeUtils.mapGrpcObjectsToPojo(
            episodesClient.getAgeRangePopulatedEpisodesByIds(randomEpisodesIds).getDocumentsList(),
            AgeRangeEpisode.class);

    grpcAgeRangeEpisodes.sort(Comparator.comparing(AgeRangeEpisode::getId));
    mongoAgeRangeEpisodes.sort(Comparator.comparing(AgeRangeEpisode::getId));

    Assert.assertEquals(mongoAgeRangeEpisodes, grpcAgeRangeEpisodes);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "NOT_FOUND: Episodes for requested IDs not found")
  @Description("GetAgeRangePopulatedEpisodesByIds - single episode with non existing ID")
  @TmsLink("28500809")
  public void C28500809_TEST() {
    episodesClient.getAgeRangePopulatedEpisodesByIds(Collections.singletonList(INVALID_ID_VALUE));
  }

  @Test
  @Description("GetAgeRangePopulatedEpisodesByIds - multiple episodes with age range")
  @TmsLink("28500810")
  public void C28500810_TEST() {
    List<String> randomEpisodesIds = episodeUtils.getIdsOfEpisodesWithAgeRange(10);

    List<AgeRangeEpisode> mongoAgeRangeEpisodes =
        ageRangeEpisodeUtils.getAgeRangePopulatedEpisodeByIds(randomEpisodesIds);

    List<AgeRangeEpisode> grpcAgeRangeEpisodes =
        episodeUtils.mapGrpcObjectsToPojo(
            episodesClient.getAgeRangePopulatedEpisodesByIds(randomEpisodesIds).getDocumentsList(),
            AgeRangeEpisode.class);

    grpcAgeRangeEpisodes.sort(Comparator.comparing(AgeRangeEpisode::getId));
    mongoAgeRangeEpisodes.sort(Comparator.comparing(AgeRangeEpisode::getId));

    Assert.assertEquals(mongoAgeRangeEpisodes, grpcAgeRangeEpisodes);
  }

  @Test
  @Description("GetAgeRangePopulatedEpisodesByIds - multiple episode without age range")
  @TmsLink("28500811")
  public void C28500811_TEST() {
    List<String> randomEpisodesIds = episodeUtils.getIdsOfEpisodesWithoutAgeRange(10);

    List<AgeRangeEpisode> mongoAgeRangeEpisodes =
        ageRangeEpisodeUtils.getAgeRangePopulatedEpisodeByIds(randomEpisodesIds);

    List<AgeRangeEpisode> grpcAgeRangeEpisodes =
        episodeUtils.mapGrpcObjectsToPojo(
            episodesClient.getAgeRangePopulatedEpisodesByIds(randomEpisodesIds).getDocumentsList(),
            AgeRangeEpisode.class);

    grpcAgeRangeEpisodes.sort(Comparator.comparing(AgeRangeEpisode::getId));
    mongoAgeRangeEpisodes.sort(Comparator.comparing(AgeRangeEpisode::getId));

    Assert.assertEquals(mongoAgeRangeEpisodes, grpcAgeRangeEpisodes);
  }

  @Test
  @Description(
      "GetAgeRangePopulatedEpisodesByIds - mixed episode: with ageRange + without ageRange + non existing episode ID")
  @TmsLink("28500812")
  public void C28500812_TEST() {
    List<String> episodesIdsWithTimeRange = episodeUtils.getIdsOfEpisodesWithAgeRange(5);
    List<String> episodesIdsWithoutTimeRange = episodeUtils.getIdsOfEpisodesWithoutAgeRange(5);

    List<String> randomEpisodesIds = new ArrayList<>(episodesIdsWithTimeRange);

    randomEpisodesIds.addAll(episodesIdsWithoutTimeRange);
    randomEpisodesIds.add(INVALID_ID_VALUE);

    List<AgeRangeEpisode> mongoAgeRangeEpisodes =
        ageRangeEpisodeUtils.getAgeRangePopulatedEpisodeByIds(randomEpisodesIds);

    List<AgeRangeEpisode> grpcAgeRangeEpisodes =
        episodeUtils.mapGrpcObjectsToPojo(
            episodesClient.getAgeRangePopulatedEpisodesByIds(randomEpisodesIds).getDocumentsList(),
            AgeRangeEpisode.class);

    grpcAgeRangeEpisodes.sort(Comparator.comparing(AgeRangeEpisode::getId));
    mongoAgeRangeEpisodes.sort(Comparator.comparing(AgeRangeEpisode::getId));

    Assert.assertEquals(mongoAgeRangeEpisodes, grpcAgeRangeEpisodes);
  }

  @Test
  @Description("GetById - episode with age range")
  @TmsLink("28500779")
  public void C28500779_TEST() {
    String randomEpisodeId = episodeUtils.getIdsOfEpisodesWithAgeRange(1).get(0);

    Episodes mongoEpisode = episodeUtils.getDocumentById(randomEpisodeId);

    Episodes mappedEpisode =
        episodeUtils.mapGrpcObjectToPojo(
            episodesClient.getEpisodeById(randomEpisodeId).getDocument(), Episodes.class);

    Assert.assertEquals(mongoEpisode, mappedEpisode);
  }

  @Test
  @Description("GetById - episode without age range")
  @TmsLink("28500780")
  public void C28500780_TEST() {
    String randomEpisodeId = episodeUtils.getIdsOfEpisodesWithoutAgeRange(1).get(0);

    Episodes mongoEpisode = episodeUtils.getDocumentById(randomEpisodeId);

    Episodes mappedEpisode =
        episodeUtils.mapGrpcObjectToPojo(
            episodesClient.getEpisodeById(randomEpisodeId).getDocument(), Episodes.class);

    Assert.assertEquals(mongoEpisode, mappedEpisode);
  }

  @Test(priority = -1)
  @Description("UpdateContentMamUniqueIdsByIds - episodes with contentUUID")
  @TmsLink("28500813")
  public void C28500813_TEST() {
    List<Episodes> mongoEpisodes = episodeUtils.getEpisodesWithUUID(5);

    List<ObjectIdContentIdSet> objectIdContentIdSets =
        episodeUtils.extractIdsAndUUID(mongoEpisodes);

    episodesClient.updateContentMamUniqueIdsByIds(objectIdContentIdSets);

    Map<String, String> contentMap = episodeUtils.getIdsFromContentSet(objectIdContentIdSets);

    SoftAssert softAssert = new SoftAssert();

    contentMap.forEach(
        (id, contentUUID) -> {
          Episodes updatedEpisode = episodeUtils.getDocumentById(id);
          softAssert.assertEquals(updatedEpisode.getContentUUID(), contentUUID);
        });

    softAssert.assertAll();
  }

  @Test(priority = -1)
  @Description("UpdateContentMamUniqueIdsByIds - episodes without contentUUID")
  @TmsLink("28500814")
  public void C28500814_TEST() {
    List<Episodes> mongoEpisodes = episodeUtils.getEpisodesWithoutUUID(5);

    List<ObjectIdContentIdSet> objectIdContentIdSets =
        episodeUtils.extractIdsAndUUID(mongoEpisodes);

    episodesClient.updateContentMamUniqueIdsByIds(objectIdContentIdSets);

    Map<String, String> contentMap = episodeUtils.getIdsFromContentSet(objectIdContentIdSets);

    SoftAssert softAssert = new SoftAssert();

    contentMap.forEach(
        (id, contentUUID) -> {
          Episodes updatedEpisode = episodeUtils.getDocumentById(id);
          softAssert.assertEquals(updatedEpisode.getContentUUID(), contentUUID);
        });

    softAssert.assertAll();
  }

  @Test
  @Description("Create - with custom_references")
  @TmsLink("28500901")
  public void C28500901_TEST() {
    Episode createdEpisode = episodesClient.getNewEpisodeBuild();

    CreateResponse createResponse = episodesClientAuth.create(createdEpisode);
    String newEpisodeId = createResponse.getDocument().getId();

    Episode episodeGrpc = episodesClient.getEpisodeById(newEpisodeId).getDocument();
    Assert.assertNotNull(episodeGrpc.getId());

    SoftAssert softAssert = new SoftAssert();
    softAssert.assertEquals(episodeGrpc.getName(), createdEpisode.getName());
    softAssert.assertEquals(episodeGrpc.getDescription(), createdEpisode.getDescription());
    softAssert.assertEquals(episodeGrpc.getSummary(), createdEpisode.getSummary());
    softAssert.assertEquals(
        episodeGrpc.getMetadataLanguage(), createdEpisode.getMetadataLanguage());
    softAssert.assertEquals(episodeGrpc.getSubGenre(), createdEpisode.getSubGenre());
    softAssert.assertEquals(episodeGrpc.getSubCategory(), createdEpisode.getSubCategory());
    softAssert.assertEquals(episodeGrpc.getCategory(), createdEpisode.getCategory());
    softAssert.assertEquals(episodeGrpc.getAllotment(), createdEpisode.getAllotment());
    softAssert.assertEquals(episodeGrpc.getTagsList(), createdEpisode.getTagsList());
    softAssert.assertEquals(
        episodeGrpc.getCustomReferencesList(), createdEpisode.getCustomReferencesList());

    episodesClientAuth.deleteById(episodeGrpc.getId());
    softAssert.assertAll();
  }

  @Test(priority = -1)
  @Description("Create - creating new episode")
  @TmsLink("28500831")
  public void C28500831_TEST() {
    String testValue = "DALValue";
    Episode updateGrpcEpisode =
        Episode.newBuilder()
            .setName(testValue)
            .setSubGenre("Cartoons")
            .setGenre("Children & Family")
            .setSubCategory("Cartoons")
            .setCategory("Pop Culture")
            .setSummary(testValue)
            .setMetadataLanguage(testValue)
            .setDescription(testValue)
            .setAllotment(5)
            .addAllTags(List.of("us"))
            .setActiveRegion("us")
            .build();

    tv.pluto.ip.dal.episode.Episodes.EpisodeCreate createResponse =
        episodesClientAuth.create(updateGrpcEpisode).getDocument();

    Assert.assertFalse(createResponse.getId().isEmpty());
    Assert.assertEquals(createResponse.getName(), updateGrpcEpisode.getName());
    Assert.assertEquals(createResponse.getActiveRegion(), updateGrpcEpisode.getActiveRegion());
    Assert.assertEquals(createResponse.getAuthor(), updateGrpcEpisode.getAuthor());
    Assert.assertEquals(createResponse.getCategory(), updateGrpcEpisode.getCategory());
    Assert.assertEquals(createResponse.getDescription(), updateGrpcEpisode.getDescription());
  }

  @Test(priority = -1)
  @Description("Create - creating episode without authentication")
  @TmsLink("28500832")
  public void C28500832_TEST() {
    try {
      episodesClient.create(Episode.newBuilder().build());
    } catch (StatusRuntimeException e) {
      Assert.assertEquals(e.getStatus().getCode().value(), 16);
      Assert.assertEquals(
          e.getMessage(), "UNAUTHENTICATED: DB writes not allowed without authentication");
    }
  }

  @Test(priority = -1)
  @Description("DeleteById - delete existing episode")
  @TmsLink("28500833")
  public void C28500833_TEST() {
    EpisodesClient auth = new EpisodesClient(grpcChannelManager.getChannel(), true);
    String episodeID = episodeUtils.createNewEpisodeId(auth);
    Assert.assertEquals(auth.deleteById(episodeID).getDeletedCount(), 1);
    Assert.assertNull(episodeUtils.getDocumentById(episodeID));
  }

  @Test(priority = -1)
  @Description("DeleteById - delete non existing episode")
  @TmsLink("28500834")
  public void C28500834_TEST() {
    try {
      episodesClientAuth.deleteById(INVALID_ID_VALUE);
    } catch (StatusRuntimeException e) {
      Assert.assertEquals(e.getStatus().getCode().value(), 5);
      Assert.assertEquals(
          e.getMessage(), "NOT_FOUND: Episode documents for requested IDs not found");
    }
  }

  @Test(priority = -1)
  @Description("DeleteById - delete episode without authentication")
  @TmsLink("28500835")
  public void C28500835_TEST() {
    try {
      String episodeId = ((Episodes) episodeUtils.getRandomDocument()).getId();
      episodesClient.deleteById(episodeId);
    } catch (StatusRuntimeException e) {
      Assert.assertEquals(e.getStatus().getCode().value(), 16);
      Assert.assertEquals(
          e.getMessage(), "UNAUTHENTICATED: DB deletes not allowed without authentication");
    }
  }

  @Test
  @Description("DeleteByIds - delete a single episode")
  @TmsLink("28500836")
  public void C28500836_TEST() {
    List<String> ids =
        Collections.singletonList(episodeUtils.createNewEpisodeId(episodesClientAuth));
    int size = ids.size();
    Assert.assertEquals(episodesClientAuth.deleteByIds(ids).getDeletedCount(), size);
    Assert.assertTrue(episodeUtils.getDocumentsByIds(ids).isEmpty());
  }

  @Test(priority = -1)
  @Description("DeleteByIds - delete multiple episodes")
  @TmsLink("28500837")
  public void C28500837_TEST() {
    List<String> listIds = List.of(episodeUtils.createNewEpisodeId(episodesClientAuth));
    Assert.assertEquals(episodesClientAuth.deleteByIds(listIds).getDeletedCount(), listIds.size());
    Assert.assertTrue(episodeUtils.getDocumentsByIds(listIds).isEmpty());
  }

  @Test(priority = -1)
  @Description("DeleteByIds - delete non existing episode")
  @TmsLink("28500838")
  public void C28500838_TEST() {
    try {
      episodesClientAuth.deleteByIds(Collections.singletonList(INVALID_ID_VALUE));
    } catch (StatusRuntimeException e) {
      Assert.assertEquals(e.getStatus().getCode().value(), 5);
      Assert.assertEquals(
          e.getMessage(), "NOT_FOUND: Episode documents for requested IDs not found");
    }
  }

  @Test(
      priority = -1,
      dependsOnMethods = {"C28500831_TEST", "C28500833_TEST", "C28500836_TEST", "C28500837_TEST"})
  @Description("DeleteByIds - delete non existing episode")
  @TmsLink("28500839")
  public void C28500839_TEST() {
    String testValue = "DALValue";
    Episode updateGrpcEpisode =
        Episode.newBuilder()
            .setName(testValue)
            .setSubGenre("Cartoons")
            .setGenre("Children & Family")
            .setSubCategory("Cartoons")
            .setCategory("Pop Culture")
            .setSummary(testValue)
            .setMetadataLanguage(testValue)
            .setDescription(testValue)
            .setAllotment(5)
            .addAllTags(List.of("us"))
            .setActiveRegion("us")
            .build();

    tv.pluto.ip.dal.episode.Episodes.EpisodeCreate createResponse =
        episodesClientAuth.create(updateGrpcEpisode).getDocument();
    List<String> createdEpisodesIds = new ArrayList<>();
    createdEpisodesIds.add(createResponse.getId());

    int size = createdEpisodesIds.size();
    createdEpisodesIds.add(INVALID_ID_VALUE);

    Assert.assertEquals(episodesClientAuth.deleteByIds(createdEpisodesIds).getDeletedCount(), size);
    Assert.assertTrue(episodeUtils.getDocumentsByIds(createdEpisodesIds).isEmpty());
  }

  @Test
  @Description(
      "GetAllPaged - get documents with limit and offset, sorting ascending by single field")
  @TmsLink("28500840")
  public void C28500840_TEST() {
    Bson sort = orderBy(ascending("_id"));
    List<Episodes> mongoEpisodes = episodeUtils.getDocuments(20, 10, sort);

    List<GetAllPagedSort> grpcSort =
        Collections.singletonList(GetAllPagedSort.newBuilder().setField("_id").setValue(1).build());

    GetAllPagedResponse response = episodesClient.getAllPaged(20, 10, grpcSort, null, null);

    List<Episodes> grpcEpisodes =
        episodeUtils.mapGrpcObjectsToPojo(response.getDocumentsList(), Episodes.class);

    Assert.assertEquals(mongoEpisodes, grpcEpisodes);
  }

  @Test
  @Description(
      "GetAllPaged - get documents with limit and offset, sorting ascending by multiple fields")
  @TmsLink("28500841")
  public void C28500841_TEST() {
    Bson sort = orderBy(ascending("_id"), ascending("name"));
    List<Episodes> mongoEpisodes = episodeUtils.getDocuments(20, 10, sort);

    List<GetAllPagedSort> grpcSort =
        Arrays.asList(
            GetAllPagedSort.newBuilder().setField("_id").setValue(1).build(),
            GetAllPagedSort.newBuilder().setField("name").setValue(1).build());

    GetAllPagedResponse response = episodesClient.getAllPaged(20, 10, grpcSort, null, null);

    List<Episodes> grpcEpisodes =
        episodeUtils.mapGrpcObjectsToPojo(response.getDocumentsList(), Episodes.class);

    Assert.assertEquals(mongoEpisodes, grpcEpisodes);
  }

  @Test
  @Description(
      "GetAllPaged - get documents with limit and offset, sorting descending by single field")
  @TmsLink("28500842")
  public void C28500842_TEST() {
    Bson sort = orderBy(descending("_id"));
    List<Episodes> mongoEpisodes = episodeUtils.getDocuments(20, 10, sort);

    List<GetAllPagedSort> grpcSort =
        Collections.singletonList(
            GetAllPagedSort.newBuilder().setField("_id").setValue(-1).build());

    GetAllPagedResponse response =
        new EpisodesClient(grpcChannelManager.getChannel(), false)
            .getAllPaged(20, 10, grpcSort, null, null);

    List<Episodes> grpcEpisodes =
        episodeUtils.mapGrpcObjectsToPojo(response.getDocumentsList(), Episodes.class);

    Assert.assertEquals(mongoEpisodes, grpcEpisodes);
  }

  @Test
  @Description("GetAllPagedResponse - has a new object Metadata")
  @TmsLink("28500893")
  public void C28500893_TEST() {
    int offset = 9;
    int limit = 18;

    List<GetAllPagedSort> grpcSort =
        Collections.singletonList(
            GetAllPagedSort.newBuilder().setField("_id").setValue(-1).build());

    GetAllPagedResponse response = episodesClient.getAllPaged(limit, offset, grpcSort, null, true);

    Assert.assertEquals(response.getMetadata().getCurrentOffset(), offset);
    Assert.assertEquals(limit, response.getMetadata().getTotalItems());
    Assert.assertEquals(limit, response.getDocumentsList().size());
  }

  @Test
  @Description("GetAllPagedResponse - has a new object Metadata disable_total_count: false")
  @TmsLink("28500895")
  public void C28500895_TEST() {
    int offset = 9;
    int limit = 18;

    List<GetAllPagedSort> grpcSort =
        Collections.singletonList(
            GetAllPagedSort.newBuilder().setField("_id").setValue(-1).build());

    GetAllPagedResponse response = episodesClient.getAllPaged(limit, offset, grpcSort, null, false);
    long countTotalEpisodes = episodeUtils.getDocumentsCountsInTheCollection();

    Assert.assertEquals(response.getMetadata().getCurrentOffset(), offset);
    Assert.assertEquals(limit, response.getDocumentsList().size());
    Assert.assertTrue(countTotalEpisodes > 0);
    Assert.assertEquals(countTotalEpisodes, response.getMetadata().getTotalItems());
  }

  @Test
  @Description(
      "GetAllPaged - get documents with limit and offset, sorting descending by multiple fields")
  @TmsLink("28500843")
  public void C28500843_TEST() {
    Bson sort = orderBy(descending("_id"), descending("name"));
    List<Episodes> mongoEpisodes = episodeUtils.getDocuments(20, 10, sort);

    List<GetAllPagedSort> grpcSort =
        Arrays.asList(
            GetAllPagedSort.newBuilder().setField("_id").setValue(-1).build(),
            GetAllPagedSort.newBuilder().setField("name").setValue(-1).build());

    GetAllPagedResponse response = episodesClient.getAllPaged(20, 10, grpcSort, null, null);

    List<Episodes> grpcEpisodes =
        episodeUtils.mapGrpcObjectsToPojo(response.getDocumentsList(), Episodes.class);

    Assert.assertEquals(mongoEpisodes, grpcEpisodes);
  }

  @Test
  @Description("GetAllPaged - get documents with default values")
  @TmsLink("28500844")
  public void C28500844_TEST() {
    // TODO investigate client issue
    EpisodesClient newClient = new EpisodesClient(grpcChannelManager.getChannel(), false);
    GetAllPagedResponse response = newClient.getAllPaged(null, null, null, null, null);

    Assert.assertEquals(response.getDocumentsList().size(), DEFAULT_LIMIT); // Default value
  }

  @Test
  @Description(
      "GetPopulatedEpisodesByClipId - get episodes with limit and offset, sorting ascending. Verify metadata object")
  @TmsLink("28500845")
  public void C28500845_TEST() {
    String clipId = episodeUtils.getClipFromSources();

    LogUtils.logInfoMessage("C28500845_TEST clipId = " + clipId);

    Bson sort = orderBy(ascending("_id"));

    int offSet = 0;
    int limit = 1;

    List<EpisodePopulated> mongoEpisodesPopulated =
        populatedEpisodesUtils.getPopulatedEpisodesByClipId(clipId, limit, offSet, sort);

    List<GetAllPagedSort> grpcSort =
        Collections.singletonList(GetAllPagedSort.newBuilder().setField("_id").setValue(1).build());

    List<EpisodePopulated> grpcEpisodesPopulated =
        populatedEpisodesUtils.mapGrpcObjectsToPojo(
            episodesClient
                .getPopulatedEpisodesByClipId(clipId, limit, offSet, grpcSort, null)
                .getEpisodes()
                .getDataList(),
            EpisodePopulated.class);

    tv.pluto.ip.dal.episode.Episodes.Metadata metadata =
        episodesClient
            .getPopulatedEpisodesByClipId(clipId, limit, offSet, grpcSort, null)
            .getEpisodes()
            .getMetadata();

    Assert.assertEquals(metadata.getCurrentOffset(), offSet);

    Long clipsInEpisode = episodeUtils.getCountClipsInEpisode(clipId);

    LogUtils.logInfoMessage("Total clips in episode: " + clipsInEpisode);
    Assert.assertEquals(metadata.getTotalItems(), clipsInEpisode.intValue());

    populatedEpisodesUtils.sortEpisodes(mongoEpisodesPopulated);
    populatedEpisodesUtils.sortEpisodes(grpcEpisodesPopulated);

    EpisodePopulated episodePopulated = mongoEpisodesPopulated.get(0);
    EpisodePopulated grpcEpisodePopulated = grpcEpisodesPopulated.get(0);

    Assert.assertEquals(mongoEpisodesPopulated.size(), limit);
    Assert.assertEquals(grpcEpisodesPopulated.size(), limit);
    Assert.assertEquals(episodePopulated, grpcEpisodePopulated);
  }

  @Test
  @Description(
      "GetPopulatedEpisodesByClipId - get episodes with limit and offset, sorting descending")
  @TmsLink("28500846")
  public void C28500846_TEST() {
    String clipId = episodeUtils.getClipFromSources();

    LogUtils.logInfoMessage("C8886916_TEST clipId = " + clipId);

    Bson sort = orderBy(descending("_id"));

    List<EpisodePopulated> mongoEpisodesPopulated =
        populatedEpisodesUtils.getPopulatedEpisodesByClipId(clipId, 4, 2, sort);

    List<GetAllPagedSort> grpcSort =
        Collections.singletonList(
            GetAllPagedSort.newBuilder().setField("_id").setValue(-1).build());

    List<EpisodePopulated> grpcEpisodesPopulated =
        populatedEpisodesUtils.mapGrpcObjectsToPojo(
            episodesClient
                .getPopulatedEpisodesByClipId(clipId, 4, 2, grpcSort, null)
                .getEpisodes()
                .getDataList(),
            EpisodePopulated.class);

    populatedEpisodesUtils.sortEpisodes(mongoEpisodesPopulated);
    populatedEpisodesUtils.sortEpisodes(grpcEpisodesPopulated);

    Assert.assertEquals(mongoEpisodesPopulated, grpcEpisodesPopulated);
  }

  @Test
  @Description("GetPopulatedEpisodesByClipId - use non existing clip id")
  @TmsLink("28500847")
  public void C28500847_TEST() {
    List<GetAllPagedSort> grpcSort =
        Collections.singletonList(
            GetAllPagedSort.newBuilder().setField("_id").setValue(-1).build());

    try {
      episodesClient.getPopulatedEpisodesByClipId(INVALID_ID_VALUE, 4, 2, grpcSort, null);
    } catch (StatusRuntimeException e) {
      Assert.assertEquals(e.getStatus().getCode().value(), 5);
      Assert.assertEquals(e.getMessage(), "NOT_FOUND: Episodes for requested clip ID not found");
    }
  }

  @Test
  @Description("GetPopulatedEpisodeById - invalid id - INTERNAL error")
  @TmsLink("28500922")
  public void C28500922_TEST() {
    boolean isTrue = false;
    try {
      episodesClient.getPopulatedEpisodeById("test", "").getEpisode();
    } catch (StatusRuntimeException e) {
      isTrue = true;
      Assert.assertTrue(e.getMessage().contains("INTERNAL: Cast to ObjectId"));
    }
    Assert.assertTrue(isTrue);
  }

  @Test
  @Description("GetAll Episodes - has only active region")
  @TmsLink("28500848")
  public void C28500848_TEST() {
    int limit = 10;
    String activeRegionFromEpisodes = episodeUtils.getSetActiveRegionFromEpisodes().get(0);
    Iterator<Episode> getAllEpisodeResponse =
        episodesClient.getAllResponse(activeRegionFromEpisodes);
    List<Episode> listEpisode = episodesClient.listFromIteratorLimit(getAllEpisodeResponse, limit);
    Assert.assertEquals(listEpisode.size(), limit);

    List<Episodes> listEpisodes = new ArrayList<>();
    List<Episodes> listMongoEpisodes = new ArrayList<>();

    listEpisode.forEach(
        episode -> {
          Episodes episodes = episodeUtils.mapGrpcObjectToPojo(episode, Episodes.class);
          listEpisodes.add(episodes);

          Episodes episodesMongoDB = episodeUtils.getDocumentById(episodes.getId());
          Assert.assertEquals(
              activeRegionFromEpisodes,
              episodesMongoDB.getActiveRegion(),
              "Active region is not expected");
          listMongoEpisodes.add(episodesMongoDB);
        });

    listEpisodes.sort(Comparator.comparing(Episodes::getId));
    listMongoEpisodes.sort(Comparator.comparing(Episodes::getId));

    Assert.assertEquals(
        listEpisodes, listMongoEpisodes, "List Episodes from gRPC and Mongo DB are not equal");
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "NOT_FOUND: Not found")
  @Description(
      "GetAll Episodes - Verify filter is not returning anything if the active region is incorrect")
  @TmsLink("28500849")
  public void C28500849_TEST() {
    episodesClient.getAllResponse("justTest").next();
  }

  @Test
  @Description("CFeedsSyncAllIds- stream returns CFEEDS stream of episode ids")
  @TmsLink("28500853")
  public void C28500853_TEST() {
    Iterator<tv.pluto.ip.dal.episode.Episodes.Id> cFeedsSyncAllIdsRequest =
        episodesClient.getCFeedsResponse(null, null, true, "cFeedsSyncAllIds");
    for (int i = 0; i < 5; i++) {
      Episodes episode = episodeUtils.getDocumentById(cFeedsSyncAllIdsRequest.next().getId());
      Assert.assertTrue(episode.getPublished());
    }
  }

  @Test
  @Description("cFeedsSyncDocuments - last_run_at/published")
  @TmsLink("28500854")
  public void C28500854_TEST() {
    LocalDateTime startTime = LocalDateTime.now(ZoneOffset.UTC).minusDays(30);

    Iterator<?> cFeedsSyncAllIdsRequest =
        new EpisodesClient(grpcChannelManager.getChannel(), false)
            .getCFeedsResponse(true, startTime + "Z", true, "cFeedsSyncDocuments");
    for (int i = 0; i < 100; i++) {
      Episode episode = (Episode) cFeedsSyncAllIdsRequest.next();
      LocalDateTime episodeTime =
          LocalDateTime.ofInstant(Instant.parse(episode.getUpdatedAt()), ZoneOffset.UTC);

      Assert.assertTrue(episode.getPublished());
      Assert.assertTrue(episodeTime.isAfter(startTime) || episodeTime.equals(startTime));
    }
  }

  @Test(
      dataProvider = "is-true-false",
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp = NOT_FOUND_MESSAGE)
  @Description("cFeedsSyncDocuments - last_run_at/published for future time")
  @TmsLink("28500854")
  public void C28500854_TEST(boolean isPublished) {
    String futureTime = CommonUtils.localDateTimeStringPlusDays(10);
    episodesClient.getCFeedsResponse(true, futureTime, isPublished, "cFeedsSyncDocuments").next();
  }

  @Test
  @Description("cFeedsSyncDocuments - added the new request field: vod")
  @TmsLink("28500917")
  public void C28500917_TEST() {
    LocalDateTime ranAt = LocalDateTime.now(ZoneOffset.UTC).minusWeeks(5);
    Iterator<Episode> cFeedsSync =
        new EpisodesClient(grpcChannelManager.getChannel(), false)
            .getCFeedsResponse(true, ranAt + "Z", true, "cFeedsSyncDocuments");
    Episode episode = cFeedsSync.next();

    BasicDBObject query =
        new BasicDBObject(
            "$or",
            new Object[] {
              new BasicDBObject("episode", new ObjectId(episode.getId())),
              new BasicDBObject("series", new ObjectId(episode.getSeries()))
            });

    List<VodCategoryEntries> listVodCategoryEntriesMango =
        vodCategoryEntriesUtils.getDocuments(query, 1);
    String vodId = listVodCategoryEntriesMango.get(0).getVodCategory();

    boolean isVod = vodClient.getActiveEpisodesForCategoryId(vodId).getEpisodesList().isEmpty();
    Assert.assertFalse(isVod);
  }

  @Test(enabled = false)
  @Description("cFeedsSyncDocuments - added the new request field: vod not vod")
  @TmsLink("28500918")
  public void C28500918_TEST() {
    LocalDateTime ranAt = LocalDateTime.now(ZoneOffset.UTC).minusDays(3);
    Iterator<Episode> cFeedsSync =
        episodesClient.getCFeedsResponse(false, ranAt + "Z", true, "cFeedsSyncDocuments");
    Episode episode = cFeedsSync.next();

    BasicDBObject query =
        new BasicDBObject(
            "$or",
            new Object[] {
              new BasicDBObject("episode", new ObjectId(episode.getId())),
              new BasicDBObject("series", new ObjectId(episode.getSeries()))
            });

    Assert.assertTrue(vodCategoryEntriesUtils.getDocuments(query, 1).isEmpty());
  }

  @Test
  @Description("GetEpisodeIdsBySeriesIds - Valid request with multiple series ids")
  @TmsLink("28500785")
  public void C28500785_TEST() {
    List<String> seriesIds = episodeUtils.getEpisodesWithSeriesIds();

    LogUtils.logInfoMessage("Series ids for C9019610_TEST " + seriesIds);

    List<String> mongoEpisodesIds = episodeUtils.getEpisodeIdsBySeriesIds(seriesIds);

    List<String> grpcEpisodesIds =
        new ArrayList<>(episodesClient.getEpisodeIdsBySeriesIds(seriesIds, null).getIdsList());

    Collections.sort(mongoEpisodesIds);
    Collections.sort(grpcEpisodesIds);

    Assert.assertEquals(mongoEpisodesIds, grpcEpisodesIds);
  }

  @Test
  @Description(
      "GetEpisodeIdsBySeriesIds - Valid request with multiple series ids and distinct series")
  @TmsLink("28500786")
  public void C28500786_TEST() {
    List<String> seriesIds = episodeUtils.getEpisodesWithSeriesIds();

    LogUtils.logInfoMessage("Series ids for C32791531_TEST " + seriesIds);
    List<String> grpcEpisodesIds =
        new ArrayList<>(episodesClient.getEpisodeIdsBySeriesIds(seriesIds, "series").getIdsList());

    Collections.sort(seriesIds);
    Collections.sort(grpcEpisodesIds);

    Assert.assertEquals(seriesIds, grpcEpisodesIds);
  }

  @Test
  @Description(
      "GetEpisodeIdsBySeriesIds - Valid request with multiple series ids and distinct sources.clip")
  @TmsLink("28500787")
  public void C28500787_TEST() {
    List<String> seriesIds =
        episodeUtils.getEpisodesWithSeriesIdsAndClips().stream().limit(2).distinct().toList();

    List<Episodes> listEpisodes = episodeUtils.getDocumentsByKeyIds("series", seriesIds);

    List<String> listClips =
        new ArrayList<>(
            listEpisodes.stream()
                .flatMap(episode -> episode.getSources().stream())
                .map(Source::getClip)
                .distinct()
                .toList());

    LogUtils.logInfoMessage("Series ids for C32791638_TEST " + seriesIds);
    List<String> grpcClipIds =
        new ArrayList<>(
            episodesClient.getEpisodeIdsBySeriesIds(seriesIds, "sources.clip").getIdsList());

    Collections.sort(listClips);
    Collections.sort(grpcClipIds);

    Assert.assertEquals(listClips, grpcClipIds);
  }

  @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = NOT_FOUND)
  @Description(
      "GetEpisodeIdsBySeriesIds - Valid request with multiple series ids and invalid distinct value")
  @TmsLink("28500788")
  public void C28500788_TEST() {
    List<String> seriesIds =
        episodeUtils.getEpisodesWithSeriesIdsAndClips().stream().limit(2).distinct().toList();

    episodesClient.getEpisodeIdsBySeriesIds(seriesIds, "invalid");
  }

  @Test
  @Description("GetEpisodeIdsBySeriesIds - Invalid request with single episode")
  @TmsLink("28500789")
  public void C28500789_TEST() {
    try {
      episodesClient.getEpisodeIdsBySeriesIds(Collections.singletonList(INVALID_ID_VALUE), null);
    } catch (StatusRuntimeException e) {
      Assert.assertEquals(e.getStatus().getCode(), Status.NOT_FOUND.getCode());
      Assert.assertEquals(
          e.getMessage(), "NOT_FOUND: EpisodeIDs for requested seriesIDs not found");
    }
  }

  @Test
  @Description("GetEpisodeIdsBySeriesIds - Valid request with invalid ids")
  @TmsLink("28500790")
  public void C28500790_TEST() {
    List<String> seriesIds = episodeUtils.getEpisodesWithSeriesIds();
    seriesIds.add(INVALID_ID_VALUE);

    LogUtils.logInfoMessage("Series ids for C9019610_TEST " + seriesIds);

    List<String> mongoEpisodesIds = episodeUtils.getEpisodeIdsBySeriesIds(seriesIds);

    List<String> grpcEpisodesIds =
        new ArrayList<>(episodesClient.getEpisodeIdsBySeriesIds(seriesIds, null).getIdsList());

    Collections.sort(mongoEpisodesIds);
    Collections.sort(grpcEpisodesIds);

    Assert.assertEquals(mongoEpisodesIds, grpcEpisodesIds);
  }

  @Test
  @Description("GetPopulatedEpisodesByClipId - filter by episode name and displayName related clip")
  @TmsLink("28500850")
  public void C28500850_TEST() {
    int limit = 1;
    int offSet = 0;
    String sortName = "name";
    String sortDisplayName = "displayName";

    Episodes mongoEpisodeWithClip = episodeUtils.getRandomEpisodeWithClips();
    String clipId = mongoEpisodeWithClip.getSources().get(0).getClip();
    LogUtils.logInfoMessage("EpisodeId with Clip: " + mongoEpisodeWithClip.getId());

    String testMongoName = episodeUtils.getUpdatedEpisodeName(mongoEpisodeWithClip.getName());

    tv.pluto.ip.dal.episode.Episodes.Filter getFilterNameEpisode =
        episodesClient.getFilterEpisode(sortName, testMongoName, "string", "and");

    List<tv.pluto.ip.dal.episode.Episodes.Filter> listFilters = new ArrayList<>();
    listFilters.add(getFilterNameEpisode);

    if (!mongoEpisodeWithClip.getDisplayName().isEmpty()) {
      String displayName =
          episodeUtils.getUpdatedEpisodeName(mongoEpisodeWithClip.getDisplayName());
      tv.pluto.ip.dal.episode.Episodes.Filter getFilterDisplayNameEpisode =
          episodesClient.getFilterEpisode(sortDisplayName, displayName, "string", "and");
      listFilters.add(getFilterDisplayNameEpisode);
    }

    List<GetAllPagedSort> grpcSort = episodesClient.grpcSortAllPagedSortList(sortName, 1);

    List<EpisodePopulated> grpcEpisodesPopulated =
        populatedEpisodesUtils.mapGrpcObjectsToPojo(
            episodesClient
                .getPopulatedEpisodesByClipId(clipId, limit, offSet, grpcSort, listFilters)
                .getEpisodes()
                .getDataList(),
            EpisodePopulated.class);

    Bson sort = orderBy(ascending(sortName));

    List<EpisodePopulated> mongoEpisodesPopulated =
        populatedEpisodesUtils.getPopulatedEpisodesByClipId(clipId, limit, offSet, sort).stream()
            .filter(
                episode -> {
                  if (!episode.getDisplayName().isEmpty()) {
                    return episode.getName().equals(mongoEpisodeWithClip.getName())
                        && episode.getDisplayName().equals(mongoEpisodeWithClip.getDisplayName());
                  } else return episode.getName().equals(mongoEpisodeWithClip.getName());
                })
            .collect(Collectors.toList());

    Assert.assertEquals(grpcEpisodesPopulated.size(), limit);
    Assert.assertEquals(mongoEpisodesPopulated, grpcEpisodesPopulated);
  }

  @Test(
      dataProvider = "name-and-displayName",
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "NOT_FOUND: Episodes for requested clip ID not found")
  @TmsLink("28500851")
  @Description(
      "GetPopulatedEpisodesByClipId - Verify filter is not returning anything if filter has incorrect name")
  public void C28500851_TEST(String value) {
    Episodes mongoEpisodeWithClip = episodeUtils.getRandomEpisodeWithClips();
    String clipId = mongoEpisodeWithClip.getSources().get(0).getClip();

    tv.pluto.ip.dal.episode.Episodes.Filter getFilterNameEpisode =
        episodesClient.getFilterEpisode(value, "testData", "string", null);

    List<tv.pluto.ip.dal.episode.Episodes.Filter> listFilters = new ArrayList<>();
    listFilters.add(getFilterNameEpisode);

    List<GetAllPagedSort> grpcSort = episodesClient.grpcSortAllPagedSortList("name", 1);

    populatedEpisodesUtils.mapGrpcObjectsToPojo(
        episodesClient
            .getPopulatedEpisodesByClipId(clipId, 1, 0, grpcSort, listFilters)
            .getEpisodes()
            .getDataList(),
        EpisodePopulated.class);
  }

  @Test(
      dataProvider = "name-and-displayName",
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "NOT_FOUND: Episodes for requested clip ID not found")
  @TmsLink("28500852")
  @Description(
      "GetPopulatedEpisodesByClipId - filter has incorrect name/displayName and correct name/displayName")
  public void C28500852_TEST(String value) {
    Episodes mongoEpisodeWithClip = episodeUtils.getRandomEpisodeWithClips();
    String clipId = mongoEpisodeWithClip.getSources().get(0).getClip();
    String valueFilter;
    String secondFilter;

    if (value.equals("name")) {
      valueFilter = mongoEpisodeWithClip.getName();
      secondFilter = "displayName";
    } else {
      valueFilter = mongoEpisodeWithClip.getDisplayName();
      secondFilter = "name";
    }

    tv.pluto.ip.dal.episode.Episodes.Filter getValidFilterEpisode =
        episodesClient.getFilterEpisode(value, valueFilter, "string", null);

    tv.pluto.ip.dal.episode.Episodes.Filter getInvalidFilterEpisode =
        episodesClient.getFilterEpisode(secondFilter, "testData", "string", null);

    List<tv.pluto.ip.dal.episode.Episodes.Filter> listFilters = new ArrayList<>();
    listFilters.add(getValidFilterEpisode);
    listFilters.add(getInvalidFilterEpisode);

    List<GetAllPagedSort> grpcSort = episodesClient.grpcSortAllPagedSortList("name", 1);

    populatedEpisodesUtils.mapGrpcObjectsToPojo(
        episodesClient
            .getPopulatedEpisodesByClipId(clipId, 1, 0, grpcSort, listFilters)
            .getEpisodes()
            .getDataList(),
        EpisodePopulated.class);
  }

  @Test
  @Description("Verify 16x9 poster field in episodes")
  @TmsLink("28500859")
  public void C28500859_TEST() {
    Episodes episodesMango = episodeUtils.getRandomDocumentWithTheField("poster16_9", true);
    Image poster169 = episodesMango.getPoster16_9();

    Episode grpcEpisodesResponse =
        episodesClient.getEpisodeById(episodesMango.getId()).getDocument();
    Image grpcPoster169 =
        episodeUtils.mapGrpcObjectToPojo(grpcEpisodesResponse.getPoster169(), Image.class);

    Assert.assertNotNull(poster169);
    Assert.assertEquals(poster169, grpcPoster169);
  }

  @Test
  @Description("GetPopulatedEpisodeById - series")
  @TmsLink("28500860")
  public void C28500860_TEST() {
    Episodes episodesMango = episodeUtils.getRandomDocumentWithTheField("series", true);
    LogUtils.logInfoMessage("Test episode with series: " + episodesMango.getId());
    tv.pluto.ip.dal.episode.Episodes.EpisodePopulated episode =
        episodesClient.getPopulatedEpisodeById(episodesMango.getId(), "").getEpisode();
    Series seriesGrpc = episodeUtils.mapGrpcObjectToPojo(episode.getSeries(), Series.class);
    Series seriesMango = seriesUtils.getDocumentById(episodesMango.getSeries());

    Assert.assertEquals(seriesGrpc, seriesMango);
  }

  @Test
  @Description("GetPopulatedEpisodeById - getVodCategoryEntriesList exists")
  @TmsLink("28500899")
  public void C28500899_TEST() {
    Episodes episodesMango = episodeUtils.getRandomDocumentWithTheField("series", true);
    LogUtils.logInfoMessage("Test episode with series: " + episodesMango.getId());
    tv.pluto.ip.dal.episode.Episodes.EpisodePopulated episode =
        episodesClient.getPopulatedEpisodeById(episodesMango.getId(), "").getEpisode();
    Assert.assertNotNull(episode.getVodCategoryEntriesList());
  }

  @Test
  @Description("GetPopulatedEpisodeById - customReference exists")
  @TmsLink("28500900")
  public void C28500900_TEST() {
    Episodes episodesMango = episodeUtils.getRandomDocumentWithTheField("series", true);
    LogUtils.logInfoMessage("Test episode with series: " + episodesMango.getId());
    tv.pluto.ip.dal.episode.Episodes.EpisodePopulated episode =
        episodesClient.getPopulatedEpisodeById(episodesMango.getId(), "").getEpisode();
    Assert.assertNotNull(episode.getCustomReferencesList());
  }

  @Test
  @Description("GetPopulatedEpisodeById - ageRange")
  @TmsLink("28500861")
  public void C28500861_TEST() {
    Episodes episodesMango = episodeUtils.getRandomDocumentWithTheField("ageRange", true);
    LogUtils.logInfoMessage("Test episode with ageRange: " + episodesMango.getId());
    tv.pluto.ip.dal.episode.Episodes.EpisodePopulated episode =
        episodesClient.getPopulatedEpisodeById(episodesMango.getId(), "").getEpisode();
    List<AgeRange> ageRangeGrpc =
        episodeUtils.mapGrpcObjectsToPojo(episode.getAgeRangeList(), AgeRange.class);
    List<String> listAgeRangeIds =
        ageRangeGrpc.stream().map(AgeRange::getId).collect(Collectors.toList());
    List<String> listAgeRangeIdsMango = ageRangeUtils.getDocumentsByIds(listAgeRangeIds);

    Assert.assertEquals(listAgeRangeIdsMango, ageRangeGrpc);
  }

  @Test
  @Description("GetPopulatedEpisodeById - parent")
  @TmsLink("28500862")
  public void C28500862_TEST() {
    Episodes episodesMango = episodeUtils.getRandomDocumentWithTheField("parent", true);
    LogUtils.logInfoMessage("Test episode with parent: " + episodesMango.getId());
    tv.pluto.ip.dal.episode.Episodes.EpisodePopulated episodeGrpc =
        episodesClient.getPopulatedEpisodeById(episodesMango.getId(), "").getEpisode();
    Assert.assertNotNull(episodeGrpc.getParent());
  }

  @Test
  @Description("GetPopulatedEpisodeById - content.clip")
  @TmsLink("28500863")
  public void C28500863_TEST() {
    Episodes episodesMango = episodeUtils.getRandomDocumentWithTheField("content.clip", true);
    LogUtils.logInfoMessage("Test episode with parent: " + episodesMango.getId());
    tv.pluto.ip.dal.episode.Episodes.EpisodePopulated episodeGrpc =
        episodesClient.getPopulatedEpisodeById(episodesMango.getId(), "").getEpisode();
    boolean isClipInContent =
        episodeGrpc.getContentList().stream()
            .anyMatch(tv.pluto.ip.dal.episode.Episodes.ContentPopulated::hasClip);
    Assert.assertTrue(isClipInContent);
  }

  @Test
  @Description("GetPopulatedEpisodeById - sources.clip")
  @TmsLink("28500864")
  public void C28500864_TEST() {
    Iterator<Episodes> iteratorEpisodesMango =
        episodeUtils.getDocumentsWithTheField("sources.clip", true);
    Episodes episodesMango = null;

    while (iteratorEpisodesMango.hasNext()) {
      Episodes testEpisode = iteratorEpisodesMango.next();
      String clipId = testEpisode.getSources().get(0).getClip();

      if (clipId != null && !clipId.isEmpty()) {
        episodesMango = testEpisode;
        break;
      }
    }

    Assert.assertNotNull(episodesMango);
    LogUtils.logInfoMessage("Test episode with parent: " + episodesMango.getId());
    tv.pluto.ip.dal.episode.Episodes.EpisodePopulated episodeGrpc =
        episodesClient.getPopulatedEpisodeById(episodesMango.getId(), "").getEpisode();

    Assert.assertEquals(episodesMango.getSources().size(), episodeGrpc.getSourcesList().size());
    Assert.assertFalse(episodeGrpc.getSourcesList().isEmpty());
  }

  @Test
  @Description("GetPopulatedEpisodeById - backups.clip")
  @TmsLink("28500865")
  public void C28500865_TEST() {
    Episodes episodesMango = episodeUtils.getRandomDocumentWithTheField("backups.clip", true);
    LogUtils.logInfoMessage("Test episode with parent: " + episodesMango.getId());
    tv.pluto.ip.dal.episode.Episodes.EpisodePopulated episodeGrpc =
        episodesClient.getPopulatedEpisodeById(episodesMango.getId(), "").getEpisode();
    Assert.assertFalse(episodeGrpc.getBackupsList().isEmpty());
  }

  @Test
  @Description("GetPopulatedEpisodeById - channels")
  @TmsLink("28500866")
  public void C28500866_TEST() {
    Episodes episodesMango = episodeUtils.getRandomDocumentWithTheFieldSize("channels", 2);
    LogUtils.logInfoMessage("Test episode with parent: " + episodesMango.getId());
    tv.pluto.ip.dal.episode.Episodes.EpisodePopulated episodeGrpc =
        episodesClient.getPopulatedEpisodeById(episodesMango.getId(), "").getEpisode();
    Assert.assertFalse(episodeGrpc.getChannelsList().isEmpty());
  }

  @Test
  @Description("GetPopulatedEpisodeById - nextTimeline._id")
  @TmsLink("28500867")
  public void C28500867_TEST() {
    Episodes episodesMango = episodeUtils.getRandomDocumentWithTheField("nextTimeline._id", true);
    LogUtils.logInfoMessage("Test episode with parent: " + episodesMango.getId());
    tv.pluto.ip.dal.episode.Episodes.EpisodePopulated episodeGrpc =
        episodesClient.getPopulatedEpisodeById(episodesMango.getId(), "").getEpisode();
    Assert.assertNotNull(episodeGrpc.getNextTimeline().getId());
  }

  @Test
  @Description("GetPopulatedEpisodeById - nextTimeline.channel")
  @TmsLink("28500868")
  public void C28500868_TEST() {
    Episodes episodesMango =
        episodeUtils.getRandomDocumentWithTheField("nextTimeline.channel", true);
    LogUtils.logInfoMessage("Test episode with parent: " + episodesMango.getId());
    tv.pluto.ip.dal.episode.Episodes.EpisodePopulated episodeGrpc =
        episodesClient.getPopulatedEpisodeById(episodesMango.getId(), "").getEpisode();
    Assert.assertNotNull(episodeGrpc.getNextTimeline().getId());
  }

  @Test
  @Description("GetPopulatedEpisodeById - vodCategoryEntries")
  @TmsLink("28500869")
  public void C28500869_TEST() {
    Episodes episodesMango =
        episodeUtils.getRandomDocumentWithTheFieldSize("vodCategoryEntries", 1);
    tv.pluto.ip.dal.episode.Episodes.EpisodePopulated episodeGrpc =
        episodesClient.getPopulatedEpisodeById(episodesMango.getId(), "").getEpisode();
    Assert.assertEquals(episodeGrpc.getVodCategoryEntriesList().size(), 1);
  }

  @Test
  @Description("GetPopulatedEpisodeById - customReference")
  @TmsLink("28500870")
  public void C28500870_TEST() {
    Episodes episodesMango = episodeUtils.getRandomDocumentWithTheFieldSize("customReferences", 1);
    tv.pluto.ip.dal.episode.Episodes.EpisodePopulated episodeGrpc =
        episodesClient.getPopulatedEpisodeById(episodesMango.getId(), "").getEpisode();
    Assert.assertEquals(1, episodeGrpc.getCustomReferencesList().size());
    Assert.assertTrue(
        episodeGrpc.getCustomReferencesList().stream()
            .anyMatch(tv.pluto.ip.dal.episode.Episodes.CustomReference::hasValue));
  }

  @Test
  @Description("GetPopulatedEpisodeById - customReference with channels")
  @TmsLink("28500871")
  public void C28500871_TEST() {
    Iterator iteratorEpisodesMango =
        episodeUtils.getRandomDocumentWithTheFieldGreaterSize("customReferences");
    Episodes episodesMango = null;
    boolean isTrue = true;

    while (iteratorEpisodesMango.hasNext() && isTrue) {
      episodesMango = (Episodes) iteratorEpisodesMango.next();
      if (!episodesMango.getChannels().isEmpty()) isTrue = false;
    }

    assert episodesMango != null;
    Assert.assertNotNull(episodesMango.getChannels());
    Assert.assertFalse(episodesMango.getChannels().isEmpty());

    tv.pluto.ip.dal.episode.Episodes.EpisodePopulated episodeGrpc =
        episodesClient.getPopulatedEpisodeById(episodesMango.getId(), "channels").getEpisode();
    Assert.assertFalse(episodeGrpc.getCustomReferencesList().isEmpty());
    Assert.assertTrue(
        episodeGrpc.getCustomReferencesList().stream()
            .anyMatch(tv.pluto.ip.dal.episode.Episodes.CustomReference::hasValue));

    for (CustomReferences references : episodesMango.getCustomReferences()) {
      tv.pluto.ip.dal.episode.Episodes.CustomReference customReferencesGrpc =
          episodeGrpc.getCustomReferencesList().stream()
              .filter(c -> c.getId().equals(references.getId()))
              .findFirst()
              .get();
      Assert.assertEquals(references.getId(), customReferencesGrpc.getId());
      Assert.assertEquals(references.getType(), customReferencesGrpc.getType());
      Assert.assertEquals(references.getValue(), customReferencesGrpc.getValue().getId());
    }
  }

  @SuppressWarnings("SizeGreaterThanOrEqualsZero")
  @Test
  @Description("GetPopulatedEpisodeById - ratingDescriptors")
  @TmsLink("28500872")
  public void C28500872_TEST() {
    Episodes episodesMango = episodeUtils.getRandomDocumentWithTheField("ratingDescriptors", true);
    tv.pluto.ip.dal.episode.Episodes.EpisodePopulated episodeGrpc =
        episodesClient.getPopulatedEpisodeById(episodesMango.getId(), "").getEpisode();
    Assert.assertTrue(episodeGrpc.getRatingDescriptorsCount() >= 0);
  }

  @Test
  @Description("GetPopulatedEpisodeById - verify __v")
  @TmsLink("28500873")
  public void C28500873_TEST() {
    Episodes episodesMango = episodeUtils.getRandomDocumentWithTheField("__v", true);
    tv.pluto.ip.dal.episode.Episodes.EpisodePopulated episodeGrpc =
        episodesClient.getPopulatedEpisodeById(episodesMango.getId(), "").getEpisode();
    Assert.assertTrue(episodeGrpc.getV() >= 0);
  }

  @Test(priority = -1)
  @Description("UpdateFields - screenshot16_9 updating path")
  @TmsLink("28500896")
  public void C28500896_TEST() {
    SoftAssert softAssert = new SoftAssert();
    Episodes episodes =
        episodeUtils.getDocumentById(episodeUtils.createNewEpisodeId(episodesClientAuth));
    String newPath = episodes.getScreenshot169().getPath() + "dalTest";
    String testEpisode = episodes.getId();
    LogUtils.logInfoMessage("Test episode id: " + testEpisode);

    tv.pluto.ip.dal.episode.Episodes.Image buildImage =
        tv.pluto.ip.dal.episode.Episodes.Image.newBuilder().setPath(newPath).build();
    Episode episode = Episode.newBuilder().setScreenshot169(buildImage).build();
    UpdateFieldsResponse updateFields =
        episodesClient.getUpdateFields(testEpisode, List.of("screenshot16_9"), episode);

    String updatedPath = updateFields.getDocument().getScreenshot169().getPath();
    softAssert.assertEquals(updatedPath, newPath);

    tv.pluto.ip.dal.episode.Episodes.Image buildOldImage =
        tv.pluto.ip.dal.episode.Episodes.Image.newBuilder()
            .setPath(episodes.getScreenshot169().getPath())
            .build();

    Episode revertEpisode = Episode.newBuilder().setScreenshot169(buildOldImage).build();
    UpdateFieldsResponse revertedEpisode =
        episodesClient.getUpdateFields(testEpisode, List.of("screenshot16_9"), revertEpisode);
    String revertedEPath = revertedEpisode.getDocument().getScreenshot169().getPath();

    softAssert.assertEquals(revertedEPath, episodes.getScreenshot169().getPath());

    Episodes episodeAfterUpdate = episodeUtils.getDocumentById(testEpisode);
    episodeAfterUpdate.setUpdatedAt(null);
    episodes.setUpdatedAt(null);
    softAssert.assertEquals(episodeAfterUpdate, episodes);
    softAssert.assertAll();
  }

  @Test(priority = -1)
  @Description("UpdateFields - screenshot4_3 updating path")
  @TmsLink("28500897")
  public void C28500897_TEST() {
    SoftAssert softAssert = new SoftAssert();
    String id = episodeUtils.createNewEpisodeId(episodesClientAuth);
    Episodes episodes = episodeUtils.getDocumentById(id);
    String newPath = episodes.getScreenshot43().getPath() + "dalTest";
    String testEpisode = episodes.getId();
    LogUtils.logInfoMessage("Test episode id: " + testEpisode);

    tv.pluto.ip.dal.episode.Episodes.Image buildImage =
        tv.pluto.ip.dal.episode.Episodes.Image.newBuilder().setPath(newPath).build();

    Episode episode = Episode.newBuilder().setScreenshot43(buildImage).build();
    List<String> listUpdates = List.of("screenshot4_3");
    UpdateFieldsResponse updateFields = null;

    updateFields = episodesClient.getUpdateFields(testEpisode, listUpdates, episode);
    String updatedPath = updateFields.getDocument().getScreenshot43().getPath();
    softAssert.assertEquals(updatedPath, newPath);

    tv.pluto.ip.dal.episode.Episodes.Image buildOldImage =
        tv.pluto.ip.dal.episode.Episodes.Image.newBuilder()
            .setPath(episodes.getScreenshot43().getPath())
            .build();

    // Revert back the Episode
    Episode revertEpisode = Episode.newBuilder().setScreenshot43(buildOldImage).build();
    UpdateFieldsResponse revertedEpisode =
        episodesClient.getUpdateFields(testEpisode, List.of("screenshot4_3"), revertEpisode);
    String revertedEPath = revertedEpisode.getDocument().getScreenshot43().getPath();

    softAssert.assertEquals(revertedEPath, episodes.getScreenshot43().getPath());

    Episodes episodeAfterUpdate = episodeUtils.getDocumentById(testEpisode);
    episodeAfterUpdate.setUpdatedAt(null);
    episodes.setUpdatedAt(null);
    softAssert.assertEquals(episodeAfterUpdate, episodes);
    softAssert.assertAll();
  }

  @Test
  @Description(
      "SetAdPodDurations - returns a valid episode with an updated ad_pods duration in milliseconds")
  @TmsLink("28500889")
  public void C28500889_TEST() {
    BasicDBObject exist = new BasicDBObject("$exists", true);
    BasicDBObject adPods =
        new BasicDBObject(
            "sources.adPods", new BasicDBObject(exist).append("$ne", Collections.emptyList()));
    BasicDBObject query =
        new BasicDBObject("$and", List.of(adPods, new BasicDBObject("adPodsDuration", exist)));

    Episodes randomEpisode = (Episodes) episodeUtils.getDocuments(query, 1).get(0);
    Episode episodeGrpc = episodesClient.getEpisodeById(randomEpisode.getId()).getDocument();

    AtomicInteger mangoAddPodDuration = new AtomicInteger();
    randomEpisode
        .getSources()
        .forEach(
            source ->
                source
                    .getAdPods()
                    .forEach(
                        adPod ->
                            mangoAddPodDuration.set(
                                mangoAddPodDuration.get() + adPod.getDuration())));
    int grpcAdpPodDuration = episodesClient.setAdPodDuration(episodeGrpc).getAdPodsDuration();
    int expectedDuration = randomEpisode.getAdPodsDuration() * 1000;

    Assert.assertEquals(mangoAddPodDuration.get(), expectedDuration);
    Assert.assertEquals(mangoAddPodDuration.get(), grpcAdpPodDuration);
  }

  @Test
  @Description("SetAdPodDurations - returns a valid episode with a correct ad_pods duration")
  @TmsLink("28500886")
  public void C28500886_TEST() {
    Episodes randomEpisode =
        episodeUtils.getDocumentByKeyValueObject("adPodsDuration", new BasicDBObject("$gt", 0));
    Episode episodeGrpc = episodesClient.getEpisodeById(randomEpisode.getId()).getDocument();
    Episode grpcEpisode = episodesClient.setAdPodDuration(episodeGrpc);

    AtomicInteger addPodDuration = new AtomicInteger();

    episodeGrpc
        .getSourcesList()
        .forEach(
            source -> {
              source
                  .getAdPodsList()
                  .forEach(adPod -> addPodDuration.set(addPodDuration.get() + adPod.getDuration()));
            });

    SoftAssert softAssert = new SoftAssert();
    softAssert.assertTrue(addPodDuration.get() > 0);
    softAssert.assertEquals(grpcEpisode.getAdPodsDuration(), addPodDuration.get());
    softAssert.assertNotNull(grpcEpisode.getPoster());
    softAssert.assertNotNull(grpcEpisode.getScreenshot43());
    softAssert.assertNotNull(grpcEpisode.getDistributeAs());
    softAssert.assertNotNull(grpcEpisode.getPlaybackRestrictions());
    softAssert.assertNotNull(grpcEpisode.getAvailabilityWindows());
    softAssert.assertAll();
  }

  @Test(priority = -1)
  @Description("CalculateTotalAdPodsDuration - returns a valid episode")
  @TmsLink("28500887")
  public void C28500887_TEST() {
    Episodes episodes =
        episodeUtils.getDocumentByKeyValueObject("adPodsDuration", new BasicDBObject("$gt", 0));
    Episode episodeGrpc = episodesClient.getEpisodeById(episodes.getId()).getDocument();
    Episode calculateGrpcEpisode = episodesClient.calculateTotalAdPodsDuration(episodeGrpc);

    SoftAssert softAssert = new SoftAssert();
    softAssert.assertEquals(calculateGrpcEpisode.getId(), episodes.getId());
    softAssert.assertNotNull(calculateGrpcEpisode.getPoster());
    softAssert.assertNotNull(calculateGrpcEpisode.getScreenshot169());
    softAssert.assertNotNull(calculateGrpcEpisode.getScreenshot43());
    softAssert.assertNotNull(calculateGrpcEpisode.getDistributeAs());
    softAssert.assertNotNull(calculateGrpcEpisode.getPlaybackRestrictions());
    softAssert.assertNotNull(calculateGrpcEpisode.getAvailabilityWindows());
    softAssert.assertAll();
  }

  @Test(dataProvider = "sort-values")
  @Description("GetChannelLibraryRequest - sort by updatedAt")
  @TmsLink("28500903")
  public void C28500903_TEST(int sort) {
    Channels channelsDb = channelsUtils.getDocumentById("51c75f7bb6f26ba1cd000013");
    if (channelsDb == null) channelsDb = channelsUtils.getRandomDocument();

    LogUtils.logInfoMessage("ChannelId: " + channelsDb.getId());
    tv.pluto.ip.dal.episode.Episodes.GetAllPagedSort updatedAt =
        tv.pluto.ip.dal.episode.Episodes.GetAllPagedSort.newBuilder()
            .setField("updatedAt")
            .setValue(sort)
            .build();

    tv.pluto.ip.dal.episode.Episodes.GetChannelLibraryRequest request =
        tv.pluto.ip.dal.episode.Episodes.GetChannelLibraryRequest.newBuilder()
            .setChannelId(channelsDb.getId())
            .addSort(updatedAt)
            .build();

    List<tv.pluto.ip.dal.episode.Episodes.ChannelLibraryEpisode> grpcList =
        episodesClient.getChannelLibrary(request).getDocumentsList();
    Assert.assertFalse(grpcList.isEmpty());

    List<tv.pluto.ip.dal.episode.Episodes.ChannelLibraryEpisode> sortedList =
        new ArrayList<>(grpcList);

    if (sort == 1)
      sortedList.sort(Comparator.comparing(e -> e.getUpdatedAt(), Comparator.naturalOrder()));
    else sortedList.sort(Comparator.comparing(e -> e.getUpdatedAt(), Comparator.reverseOrder()));

    Assert.assertEquals(sortedList, grpcList);
  }

  @Test
  @Description("GetAllIds - happy path")
  @TmsLink("28500906")
  public void C28500906_TEST() {
    boolean isPublished = true;
    int limit = 1;
    // published true return only published true but false can return true and false
    tv.pluto.ip.dal.episode.Episodes.GetAllIdsResponse allIds =
        episodesClient.getAllIds(isPublished, limit, 0, true, null);

    Assert.assertEquals(limit, allIds.getMetadata().getTotalItems());

    List<String> listEpisodeIds =
        allIds.getIdsList().stream().map(ob -> ob.getId()).collect(Collectors.toList());
    List<Episodes> listMongoEpisodes = episodeUtils.getDocumentsByIds(listEpisodeIds);

    Assert.assertEquals(allIds.getMetadata().getTotalItems(), listMongoEpisodes.size());
    Assert.assertEquals(0, allIds.getMetadata().getCurrentOffset());

    List<tv.pluto.ip.dal.episode.Episodes.EpisodeSeriesIds> idsList =
        new ArrayList<>(allIds.getIdsList());
    List<tv.pluto.ip.dal.episode.Episodes.EpisodeSeriesIds> dbIdsList = new ArrayList<>();

    listMongoEpisodes.forEach(
        episode -> {
          String episodeTime = episode.getUpdatedAt().toInstant(ZoneOffset.UTC).toString();
          tv.pluto.ip.dal.episode.Episodes.EpisodeSeriesIds episodeSeries =
              tv.pluto.ip.dal.episode.Episodes.EpisodeSeriesIds.newBuilder()
                  .setId(episode.getId())
                  .setSeries(episode.getSeries())
                  .setUpdatedAt(episodeTime)
                  .build();

          Assert.assertEquals(episode.getPublished(), isPublished);
          dbIdsList.add(episodeSeries);
        });

    idsList.sort(Comparator.comparing(tv.pluto.ip.dal.episode.Episodes.EpisodeSeriesIds::getId));
    dbIdsList.sort(Comparator.comparing(tv.pluto.ip.dal.episode.Episodes.EpisodeSeriesIds::getId));
    Assert.assertEquals(idsList, dbIdsList);
  }

  @Test
  @Description("GetAllIds - updatedAt")
  @TmsLink("28500919")
  public void C28500919_TEST() {
    tv.pluto.ip.dal.episode.Episodes.GetAllIdsResponse allIds =
        episodesClient.getAllIds(true, 5, 0, true, null);

    List<String> listEpisodeIds =
        allIds.getIdsList().stream().map(ob -> ob.getId()).collect(Collectors.toList());
    List<Episodes> listMongoEpisodes = episodeUtils.getDocumentsByIds(listEpisodeIds);
    Assert.assertFalse(listMongoEpisodes.isEmpty());

    allIds
        .getIdsList()
        .forEach(
            e -> {
              Episodes episodes =
                  listMongoEpisodes.stream()
                      .filter(l -> l.getId().equals(e.getId()))
                      .findFirst()
                      .orElse(null);

              Instant responseTime = Instant.parse(e.getUpdatedAt());
              assert episodes != null;
              Assert.assertEquals(responseTime, episodes.getUpdatedAt().toInstant(ZoneOffset.UTC));
            });
  }

  @Test
  @Description("GetAllIds - total count")
  @TmsLink("28500907")
  public void C28500907_TEST() {
    boolean isPublished = true;
    int limit = 1;
    tv.pluto.ip.dal.episode.Episodes.GetAllIdsResponse allIds =
        episodesClient.getAllIds(isPublished, limit, 0, false, null);
    Assert.assertEquals(limit, allIds.getIdsList().size());

    long numPublished = episodeUtils.getKeyValueCount("published", isPublished);
    Assert.assertEquals(numPublished, allIds.getMetadata().getTotalItems());
  }

  @Test(enabled = false)
  @Description("GetChannelLibrary - existing channel, with limit and sorting")
  @TmsLink("28500902")
  public void C28500902_TEST() {
    Channels channels = channelUtils.getRandomDocument();
    LogUtils.logInfoMessage("C23971918_TEST channelId = " + channels.getId());

    GetChannelLibraryRequest request = new GetChannelLibraryRequest();
    request.setChannelId(channels.getId());
    request.setLimit(10);
    request.setSort(
        Collections.singletonList(
            GetAllPagedSort.newBuilder().setField("_id").setValue(1).build()));

    GetChannelLibraryResponse mongoResponse = getChannelLibraryUtils.getChannelLibrary(request);

    GetChannelLibraryResponse grpcResponse =
        getChannelLibraryUtils.mapGrpcObjectToPojo(
            episodesClient.getChannelLibrary(request), GetChannelLibraryResponse.class);

    Assert.assertEquals(mongoResponse, grpcResponse);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "NOT_FOUND: Episode document for requested ID not found")
  @Description("GetChannelLibrary - non existing channel")
  @TmsLink("28500904")
  public void C28500904_TEST() {
    GetChannelLibraryRequest request = new GetChannelLibraryRequest();
    request.setChannelId(INVALID_ID_VALUE);

    episodesClient.getChannelLibrary(request);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "INVALID_ARGUMENT: Channel ID cannot be undefined or empty")
  @Description("GetChannelLibrary - request without channel ID")
  @TmsLink("28500905")
  public void C28500905_TEST() {
    episodesClient.getChannelLibrary(new GetChannelLibraryRequest());
  }

  @Test
  @Description("GetEpisodeUsingExternalId - happy path")
  @TmsLink("28500908")
  public void C28500908_TEST() {
    Episodes episodes = episodeUtils.getExistsAndSizeNotZero("channels");
    String channelId = episodes.getChannels().get(0).toString();

    tv.pluto.ip.dal.episode.Episodes.GetEpisodeUsingExternalIdRequest request =
        tv.pluto.ip.dal.episode.Episodes.GetEpisodeUsingExternalIdRequest.newBuilder()
            .setPublished(episodes.getPublished())
            .setActiveRegion(episodes.getActiveRegion())
            .setGenre(episodes.getGenre())
            .setCategory(episodes.getCategory())
            .addAllChannels(List.of(channelId))
            .build();

    tv.pluto.ip.dal.episode.Episodes.GetEpisodeUsingExternalIdResponse episodeExternalId =
        episodesClient.getEpisodesUsingExternalId(request);

    List<Episodes> listEpisodes =
        episodeUtils.mapGrpcObjectsToPojo(episodeExternalId.getDocumentsList(), Episodes.class);
    Assert.assertFalse(listEpisodes.isEmpty());

    List<String> listEpisodesIds =
        listEpisodes.stream().map(Episodes::getId).collect(Collectors.toList());

    boolean allIdsInEpisodes =
        listEpisodes.stream()
            .flatMap(ser -> ser.getChannels().stream())
            .anyMatch(c -> c.toString().equals(channelId));

    List<Episodes> listEpisodesDb = episodeUtils.getDocumentsByIds(listEpisodesIds);

    Assert.assertTrue(allIdsInEpisodes);
    Assert.assertEquals(listEpisodesDb.size(), listEpisodes.size());

    listEpisodesDb.sort(Comparator.comparing(Episodes::getId));
    listEpisodes.sort(Comparator.comparing(Episodes::getId));

    Assert.assertEquals(listEpisodesDb, listEpisodes);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp =
          "NOT_FOUND: No Episode documents found for provided external ID")
  @Description("GetEpisodesUsingExternalId - invalid channelId")
  @TmsLink("28500909")
  public void C28500909_TEST() {
    Episodes episodes = episodeUtils.getExistsAndSizeNotZero("channels");

    tv.pluto.ip.dal.episode.Episodes.GetEpisodeUsingExternalIdRequest request =
        tv.pluto.ip.dal.episode.Episodes.GetEpisodeUsingExternalIdRequest.newBuilder()
            .setPublished(episodes.getPublished())
            .setActiveRegion(episodes.getActiveRegion())
            .setGenre(episodes.getGenre())
            .setCategory(episodes.getCategory())
            .addAllChannels(List.of("00000f7bb6f26ba1cd00002d"))
            .build();

    episodesClient.getEpisodesUsingExternalId(request);
  }

  @Test
  @Description("GetPopulatedEpisodeById - with the empty Timeline")
  @TmsLink("28500913")
  public void C28500913_TEST() {
    Episodes episodes =
        episodeUtils.getDocumentByKeyValueObject("nextTimeline", new BasicDBObject());
    tv.pluto.ip.dal.episode.Episodes.EpisodePopulated episode =
        episodesClient.getPopulatedEpisodeById(episodes.getId(), "").getEpisode();
    Assert.assertEquals(episodes.getId(), episode.getId());
  }

  @Test
  @Description("GetPopulatedEpisodeById - with the empty content field")
  @TmsLink("28500914")
  public void C28500914_TEST() {
    BasicDBObject query = new BasicDBObject();
    query.append("$size", 0);
    Episodes episodes = episodeUtils.getDocumentByKeyValueObject("content", query);
    String id = episodes.getId();
    tv.pluto.ip.dal.episode.Episodes.EpisodePopulated episode =
        episodesClient.getPopulatedEpisodeById(id, "").getEpisode();
    Assert.assertEquals(id, episode.getId());

    Episode episodeById = episodesClient.getEpisodeById(id).getDocument();
    Assert.assertEquals(id, episodeById.getId());
  }

  @Test
  @Description("GetPopulatedEpisodeById - with external ids")
  @TmsLink("28500920")
  public void C28500920_TEST() {
    Episodes episodes = episodeUtils.getExistsAndSizeNotZero("externalIds");
    String id = episodes.getId();
    tv.pluto.ip.dal.episode.Episodes.EpisodePopulated episode =
        episodesClient.getPopulatedEpisodeById(id, "").getEpisode();
    Assert.assertEquals(id, episode.getId());

    Episode episodeById = episodesClient.getEpisodeById(id).getDocument();

    Assert.assertFalse(episode.getExternalIdsList().isEmpty());
    Assert.assertEquals(episode.getExternalIdsList(), episodeById.getExternalIdsList());
  }

  @Test
  @Description("GetPopulatedEpisodeById - with no external ids")
  @TmsLink("28500921")
  public void C28500921_TEST() {
    Episodes episodes = episodeUtils.getRandomDocumentWithTheField("externalIds", false);
    String id = episodes.getId();
    tv.pluto.ip.dal.episode.Episodes.EpisodePopulated episode =
        episodesClient.getPopulatedEpisodeById(id, "").getEpisode();
    Assert.assertEquals(id, episode.getId());
    Assert.assertTrue(episode.getExternalIdsList().isEmpty());
  }

  @Test
  @Description("GetPopulatedEpisodeById - path series")
  @TmsLink("28500874")
  public void C28500874_TEST() {
    Episodes episodes = episodeUtils.getRandomDocumentWithTheField("externalIds", false);
    String id = episodes.getId();
    tv.pluto.ip.dal.episode.Episodes.EpisodePopulated episode =
        episodesClient.getPopulatedEpisodeById(id, "series").getEpisode();
    Assert.assertEquals(episodes.getSeries(), episode.getSeries().getId());
  }

  @Test
  @Description("GetPopulatedEpisodeById - path ageRange")
  @TmsLink("28500875")
  public void C28500875_TEST() {
    Episodes episodes = episodeUtils.getRandomDocumentWithTheField("externalIds", false);
    tv.pluto.ip.dal.episode.Episodes.EpisodePopulated episodeGrpc =
        episodesClient.getPopulatedEpisodeById(episodes.getId(), "ageRange").getEpisode();
    Set<String> dbAgeRanges =
        episodes.getAgeRange().stream().map(ObjectId::toString).collect(Collectors.toSet());
    Set<String> grpc =
        episodeGrpc.getAgeRangeList().stream()
            .map(tv.pluto.ip.dal.episode.Episodes.AgeRange::getId)
            .collect(Collectors.toSet());
    Assert.assertEquals(dbAgeRanges, grpc);
  }

  @Test
  @Description("GetPopulatedEpisodeById - path parent")
  @TmsLink("28500876")
  public void C28500876_TEST() {
    Episodes episodes = episodeUtils.getRandomDocumentWithTheField("externalIds", false);
    String id = episodes.getId();
    tv.pluto.ip.dal.episode.Episodes.EpisodePopulated episodeGrpc =
        episodesClient.getPopulatedEpisodeById(id, "parent").getEpisode();
    Assert.assertEquals(episodes.getParent(), episodeGrpc.getParent().getId());
  }

  @Test
  @Description("GetPopulatedEpisodeById - path content.clip")
  @TmsLink("28500877")
  public void C28500877_TEST() {
    Episodes episodes = episodeUtils.getRandomDocumentWithTheField("externalIds", false);
    String id = episodes.getId();
    tv.pluto.ip.dal.episode.Episodes.EpisodePopulated episodeGrpc =
        episodesClient.getPopulatedEpisodeById(id, "content.clip").getEpisode();
    Set<String> setGrpc =
        episodeGrpc.getContentList().stream()
            .map(c -> c.getClip().getId())
            .collect(Collectors.toSet());
    Set<String> setMongoDb =
        episodes.getContent().stream().map(Content::getClip).collect(Collectors.toSet());
    Assert.assertEquals(setGrpc, setMongoDb);
  }

  @Test
  @Description("GetPopulatedEpisodeById - path sources.clip")
  @TmsLink("28500878")
  public void C28500878_TEST() {
    Episodes episodes = episodeUtils.getRandomDocumentWithTheField("externalIds", false);
    String id = episodes.getId();
    tv.pluto.ip.dal.episode.Episodes.EpisodePopulated episodeGrpc =
        episodesClient.getPopulatedEpisodeById(id, "sources.clip").getEpisode();
    Set<String> setGrpc =
        episodeGrpc.getSourcesList().stream()
            .map(c -> c.getClip().getId())
            .collect(Collectors.toSet());
    Set<String> setMongoDb =
        episodes.getSources().stream().map(Source::getClip).collect(Collectors.toSet());
    Assert.assertEquals(setGrpc, setMongoDb);
  }

  @Test
  @Description("GetPopulatedEpisodeById - path backups.clip")
  @TmsLink("28500879")
  public void C28500879_TEST() {
    Episodes episodes = episodeUtils.getRandomDocumentWithTheField("externalIds", false);
    String id = episodes.getId();
    tv.pluto.ip.dal.episode.Episodes.EpisodePopulated episodeGrpc =
        episodesClient.getPopulatedEpisodeById(id, "backups.clip").getEpisode();
    Set<String> setGrpc =
        episodeGrpc.getBackupsList().stream()
            .map(c -> c.getClip().getId())
            .collect(Collectors.toSet());
    Set<String> setMongoDb =
        episodes.getBackups().stream().map(Backups::getClip).collect(Collectors.toSet());
    Assert.assertEquals(setGrpc, setMongoDb);
  }

  @Test
  @Description("GetPopulatedEpisodeById - path channels")
  @TmsLink("28500880")
  public void C28500880_TEST() {
    Episodes episodes = episodeUtils.getRandomDocumentWithTheField("externalIds", false);
    String id = episodes.getId();
    tv.pluto.ip.dal.episode.Episodes.EpisodePopulated episodeGrpc =
        episodesClient.getPopulatedEpisodeById(id, "channels").getEpisode();
    Set<String> setGrpc =
        episodeGrpc.getChannelsList().stream()
            .map(tv.pluto.ip.dal.episode.Episodes.Channel::getId)
            .collect(Collectors.toSet());
    List<String> listMongoDb =
        episodes.getChannels().stream().map(ObjectId::toString).collect(Collectors.toList());

    List<Channels> listChannels = channelsUtils.getDocumentsByIds(listMongoDb);
    Set<String> setMongoDb = listChannels.stream().map(Channels::getId).collect(Collectors.toSet());

    // episode.channel should be in channels collection
    Assert.assertEquals(setGrpc, setMongoDb);
  }

  @Test
  @Description("GetPopulatedEpisodeById - path nextTimeline._id")
  @TmsLink("28500881")
  public void C28500881_TEST() {
    Episodes episodes = episodeUtils.getRandomDocumentWithTheField("externalIds", false);
    String id = episodes.getId();
    tv.pluto.ip.dal.episode.Episodes.EpisodePopulated episodeGrpc =
        episodesClient.getPopulatedEpisodeById(id, "nextTimeline._id").getEpisode();
    Assert.assertEquals(
        episodeGrpc.getNextTimeline().getId().getId(), episodes.getNextTimeline().get_id());
    Assert.assertEquals(
        episodeGrpc.getNextTimeline().getId().getChannel(),
        episodes.getNextTimeline().getChannel());
  }

  @Test
  @Description("GetPopulatedEpisodeById - path nextTimeline.channel")
  @TmsLink("28500882")
  public void C28500882_TEST() {
    Episodes episodes = episodeUtils.getRandomDocumentWithTheField("externalIds", false);
    String id = episodes.getId();
    tv.pluto.ip.dal.episode.Episodes.EpisodePopulated episodeGrpc =
        episodesClient.getPopulatedEpisodeById(id, "nextTimeline.channel").getEpisode();
    Assert.assertEquals(
        episodeGrpc.getNextTimeline().getId().getChannel(),
        episodes.getNextTimeline().getChannel());
  }

  @Test
  @Description("GetPopulatedEpisodeById - path customReferences")
  @TmsLink("28500883")
  public void C28500883_TEST() {
    Episodes episodes =
        episodeUtils.getRandomDocumentWithTheField("externalIds", false, "customReferences", true);
    String id = episodes.getId();
    tv.pluto.ip.dal.episode.Episodes.EpisodePopulated episodeGrpc =
        episodesClient.getPopulatedEpisodeById(id, "customReferences").getEpisode();

    Set<String> setGrpcReferences =
        episodeGrpc.getCustomReferencesList().stream()
            .map(c -> c.getId())
            .collect(Collectors.toSet());
    Set<String> setMongoDbReferences =
        episodes.getCustomReferences().stream().map(c -> c.getId()).collect(Collectors.toSet());
    Assert.assertEquals(setGrpcReferences, setMongoDbReferences);
  }

  @Test(
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp = "FAILED_PRECONDITION: Populate is invalid value for: dal")
  @Description("GetPopulatedEpisodeById - invalid path")
  @TmsLink("28500884")
  public void C28500884_TEST() {
    Episodes episodes = episodeUtils.getRandomDocument();
    episodesClient.getPopulatedEpisodeById(episodes.getId(), "dal");
  }

  @Test
  @Description("GetAllIds - vod flag offset")
  @TmsLink("28500915")
  public void C28500915_TEST() {
    int limit = 10;
    tv.pluto.ip.dal.episode.Episodes.GetAllIdsResponse allIds =
        episodesClient.getAllIds(null, limit, 1, true, true);
    Assert.assertTrue(allIds.getMetadata().getTotalItems() <= limit);

    List<String> listEpisodeIds =
        allIds.getIdsList().stream().map(ob -> ob.getId()).collect(Collectors.toList());
    List<Episodes> listMongoEpisodes = episodeUtils.getDocumentsByIds(listEpisodeIds);
    listMongoEpisodes.forEach(episode -> Assert.assertTrue(episode.getDistributeAs().getAvod()));
    allIds.getIdsList().forEach(doc -> Assert.assertFalse(doc.getSeries().isEmpty()));
  }

  @Test
  @Description("GetAllIds - vod flag true")
  @TmsLink("28500916")
  public void C28500916_TEST() {
    int limit = 15;
    boolean isVod = true;
    tv.pluto.ip.dal.episode.Episodes.GetAllIdsResponse allIds =
        new EpisodesClient(grpcChannelManager.getChannel(), false)
            .getAllIds(null, limit, 0, true, isVod);
    Assert.assertTrue(limit >= allIds.getMetadata().getTotalItems());

    List<String> listEpisodeIds =
        allIds.getIdsList().stream().map(ob -> ob.getId()).collect(Collectors.toList());
    List<Episodes> listMongoEpisodes = episodeUtils.getDocumentsByIds(listEpisodeIds);
    listMongoEpisodes.forEach(
        episode -> {
          if (!episode.getDistributeAs().getAvod()) {
            String failMessage =
                String.format(
                    "Episode: %s, get DistributeAs.getAvod is %s", episode.getId(), !isVod);
            Assert.fail(failMessage);
          }
        });

    listMongoEpisodes.forEach(episode -> Assert.assertTrue(episode.getDistributeAs().getAvod()));

    allIds.getIdsList().forEach(doc -> Assert.assertFalse(doc.getSeries().isEmpty()));
  }

  @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = NOT_FOUND)
  @Description("GetPopulatedEpisodesByIds - NOT_FOUND error")
  @TmsLink("28500923")
  public void C28500923_TEST() {
    episodesClient.getPopulatedEpisodesByIds(List.of(INVALID_ID_VALUE), "");
  }

  @Test(expectedExceptions = RuntimeException.class, expectedExceptionsMessageRegExp = "INTERNAL.*")
  @Description("GetPopulatedEpisodesByIds - INTERNAL_ERROR error")
  @TmsLink("28500924")
  public void C28500924_TEST() {
    episodesClient.getPopulatedEpisodesByIds(List.of("test"), "");
  }

  @Test(
      expectedExceptions = RuntimeException.class,
      expectedExceptionsMessageRegExp = "FAILED_PRECONDITION.*")
  @Description("GetPopulatedEpisodesByIds - FAILED_PRECONDITION error")
  @TmsLink("28500925")
  public void C28500925_TEST() {
    episodesClient.getPopulatedEpisodesByIds(List.of(""), "test");
  }

  @Test
  @Description("GetPopulatedEpisodesByIds - ageRange field")
  @TmsLink("28500926")
  public void C28500926_TEST() {
    List<String> randomEpisodeIds = episodeUtils.getIdsOfEpisodesWithAgeRange(2);
    Assert.assertEquals(randomEpisodeIds.size(), 2);

    List<Episodes> listEpisodes = episodeUtils.getDocumentsByIds(randomEpisodeIds);
    Set<String> setDbAgeRanges =
        listEpisodes.stream()
            .flatMap(e -> e.getAgeRange().stream())
            .map(ObjectId::toString)
            .collect(Collectors.toSet());

    List<tv.pluto.ip.dal.episode.Episodes.AgeRangeEpisode> listClientAgeRangeEpisode =
        episodesClient.getAgeRangePopulatedEpisodesByIds(randomEpisodeIds).getDocumentsList();

    Set<String> clientAgeEpisodeIds =
        listClientAgeRangeEpisode.stream()
            .flatMap(e -> e.getAgeRangeList().stream())
            .map(o -> o.getId())
            .collect(Collectors.toSet());

    Assert.assertFalse(listClientAgeRangeEpisode.isEmpty());
    Assert.assertEquals(setDbAgeRanges, clientAgeEpisodeIds);
  }

  @Test
  @Description("GetPopulatedEpisodesByIds - series field")
  @TmsLink("28500927")
  public void C28500927_TEST() {
    List<Episodes> listEpisodes = episodeUtils.getEpisodesWithNeNullField("series", 5);
    List<String> randomEpisodeIds =
        listEpisodes.stream().map(Episodes::getId).collect(Collectors.toList());
    Assert.assertEquals(listEpisodes.size(), 5);
    LogUtils.logInfoMessage("ListEpisodes: " + randomEpisodeIds);

    List<String> listSeriesIds =
        listEpisodes.stream().map(Episodes::getSeries).collect(Collectors.toList());
    List<Series> listSeries = seriesUtils.getDocumentsByIds(listSeriesIds);
    Set<Series> setSeries = new HashSet<>(listSeries);

    List<tv.pluto.ip.dal.episode.Episodes.EpisodePopulated> populatedResponse =
        episodesClient.getPopulatedEpisodesByIds(randomEpisodeIds, "series").getEpisodesList();

    List<tv.pluto.ip.dal.episode.Episodes.Series> setPopulatedSeries =
        populatedResponse.stream().map(e -> e.getSeries()).collect(Collectors.toList());

    List<Series> trSeries = seriesUtils.mapGrpcObjectsToPojo(setPopulatedSeries, Series.class);
    Set<Series> trSetSeries = new HashSet<>(trSeries);

    Assert.assertEquals(setSeries, trSetSeries);
  }

  @Test
  @Description("GetPopulatedEpisodesByIds - content.clip field")
  @TmsLink("28500928")
  public void C28500928_TEST() {
    int limit = 2;
    String testFields = "content.clip";
    List<Episodes> listEpisodes = episodeUtils.getEpisodesWithNeNullField(testFields, limit);
    List<String> randomEpisodeIds =
        listEpisodes.stream().map(Episodes::getId).collect(Collectors.toList());
    Assert.assertEquals(listEpisodes.size(), limit);
    LogUtils.logInfoMessage("ListEpisodes: " + randomEpisodeIds);

    List<String> listClipIdsDb =
        listEpisodes.stream()
            .flatMap(e -> e.getContent().stream())
            .map(Content::getClip)
            .collect(Collectors.toList());
    List<Clips> listClips = clipsUtils.getDocumentsByIds(listClipIdsDb);

    List<tv.pluto.ip.dal.episode.Episodes.EpisodePopulated> populatedResponse =
        episodesClient.getPopulatedEpisodesByIds(randomEpisodeIds, testFields).getEpisodesList();

    List<tv.pluto.ip.dal.episode.Episodes.Clip> grpcClipResponse =
        populatedResponse.stream()
            .flatMap(e -> e.getContentList().stream())
            .map(c -> c.getClip())
            .collect(Collectors.toList());

    List<Clips> trClips = seriesUtils.mapGrpcObjectsToPojo(grpcClipResponse, Clips.class);

    Set<Clips> trSetClips = clipsUtils.setCategoryAndVotes(trClips);
    Set<Clips> setClips = clipsUtils.setCategoryAndVotes(listClips);

    Assert.assertEquals(setClips, trSetClips);
  }

  @Test
  @Description("GetPopulatedEpisodesByIds - sources.clip field")
  @TmsLink("28500929")
  public void C28500929_TEST() {
    int limit = 2;
    String testFields = "sources.clip";
    List<Episodes> listEpisodes = episodeUtils.getEpisodesWithNeNullField(testFields, limit);
    List<String> randomEpisodeIds =
        listEpisodes.stream().map(Episodes::getId).collect(Collectors.toList());
    Assert.assertEquals(limit, listEpisodes.size());
    LogUtils.logInfoMessage("ListEpisodes: " + randomEpisodeIds);

    List<String> listClipIdsDb =
        listEpisodes.stream()
            .flatMap(e -> e.getSources().stream())
            .map(e -> e.getClip())
            .collect(Collectors.toList());
    List<Clips> listClips = clipsUtils.getDocumentsByIds(listClipIdsDb);

    List<tv.pluto.ip.dal.episode.Episodes.EpisodePopulated> populatedResponse =
        episodesClient.getPopulatedEpisodesByIds(randomEpisodeIds, testFields).getEpisodesList();

    List<tv.pluto.ip.dal.episode.Episodes.Clip> grpcClipResponse =
        populatedResponse.stream()
            .flatMap(e -> e.getSourcesList().stream())
            .map(c -> c.getClip())
            .collect(Collectors.toList());

    List<Clips> trClips = seriesUtils.mapGrpcObjectsToPojo(grpcClipResponse, Clips.class);
    Set<Clips> trSetClips = clipsUtils.setCategoryAndVotes(trClips);
    Set<Clips> setClips = clipsUtils.setCategoryAndVotes(listClips);

    Assert.assertFalse(setClips.isEmpty());
    Assert.assertEquals(setClips, trSetClips);
  }

  @Test
  @Description("GetPopulatedEpisodesByIds - backups.clip field")
  @TmsLink("28500930")
  public void C28500930_TEST() {
    int limit = 2;
    String testFields = "backups.clip";
    Iterator iteratorVodCategories = episodeUtils.getExistsAndSizeNotZeroIterator(testFields, 1);
    List listEpisodesIterator = List.of(iteratorVodCategories.next(), iteratorVodCategories.next());
    List<Episodes> listEpisodes =
        episodeUtils.mapGrpcObjectsToPojo(listEpisodesIterator, Episodes.class);

    List<String> randomEpisodeIds =
        listEpisodes.stream().map(Episodes::getId).collect(Collectors.toList());
    Assert.assertEquals(limit, listEpisodes.size());
    LogUtils.logInfoMessage("ListEpisodes: " + randomEpisodeIds);

    List<String> listClipIdsDb =
        listEpisodes.stream()
            .flatMap(e -> e.getBackups().stream())
            .map(e -> e.getClip())
            .collect(Collectors.toList());
    List<Clips> listClips = clipsUtils.getDocumentsByIds(listClipIdsDb);
    Set<String> setClips = listClips.stream().map(c -> c.getId()).collect(Collectors.toSet());

    List<tv.pluto.ip.dal.episode.Episodes.EpisodePopulated> populatedResponse =
        episodesClient.getPopulatedEpisodesByIds(randomEpisodeIds, testFields).getEpisodesList();

    List<tv.pluto.ip.dal.episode.Episodes.Clip> grpcClipResponse =
        populatedResponse.stream()
            .flatMap(e -> e.getBackupsList().stream())
            .map(c -> c.getClip())
            .collect(Collectors.toList());

    List<Clips> trClips = seriesUtils.mapGrpcObjectsToPojo(grpcClipResponse, Clips.class);
    Set<String> trSetClips = trClips.stream().map(c -> c.getId()).collect(Collectors.toSet());

    LogUtils.logInfoMessage("BackupSize: " + setClips.size());
    Assert.assertTrue(setClips.size() > 0);
    Assert.assertEquals(setClips, trSetClips);
  }

  @Test
  @Description("GetPopulatedEpisodesByIds - channels field")
  @TmsLink("28500931")
  public void C28500931_TEST() {
    int limit = 2;
    String testFields = "channels";
    Iterator iteratorVodCategories = episodeUtils.getEpisodesWithChannels();
    List listEpisodesIterator = List.of(iteratorVodCategories.next(), iteratorVodCategories.next());
    List<Episodes> listEpisodes =
        episodeUtils.mapGrpcObjectsToPojo(listEpisodesIterator, Episodes.class);

    List<String> randomEpisodeIds =
        listEpisodes.stream().map(Episodes::getId).collect(Collectors.toList());
    Assert.assertEquals(listEpisodes.size(), limit);
    LogUtils.logInfoMessage("ListEpisodes: " + randomEpisodeIds);

    List<String> listChannelIdsDb =
        listEpisodes.stream()
            .flatMap(e -> e.getChannels().stream())
            .map(ObjectId::toString)
            .collect(Collectors.toList());
    List<Channels> listChannels = channelsUtils.getDocumentsByIds(listChannelIdsDb);
    Set<Channels> setChannels = channelsUtils.setChannelsCategoryAndVotes(listChannels);

    List<tv.pluto.ip.dal.episode.Episodes.EpisodePopulated> populatedResponse =
        episodesClient.getPopulatedEpisodesByIds(randomEpisodeIds, testFields).getEpisodesList();

    List<tv.pluto.ip.dal.episode.Episodes.Channel> listChannelsGrpc =
        populatedResponse.stream()
            .flatMap(e -> e.getChannelsList().stream())
            .collect(Collectors.toList());
    List<Channels> trChannels =
        channelsUtils.mapGrpcObjectsToPojo(listChannelsGrpc, Channels.class);
    Set<Channels> setTrcChannels = channelsUtils.setChannelsCategoryAndVotes(trChannels);

    Assert.assertFalse(setChannels.isEmpty());

    List<Channels> listChannelsSorted = channelsUtils.getChannelsGracenoteSorted(setChannels);
    List<Channels> listTrcChannelsSorted = channelsUtils.getChannelsGracenoteSorted(setTrcChannels);

    Assert.assertEquals(listChannelsSorted, listTrcChannelsSorted);
  }

  @Test
  @Description("GetPopulatedEpisodesByIds - nextTimeline._id field")
  @TmsLink("28500932")
  public void C28500932_TEST() {
    int limit = 2;
    String testFields = "nextTimeline._id";
    Iterator iteratorVodCategories = episodeUtils.getExistsAndSizeNotZeroIterator(testFields, 3);
    List listEpisodesIterator = List.of(iteratorVodCategories.next(), iteratorVodCategories.next());
    List<Episodes> listEpisodes =
        episodeUtils.mapGrpcObjectsToPojo(listEpisodesIterator, Episodes.class);
    List<String> randomEpisodeIds =
        listEpisodes.stream().map(Episodes::getId).collect(Collectors.toList());
    Assert.assertEquals(listEpisodes.size(), limit);
    LogUtils.logInfoMessage("ListEpisodes: " + randomEpisodeIds);

    Set<NextTimeline> setTimelineIdsDb =
        listEpisodes.stream().map(e -> e.getNextTimeline()).collect(Collectors.toSet());

    List<tv.pluto.ip.dal.episode.Episodes.EpisodePopulated> populatedResponse =
        episodesClient.getPopulatedEpisodesByIds(randomEpisodeIds, testFields).getEpisodesList();

    List<NextTimeline> nextTimelineList = new ArrayList<>();

    for (tv.pluto.ip.dal.episode.Episodes.EpisodePopulated episodePopulated : populatedResponse) {
      NextTimeline newTimeline = new NextTimeline();
      newTimeline.set_id(episodePopulated.getNextTimeline().getId().toString());
      newTimeline.setChannel(episodePopulated.getNextTimeline().getChannel().getId());
      newTimeline.setStart(Date.from(Instant.parse(episodePopulated.getNextTimeline().getStart())));

      nextTimelineList.add(newTimeline);
    }

    Set<NextTimeline> nextTimelineGrpcSet = new HashSet<>(nextTimelineList);

    Assert.assertFalse(nextTimelineGrpcSet.isEmpty());
    Assert.assertEquals(nextTimelineGrpcSet, setTimelineIdsDb);
  }

  @Test
  @Description("GetPopulatedEpisodesByIds - nextTimeline.channel field")
  @TmsLink("28500933")
  public void C28500933_TEST() {
    int limit = 2;
    String testFields = "nextTimeline.channel";

    Iterator iteratorVodCategories = episodeUtils.getExistsAndSizeNotZeroIterator(testFields, 5);
    List listEpisodesIterator = List.of(iteratorVodCategories.next(), iteratorVodCategories.next());
    List<Episodes> listEpisodes =
        episodeUtils.mapGrpcObjectsToPojo(listEpisodesIterator, Episodes.class);
    List<String> randomEpisodeIds =
        listEpisodes.stream().map(Episodes::getId).collect(Collectors.toList());
    Assert.assertEquals(listEpisodes.size(), limit);

    List<String> listChannelIds =
        listEpisodes.stream()
            .flatMap(e -> e.getChannels().stream())
            .map(ObjectId::toString)
            .collect(Collectors.toList());
    LogUtils.logInfoMessage("ListEpisodes: " + randomEpisodeIds);
    List<tv.pluto.ip.dal.episode.Episodes.EpisodePopulated> populatedResponse =
        new EpisodesClient(grpcChannelManager.getChannel(), false)
            .getPopulatedEpisodesByIds(randomEpisodeIds, testFields)
            .getEpisodesList();

    List<String> listTimelineChannels =
        populatedResponse.stream()
            .map(e -> e.getNextTimeline().getChannel().getId())
            .collect(Collectors.toList());
    Collections.sort(listChannelIds);
    Collections.sort(listTimelineChannels);

    Assert.assertFalse(listTimelineChannels.isEmpty());
    Assert.assertEquals(listTimelineChannels, listChannelIds);
  }

  @Test
  @Description("GetPopulatedEpisodesByIds - vodCategoryEntries field")
  @TmsLink("28500934")
  public void C28500934_TEST() {
    String testFields = "vodCategoryEntries";
    Iterator iteratorVodCategories =
        episodeUtils.getRandomDocumentsWithTheFieldSize(testFields, 2, 2);
    Episodes firstEpisode = ((Episodes) iteratorVodCategories.next());
    Episodes secondEpisode = ((Episodes) iteratorVodCategories.next());
    List<String> randomEpisodeIds = List.of(firstEpisode.getId(), secondEpisode.getId());

    Set<String> setOfVodCategoryIds =
        Stream.of(firstEpisode, secondEpisode)
            .flatMap(
                e ->
                    e.getVodCategoryEntries().stream()
                        .filter(Objects::nonNull) // Filter out null values
                        .map(ObjectId::toString))
            .collect(Collectors.toSet());

    LogUtils.logInfoMessage("Set of ids from db: " + setOfVodCategoryIds);

    LogUtils.logInfoMessage("ListEpisodes: " + randomEpisodeIds);
    List<tv.pluto.ip.dal.episode.Episodes.EpisodePopulated> populatedResponse =
        episodesClient.getPopulatedEpisodesByIds(randomEpisodeIds, testFields).getEpisodesList();

    Set<String> setOfVodCategoryIdsGrpc =
        populatedResponse.stream()
            .flatMap(e -> e.getVodCategoryEntriesList().stream())
            .map(tv.pluto.ip.dal.episode.Episodes.VodCategoryEntry::getId)
            .collect(Collectors.toSet());

    List<VodCategoryEntries> vodCategoryEntries =
        vodCategoryEntriesUtils.getDocumentsByIds(new ArrayList<>(setOfVodCategoryIds));
    Set<String> idsWithCodCatIds =
        vodCategoryEntries.stream().map(VodCategoryEntries::getId).collect(Collectors.toSet());
    Assert.assertEquals(idsWithCodCatIds, setOfVodCategoryIdsGrpc);
  }

  @Test
  @Description("GetPopulatedEpisodesByIds - customReferences field")
  @TmsLink("28500935")
  public void C28500935_TEST() {
    String testFields = "customReferences";
    Iterator iteratorVodCategories =
        episodeUtils.getRandomDocumentsWithTheFieldSize(testFields, 2, 2);
    Episodes firstEpisode = ((Episodes) iteratorVodCategories.next());
    Episodes secondEpisode = ((Episodes) iteratorVodCategories.next());
    List<String> randomEpisodeIds = List.of(firstEpisode.getId(), secondEpisode.getId());
    Set<String> setOfVodCategoryIds =
        Stream.of(firstEpisode, secondEpisode)
            .flatMap(e -> e.getCustomReferences().stream())
            .map(CustomReferences::getId)
            .collect(Collectors.toSet());

    LogUtils.logInfoMessage("ListEpisodes: " + randomEpisodeIds);
    List<tv.pluto.ip.dal.episode.Episodes.EpisodePopulated> populatedResponse =
        episodesClient.getPopulatedEpisodesByIds(randomEpisodeIds, testFields).getEpisodesList();

    Set<String> setIdsGrpc =
        populatedResponse.stream()
            .flatMap(e -> e.getCustomReferencesList().stream())
            .map(tv.pluto.ip.dal.episode.Episodes.CustomReference::getId)
            .collect(Collectors.toSet());

    Assert.assertFalse(setIdsGrpc.isEmpty());
    Assert.assertEquals(setOfVodCategoryIds, setIdsGrpc);
  }

  @Test(groups = {"ttfb"})
  @Description("Check TTFB 90 percentile for the EpisodeManager GetById operation.")
  @TmsLink("28500936")
  public void C28500936_TEST() {
    Episodes mongoEpisode = episodeUtils.getRandomDocument();
    String randomEpisodeId = mongoEpisode.getId();

    GrpcCall grpcCall =
        new GrpcCall(
            "EpisodeManager.GetById", () -> episodesClient.getEpisodeById(randomEpisodeId));

    TTFBGrpcValidator ttfbGrpcValidator = TTFBGrpcValidator.builder().build();

    ttfbGrpcValidator.validateTTFBForGrpcCalls(List.of(grpcCall));
  }

  @Test(groups = {"ttfb"})
  @Description(
      "Check TTFB 90 percentile for the EpisodeManager GetAgeRangePopulatedEpisodeById operation.")
  @TmsLink("28500937")
  public void C28500937_TEST() {
    String randomEpisodeId = episodeUtils.getIdsOfEpisodesWithAgeRange(1).get(0);

    GetAgeRangePopulatedEpisodeByIdRequest getAgeRangePopulatedEpisodeByIdRequest =
        GetAgeRangePopulatedEpisodeByIdRequest.newBuilder().setId(randomEpisodeId).build();

    GrpcCall grpcCall =
        new GrpcCall(
            "EpisodeManager.GetAgeRangePopulatedEpisodeById",
            () ->
                episodesClient.getAgeRangePopulatedEpisodeById(
                    getAgeRangePopulatedEpisodeByIdRequest));

    TTFBGrpcValidator ttfbGrpcValidator = TTFBGrpcValidator.builder().build();

    ttfbGrpcValidator.validateTTFBForGrpcCalls(List.of(grpcCall));
  }

  @Test(groups = {"ttfb"})
  @Description("Check TTFB 90 percentile for the EpisodeManager GetPopulatedEpisodeById operation.")
  @TmsLink("28500938")
  public void C28500938_TEST() {
    Episodes episodesMango = episodeUtils.getRandomDocumentWithTheFieldSize("channels", 2);

    GrpcCall grpcCall =
        new GrpcCall(
            "EpisodeManager.GetPopulatedEpisodeById",
            () -> episodesClient.getPopulatedEpisodeById(episodesMango.getId(), ""));

    TTFBGrpcValidator ttfbGrpcValidator = TTFBGrpcValidator.builder().build();

    ttfbGrpcValidator.validateTTFBForGrpcCalls(List.of(grpcCall));
  }

  @Test
  @Description("UnsetNextTimeline-EpisodeId & ChannelId & MaxStart")
  @TmsLink("28500939")
  public void C28500939_TEST() {

    Episodes episodesMango = episodeUtils.getRandomDocumentWithTheField("nextTimeline._id", true);
    LogUtils.logInfoMessage("Test episode with parent: " + episodesMango.getId());

    String episodeId = episodesMango.getId();
    String channelId = String.valueOf(episodesMango.getChannels().get(0));
    NextTimeline firstNextTimeline = episodesMango.getNextTimeline();
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
    String maxStart = dateFormat.format(firstNextTimeline.getStart());

    Assert.assertNotNull("Channel ID must be valid", channelId);

    tv.pluto.ip.dal.episode.Episodes.UnsetNextTimelineResponse unsetResponse =
        episodesClient.unsetNextTimeline(Collections.singletonList(episodeId), channelId, maxStart);

    Assert.assertNotNull(unsetResponse);

    Episodes checkEpisode = episodeUtils.getDocumentById(episodeId);
    LogUtils.logInfoMessage("Fetched episode after UnsetNextTimeline: " + checkEpisode.getId());

    NextTimeline nextTimeline = checkEpisode.getNextTimeline();
    Assert.assertTrue(nextTimeline.getChannel() == null || nextTimeline.getChannel().isEmpty());
    Assert.assertTrue(nextTimeline.get_id() == null || nextTimeline.get_id().isEmpty());
    Assert.assertTrue(nextTimeline.getStart() == null);
    Assert.assertTrue(nextTimeline == null || nextTimeline.isEmpty());
  }

  @Test
  @Description("UnsetNextTimeline-No NextTimeLine,UNKNOWN error")
  @TmsLink("28500940")
  public void C28500940_TEST() {
    boolean isTrue = false;
    Episodes episodesMango = episodeUtils.getRandomDocumentWithTheField("nextTimeline._id", false);

    String episodeId = episodesMango.getId();

    String channelId = String.valueOf(episodesMango.getChannels().get(0));
    String maxStart = "2026-01-01";

    Assert.assertNotNull(channelId);
    try {
      tv.pluto.ip.dal.episode.Episodes.UnsetNextTimelineResponse unsetResponse =
          episodesClient.unsetNextTimeline(
              Collections.singletonList(episodeId), channelId, maxStart);
    } catch (Exception e) {
      isTrue = true;
      Assert.assertTrue(
          e.getMessage().contains("Could not find any episodes with requested params"));
    }
    Assert.assertTrue(isTrue);
  }

  @Test
  @TmsLink("28500941")
  @Description(
      "UnsetNextTimeline-Invalid episode ID,invalid start and channelID ,FAILED_PRECONDITION")
  public void C28500941_TEST() {
    boolean isTrue = false;
    String invalidData = "invalidEpisodeId";

    try {
      tv.pluto.ip.dal.episode.Episodes.UnsetNextTimelineResponse unsetResponse =
          episodesClient.unsetNextTimeline(
              Collections.singletonList(invalidData), invalidData, invalidData);
    } catch (Exception e) {
      isTrue = true;
      Assert.assertTrue(
          e.getMessage().contains("Request param idsToRemove must contain valid ids"));
    }
    Assert.assertTrue(isTrue);
  }

  @Test
  @Description("UnsetNextTimeline-MaxStart Error")
  public void C28500942_TEST() {
    boolean isTrue = false;
    Episodes episodesMango = episodeUtils.getRandomDocumentWithTheField("nextTimeline._id", true);

    String episodeId = episodesMango.getId();

    String channelId = String.valueOf(episodesMango.getChannels().get(0));
    String maxStart = "";
    Assert.assertNotNull("Channel ID must be valid", channelId);
    try {
      tv.pluto.ip.dal.episode.Episodes.UnsetNextTimelineResponse unsetResponse =
          episodesClient.unsetNextTimeline(
              Collections.singletonList(episodeId), channelId, maxStart);
    } catch (Exception e) {
      isTrue = true;

      Assert.assertTrue(
          e.getMessage()
              .contains(
                  "Cast to date failed for value \"Invalid Date\" (type Date) at path \"nextTimeline.start\" for model \"Episode\""));
    }
    Assert.assertTrue(isTrue);
  }

  @Test
  @Description("Valid request using projection - GetEpisodeById")
  @TmsLink("28500943")
  public void C28500943_TEST() {

    String[] projection = {"_id", "name"};

    Episodes mongoEpisode = episodeUtils.getRandomDocument(projection);
    String randomEpisodeId = mongoEpisode.getId();

    List<String> projections = new ArrayList<>();
    projections.add("id");
    projections.add("name");

    Episodes mappedEpisode =
        episodeUtils.mapGrpcObjectToPojo(
            episodesClient.getEpisodeById(randomEpisodeId, projections).getDocument(),
            Episodes.class);

    Assert.assertEquals(mongoEpisode, mappedEpisode);
  }

  @Test
  @Description("Valid request with multiple episodes using projection - GetEpisodesByIds")
  @TmsLink("28500944")
  public void C28500944_TEST() {
    List<String> randomEpisodesIds = episodeUtils.getRandomEpisodesIds();

    String[] projection = {"_id", "name"};

    List<Episodes> mongoEpisodesList =
        episodeUtils.getDocumentsByIds(randomEpisodesIds, projection);

    List<String> projections = new ArrayList<>();
    projections.add("id");
    projections.add("name");

    List<?> mappedEpisodesList =
        episodeUtils.mapGrpcObjectsToPojo(
            episodesClient.getByIds(randomEpisodesIds, projections).getDocumentsList(),
            Episodes.class);

    Assert.assertEquals(mongoEpisodesList, mappedEpisodesList);
  }

  @Test
  @Description("GetAllPaged - filter valid values for episodes")
  @TmsLink("28500945")
  public void C28500945_TEST() {
    EpisodesClient episodesClient = new EpisodesClient(grpcChannelManager.getChannel(), false);
    Episodes mongoEpisode = episodeUtils.getRandomDocument();

    List<tv.pluto.ip.dal.episode.Episodes.Filter> listFiltersBodyInEpisodes =
        episodesClient.filtersBodyInEpisodes(mongoEpisode);

    GetAllPagedResponse getAllPagedClientResponse =
        episodesClient.getAllPagedResponse(listFiltersBodyInEpisodes);

    List<String> episodeIds =
        getAllPagedClientResponse.getDocumentsList().stream()
            .map(Episode::getId)
            .collect(Collectors.toList());

    List<Episodes> mongoDBEpisodes = episodeUtils.getDocumentsByIds(episodeIds);

    List<Episodes> clientEpisodes =
        episodeUtils.mapGrpcObjectsToPojo(
            getAllPagedClientResponse.getDocumentsList(), Episodes.class);

    Assert.assertEquals(mongoDBEpisodes.size(), clientEpisodes.size(), "Episode size mismatch");

    mongoDBEpisodes.sort(Comparator.comparing(Episodes::getId));
    clientEpisodes.sort(Comparator.comparing(Episodes::getId));

    Assert.assertEquals(
        mongoDBEpisodes, clientEpisodes, "Episodes from MongoDB and gRPC service do not match");
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "NOT_FOUND: Episode documents not found")
  @Description("GetAllPaged - filter by invalid episode name")
  @TmsLink("28500946")
  public void C28500946_TEST() {
    EpisodesClient episodesClient = new EpisodesClient(grpcChannelManager.getChannel(), false);
    Episodes episode = episodeUtils.getRandomDocument();
    episode.setName("invalidTestName");

    List<tv.pluto.ip.dal.episode.Episodes.Filter> listFiltersBodyInEpisodes =
        episodesClient.filtersBodyInEpisodes(episode);

    episodesClient.getAllPagedResponse(listFiltersBodyInEpisodes);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "NOT_FOUND: Episode documents not found")
  @Description("GetAllPaged - filter by invalid active region")
  @TmsLink("28500950")
  public void C28500950_TEST() {
    EpisodesClient episodesClient = new EpisodesClient(grpcChannelManager.getChannel(), false);
    Episodes episode = episodeUtils.getRandomDocument();
    episode.setActiveRegion("invalidRegion");

    List<tv.pluto.ip.dal.episode.Episodes.Filter> listFiltersBodyInEpisodes =
        episodesClient.filtersBodyInEpisodes(episode);

    episodesClient.getAllPagedResponse(listFiltersBodyInEpisodes);
  }

  @Test
  @Description("GetAllPaged - filter by activeRegion")
  @TmsLink("28500947")
  public void C28500947_Test() {
    EpisodesClient episodesClient = new EpisodesClient(grpcChannelManager.getChannel(), false);

    tv.pluto.ip.dal.episode.Episodes.Filter filter =
        tv.pluto.ip.dal.episode.Episodes.Filter.newBuilder()
            .setField("activeRegion")
            .setValue("us")
            .setType("string")
            .setOperator("")
            .build();

    List<tv.pluto.ip.dal.episode.Episodes.Filter> listFiltersBodyInEpisodes = List.of(filter);

    GetAllPagedResponse getAllPagedClientResponse =
        episodesClient.getAllPagedResponse(listFiltersBodyInEpisodes);

    List<String> episodeIds =
        getAllPagedClientResponse.getDocumentsList().stream()
            .map(Episode::getId)
            .collect(Collectors.toList());

    List<Episodes> mongoDBEpisodes = episodeUtils.getDocumentsByIds(episodeIds);

    List<Episodes> clientEpisodes =
        episodeUtils.mapGrpcObjectsToPojo(
            getAllPagedClientResponse.getDocumentsList(), Episodes.class);

    Assert.assertEquals(mongoDBEpisodes.size(), clientEpisodes.size(), "Episode size mismatch");

    mongoDBEpisodes.sort(Comparator.comparing(Episodes::getId));
    clientEpisodes.sort(Comparator.comparing(Episodes::getId));

    Assert.assertEquals(
        mongoDBEpisodes, clientEpisodes, "Episodes from MongoDB and gRPC service do not match");
  }

  @Test
  @Description("GetAllPaged - filter with OR operator")
  @TmsLink("28500948")
  public void C28500948_TEST() {
    EpisodesClient episodesClient = new EpisodesClient(grpcChannelManager.getChannel(), false);

    tv.pluto.ip.dal.episode.Episodes.Filter filter1 =
        tv.pluto.ip.dal.episode.Episodes.Filter.newBuilder()
            .setField("activeRegion")
            .setValue("us")
            .setType("string")
            .setOperator("or")
            .build();

    tv.pluto.ip.dal.episode.Episodes.Filter filter2 =
        tv.pluto.ip.dal.episode.Episodes.Filter.newBuilder()
            .setField("published")
            .setValue("true")
            .setType("boolean")
            .setOperator("or")
            .build();

    List<tv.pluto.ip.dal.episode.Episodes.Filter> listFiltersBodyInEpisodes =
        List.of(filter1, filter2);

    GetAllPagedResponse getAllPagedClientResponse =
        episodesClient.getAllPagedResponse(listFiltersBodyInEpisodes);

    for (Episode Episode : getAllPagedClientResponse.getDocumentsList()) {
      Assert.assertTrue(
          "us".equals(Episode.getActiveRegion()) || Boolean.TRUE.equals(Episode.getPublished()),
          "The episode does not match the filter criteria.");
    }
  }

  @Test
  @Description("GetAllPaged - filter with AND operator")
  @TmsLink("28500949")
  public void C28500949_TEST() {
    EpisodesClient episodesClient = new EpisodesClient(grpcChannelManager.getChannel(), false);

    tv.pluto.ip.dal.episode.Episodes.Filter filter1 =
        tv.pluto.ip.dal.episode.Episodes.Filter.newBuilder()
            .setField("activeRegion")
            .setValue("us")
            .setType("string")
            .setOperator("and")
            .build();

    tv.pluto.ip.dal.episode.Episodes.Filter filter2 =
        tv.pluto.ip.dal.episode.Episodes.Filter.newBuilder()
            .setField("published")
            .setValue("true")
            .setType("boolean")
            .setOperator("and")
            .build();

    List<tv.pluto.ip.dal.episode.Episodes.Filter> listFiltersBodyInEpisodes =
        List.of(filter1, filter2);

    GetAllPagedResponse getAllPagedClientResponse =
        episodesClient.getAllPagedResponse(listFiltersBodyInEpisodes);

    for (Episode Episode : getAllPagedClientResponse.getDocumentsList()) {
      Assert.assertTrue(
          "us".equals(Episode.getActiveRegion()) && Boolean.TRUE.equals(Episode.getPublished()),
          "The episode does not match the filter criteria.");
    }
  }

  @Test
  @Description(
      "GetPopulatedEpisodesByClipId - Get episodes with limit 25, offset 0 and an empty nextTimeline _id field or non-existent nextTimeline.")
  @TmsLink("28500951")
  public void C28500951_TEST() {
    EpisodesClient episodesClient = new EpisodesClient(grpcChannelManager.getChannel(), false);
    String clipId =
        episodeUtils.getClipFromSourcesWithNonExistentOrCondition("nextTimeline._id", "");

    LogUtils.logInfoMessage("C33739913_TEST clipId = " + clipId);
    Bson sort = orderBy(ascending("_id"));

    List<GetAllPagedSort> grpcSort =
        Collections.singletonList(GetAllPagedSort.newBuilder().setField("_id").setValue(1).build());

    List<EpisodePopulated> mongoEpisodesPopulated =
        populatedEpisodesUtils.getPopulatedEpisodesByClipId(clipId, 25, 0, sort);

    List<EpisodePopulated> grpcEpisodesPopulated =
        populatedEpisodesUtils.mapGrpcObjectsToPojo(
            episodesClient
                .getPopulatedEpisodesByClipId(clipId, 25, 0, grpcSort, null)
                .getEpisodes()
                .getDataList(),
            EpisodePopulated.class);

    Assert.assertNotNull(mongoEpisodesPopulated);
    Assert.assertNotNull(grpcEpisodesPopulated);
    Assert.assertEquals(mongoEpisodesPopulated, grpcEpisodesPopulated);
  }

  @Test
  @Description(
      "GetEpisodeByNameSeriesAndActiveRegion - Request with valid name, series and region.")
  @TmsLink("28500952")
  public void C28500952_TEST() {
    Episodes randomEpisode = episodeUtils.getRandomDocument();

    Episodes mongoEpisode =
        episodeUtils.getEpisodeByNameSeriesAndActiveRegion(
            randomEpisode.getName(), randomEpisode.getSeries(), randomEpisode.getActiveRegion());

    tv.pluto.ip.dal.episode.Episodes.GetEpisodeByNameSeriesAndActiveRegionRequest request =
        tv.pluto.ip.dal.episode.Episodes.GetEpisodeByNameSeriesAndActiveRegionRequest.newBuilder()
            .setName(randomEpisode.getName())
            .setSeries(randomEpisode.getSeries())
            .setActiveRegion(randomEpisode.getActiveRegion())
            .build();

    tv.pluto.ip.dal.episode.Episodes.GetEpisodeByNameSeriesAndActiveRegionResponse response =
        episodesClient.getEpisodeByNameSeriesAndActiveRegion(request);

    Episodes mappedEpisode =
        episodeUtils.mapGrpcObjectToPojo(response.getDocument(), Episodes.class);

    Assert.assertNotNull(mongoEpisode);
    Assert.assertNotNull(mappedEpisode);
    Assert.assertEquals(mongoEpisode, mappedEpisode);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "NOT_FOUND: Episode for the requested parameters not found")
  @Description("GetEpisodeByNameSeriesAndActiveRegion - Request with a non-existent name.")
  @TmsLink("28500953")
  public void C28500953_TEST() {
    Episodes randomEpisode = episodeUtils.getRandomDocument();

    String nonExistentName = "Peñarol";

    tv.pluto.ip.dal.episode.Episodes.GetEpisodeByNameSeriesAndActiveRegionRequest request =
        tv.pluto.ip.dal.episode.Episodes.GetEpisodeByNameSeriesAndActiveRegionRequest.newBuilder()
            .setName(nonExistentName)
            .setSeries(randomEpisode.getSeries())
            .setActiveRegion(randomEpisode.getActiveRegion())
            .build();

    episodesClient.getEpisodeByNameSeriesAndActiveRegion(request);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "NOT_FOUND: Episode for the requested parameters not found")
  @Description("GetEpisodeByNameSeriesAndActiveRegion - Request with a non existent series.")
  @TmsLink("28500954")
  public void C28500954_TEST() {
    Episodes randomEpisode = episodeUtils.getRandomDocument();

    ObjectId nonExistentObjectId = new ObjectId("1a2b3c4d5e6f7a8b9c10d11e");

    tv.pluto.ip.dal.episode.Episodes.GetEpisodeByNameSeriesAndActiveRegionRequest request =
        tv.pluto.ip.dal.episode.Episodes.GetEpisodeByNameSeriesAndActiveRegionRequest.newBuilder()
            .setName(randomEpisode.getName())
            .setSeries(nonExistentObjectId.toString())
            .setActiveRegion(randomEpisode.getActiveRegion())
            .build();

    episodesClient.getEpisodeByNameSeriesAndActiveRegion(request);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "NOT_FOUND: Episode for the requested parameters not found")
  @Description("GetEpisodeByNameSeriesAndActiveRegion - Request with a non existent active region.")
  @TmsLink("28500955")
  public void C28500955_TEST() {
    Episodes randomEpisode = episodeUtils.getRandomDocument();

    String nonExistentActiveRegion = "nonExistentActiveRegion";

    tv.pluto.ip.dal.episode.Episodes.GetEpisodeByNameSeriesAndActiveRegionRequest request =
        tv.pluto.ip.dal.episode.Episodes.GetEpisodeByNameSeriesAndActiveRegionRequest.newBuilder()
            .setName(randomEpisode.getName())
            .setSeries(randomEpisode.getSeries())
            .setActiveRegion(nonExistentActiveRegion)
            .build();

    episodesClient.getEpisodeByNameSeriesAndActiveRegion(request);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "NOT_FOUND: Episode for the requested parameters not found")
  @Description("GetEpisodeByNameSeriesAndActiveRegion - Request with without name.")
  @TmsLink("28500956")
  public void C28500956_TEST() {
    Episodes randomEpisode = episodeUtils.getRandomDocument();

    tv.pluto.ip.dal.episode.Episodes.GetEpisodeByNameSeriesAndActiveRegionRequest request =
        tv.pluto.ip.dal.episode.Episodes.GetEpisodeByNameSeriesAndActiveRegionRequest.newBuilder()
            .setSeries(randomEpisode.getSeries())
            .setActiveRegion(randomEpisode.getActiveRegion())
            .build();

    episodesClient.getEpisodeByNameSeriesAndActiveRegion(request);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = INTERNAL_MESSAGE)
  @Description("GetEpisodeByNameSeriesAndActiveRegion - Request with without series.")
  @TmsLink("28500957")
  public void C28500957_TEST() {
    Episodes randomEpisode = episodeUtils.getRandomDocument();

    tv.pluto.ip.dal.episode.Episodes.GetEpisodeByNameSeriesAndActiveRegionRequest request =
        tv.pluto.ip.dal.episode.Episodes.GetEpisodeByNameSeriesAndActiveRegionRequest.newBuilder()
            .setName(randomEpisode.getName())
            .setActiveRegion(randomEpisode.getActiveRegion())
            .build();

    episodesClient.getEpisodeByNameSeriesAndActiveRegion(request);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "NOT_FOUND: Episode for the requested parameters not found")
  @Issue("IP-5733")
  @Description("GetEpisodeByNameSeriesAndActiveRegion - Request with without active region.")
  @TmsLink("28500958")
  public void C28500958_TEST() {
    Episodes randomEpisode = episodeUtils.getRandomDocument();

    tv.pluto.ip.dal.episode.Episodes.GetEpisodeByNameSeriesAndActiveRegionRequest request =
        tv.pluto.ip.dal.episode.Episodes.GetEpisodeByNameSeriesAndActiveRegionRequest.newBuilder()
            .setName(randomEpisode.getName())
            .setSeries(randomEpisode.getSeries())
            .build();

    episodesClient.getEpisodeByNameSeriesAndActiveRegion(request);
  }

  @Test
  @Description("GetEpisodeBySeriesSeasonAndNumber - Request with valid series, season and number.")
  @TmsLink("28500959")
  public void C28500959_TEST() {
    Episodes randomEpisode = episodeUtils.getRandomEpisodeWithSeriesSeasonAndNumber();

    Episodes mongoEpisode =
        episodeUtils.getEpisodeBySeriesSeasonAndNumber(
            randomEpisode.getSeries(), randomEpisode.getSeason(), randomEpisode.getNumber());

    tv.pluto.ip.dal.episode.Episodes.GetEpisodeBySeriesSeasonAndNumberRequest request =
        tv.pluto.ip.dal.episode.Episodes.GetEpisodeBySeriesSeasonAndNumberRequest.newBuilder()
            .setSeries(randomEpisode.getSeries())
            .setSeason(randomEpisode.getSeason())
            .setNumber(randomEpisode.getNumber())
            .build();

    tv.pluto.ip.dal.episode.Episodes.GetEpisodeBySeriesSeasonAndNumberResponse response =
        episodesClient.getEpisodeBySeriesSeasonAndNumber(request);

    Episodes mappedEpisode =
        episodeUtils.mapGrpcObjectToPojo(response.getDocument(), Episodes.class);

    Assert.assertNotNull(mongoEpisode);
    Assert.assertNotNull(mappedEpisode);
    Assert.assertEquals(mongoEpisode, mappedEpisode);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "NOT_FOUND: Episode document for requested ID not found")
  @Description("GetEpisodeBySeriesSeasonAndNumber - Request with a non-existent series.")
  @TmsLink("28500960")
  public void C28500960_TEST() {
    Episodes randomEpisode = episodeUtils.getRandomEpisodeWithSeriesSeasonAndNumber();

    String nonExistentObjectId = "1a2b3c4d5e6f7a8b9c10d11e";

    tv.pluto.ip.dal.episode.Episodes.GetEpisodeBySeriesSeasonAndNumberRequest request =
        tv.pluto.ip.dal.episode.Episodes.GetEpisodeBySeriesSeasonAndNumberRequest.newBuilder()
            .setSeries(nonExistentObjectId)
            .setSeason(randomEpisode.getSeason())
            .setNumber(randomEpisode.getNumber())
            .build();

    episodesClient.getEpisodeBySeriesSeasonAndNumber(request);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "NOT_FOUND: Episode document for requested ID not found")
  @Description("GetEpisodeBySeriesSeasonAndNumber - Request with a non-existent season.")
  @TmsLink("28500961")
  public void C28500961_TEST() {
    Episodes randomEpisode = episodeUtils.getRandomEpisodeWithSeriesSeasonAndNumber();

    int nonExistentSeason = 999;

    tv.pluto.ip.dal.episode.Episodes.GetEpisodeBySeriesSeasonAndNumberRequest request =
        tv.pluto.ip.dal.episode.Episodes.GetEpisodeBySeriesSeasonAndNumberRequest.newBuilder()
            .setSeries(randomEpisode.getSeries())
            .setSeason(nonExistentSeason)
            .setNumber(randomEpisode.getNumber())
            .build();

    episodesClient.getEpisodeBySeriesSeasonAndNumber(request);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "NOT_FOUND: Episode document for requested ID not found")
  @Description("GetEpisodeBySeriesSeasonAndNumber - Request with a non-existent number.")
  @TmsLink("28500962")
  public void C28500962_TEST() {
    Episodes randomEpisode = episodeUtils.getRandomEpisodeWithSeriesSeasonAndNumber();

    int nonExistentNumber = 999;

    tv.pluto.ip.dal.episode.Episodes.GetEpisodeBySeriesSeasonAndNumberRequest request =
        tv.pluto.ip.dal.episode.Episodes.GetEpisodeBySeriesSeasonAndNumberRequest.newBuilder()
            .setSeries(randomEpisode.getSeries())
            .setSeason(randomEpisode.getSeason())
            .setNumber(nonExistentNumber)
            .build();

    episodesClient.getEpisodeBySeriesSeasonAndNumber(request);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = INTERNAL_MESSAGE)
  @Description("GetEpisodeBySeriesSeasonAndNumber - Request without series.")
  @TmsLink("28500963")
  public void C28500963_TEST() {
    Episodes randomEpisode = episodeUtils.getRandomEpisodeWithSeriesSeasonAndNumber();

    tv.pluto.ip.dal.episode.Episodes.GetEpisodeBySeriesSeasonAndNumberRequest request =
        tv.pluto.ip.dal.episode.Episodes.GetEpisodeBySeriesSeasonAndNumberRequest.newBuilder()
            .setSeason(randomEpisode.getSeason())
            .setNumber(randomEpisode.getNumber())
            .build();

    episodesClient.getEpisodeBySeriesSeasonAndNumber(request);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "NOT_FOUND: Episode document for requested ID not found")
  @Description("GetEpisodeBySeriesSeasonAndNumber - Request without season.")
  @TmsLink("28500964")
  public void C28500964_TEST() {
    Episodes randomEpisode = episodeUtils.getRandomEpisodeWithSeriesSeasonAndNumber();

    tv.pluto.ip.dal.episode.Episodes.GetEpisodeBySeriesSeasonAndNumberRequest request =
        tv.pluto.ip.dal.episode.Episodes.GetEpisodeBySeriesSeasonAndNumberRequest.newBuilder()
            .setSeries(randomEpisode.getSeries())
            .setNumber(randomEpisode.getNumber())
            .build();

    episodesClient.getEpisodeBySeriesSeasonAndNumber(request);
  }

  @Test
  @Description("GetEpisodeBySeriesSeasonAndNumber - Request without number.")
  @TmsLink("28500965")
  public void C28500965_TEST() {
    Episodes randomEpisode = episodeUtils.getRandomEpisodeWithSeriesSeasonAndNumber();

    Episodes mongoEpisode =
        episodeUtils.getEpisodeBySeriesSeasonAndNumber(
            randomEpisode.getSeries(), randomEpisode.getSeason(), 0);

    tv.pluto.ip.dal.episode.Episodes.GetEpisodeBySeriesSeasonAndNumberRequest request =
        tv.pluto.ip.dal.episode.Episodes.GetEpisodeBySeriesSeasonAndNumberRequest.newBuilder()
            .setSeries(randomEpisode.getSeries())
            .setSeason(randomEpisode.getSeason())
            .build();

    tv.pluto.ip.dal.episode.Episodes.GetEpisodeBySeriesSeasonAndNumberResponse response =
        episodesClient.getEpisodeBySeriesSeasonAndNumber(request);

    Episodes mappedEpisode =
        episodeUtils.mapGrpcObjectToPojo(response.getDocument(), Episodes.class);

    Assert.assertNotNull(mongoEpisode);
    Assert.assertNotNull(mappedEpisode);
    Assert.assertEquals(mongoEpisode, mappedEpisode);
  }

  @Test
  @Description("GetEpisodesByClipId - Ascending Sorting Order validation")
  @TmsLink("28500966")
  public void C28500966_TEST() {
    String clipId = clipsUtils.getRandomItem(clipsUtils.getIdsOfClipsInMoreThanOneEpisode(10));

    int sortOrder = 1;

    GetAllPagedSort idSort =
        GetAllPagedSort.newBuilder().setField("_id").setValue(sortOrder).build();

    List<GetAllPagedSort> grpcSort = List.of(idSort);

    GetEpisodesByClipIdRequest getEpisodesByClipIdRequest =
        GetEpisodesByClipIdRequest.newBuilder()
            .setId(clipId)
            .setLimit(10)
            .setOffset(0)
            .addAllSort(grpcSort)
            .build();

    List<Episodes> grpcEpisodes =
        episodeUtils.mapGrpcObjectsToPojo(
            episodesClient.getEpisodesByClipId(getEpisodesByClipIdRequest).getEpisodesList(),
            Episodes.class);

    String[] sortFields = new String[] {"_id", "name", "displayName"};
    Bson sort = orderBy(ascending(sortFields));

    List<Episodes> mongoEpisodes = episodeUtils.getEpisodesByClipId(clipId, 10, 0, sort, null);

    Assert.assertEquals(mongoEpisodes, grpcEpisodes);
  }

  @Test
  @Description("GetEpisodesByClipId - Descending Sorting Order validation")
  @TmsLink("28500967")
  public void C28500967_TEST() {
    String clipId = clipsUtils.getRandomItem(clipsUtils.getIdsOfClipsInMoreThanOneEpisode(10));

    int sortOrder = -1;

    GetAllPagedSort idSort =
        GetAllPagedSort.newBuilder().setField("_id").setValue(sortOrder).build();

    List<GetAllPagedSort> grpcSort = List.of(idSort);

    GetEpisodesByClipIdRequest getEpisodesByClipIdRequest =
        GetEpisodesByClipIdRequest.newBuilder()
            .setId(clipId)
            .setLimit(10)
            .setOffset(0)
            .addAllSort(grpcSort)
            .build();

    List<Episodes> grpcEpisodes =
        episodeUtils.mapGrpcObjectsToPojo(
            episodesClient.getEpisodesByClipId(getEpisodesByClipIdRequest).getEpisodesList(),
            Episodes.class);

    String[] sortFields = new String[] {"_id", "name", "displayName"};
    Bson sort = orderBy(descending(sortFields));

    List<Episodes> mongoEpisodes = episodeUtils.getEpisodesByClipId(clipId, 10, 0, sort, null);

    Assert.assertEquals(mongoEpisodes, grpcEpisodes);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp =
          "INTERNAL: One or more provided sort values no not match 1 or -1.")
  @Description("GetEpisodesByClipId - Invalid sort order")
  @TmsLink("28500968")
  public void C28500968_TEST() {
    String clipId = clipsUtils.getRandomItem(clipsUtils.getIdsOfClipsInMoreThanOneEpisode(10));

    int invalidSortOrder = 10;

    GetAllPagedSort idSort =
        GetAllPagedSort.newBuilder().setField("_id").setValue(invalidSortOrder).build();

    List<GetAllPagedSort> grpcSort = List.of(idSort);

    GetEpisodesByClipIdRequest getEpisodesByClipIdRequest =
        GetEpisodesByClipIdRequest.newBuilder()
            .setId(clipId)
            .setLimit(10)
            .setOffset(0)
            .addAllSort(grpcSort)
            .build();

    episodesClient.getEpisodesByClipId(getEpisodesByClipIdRequest);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp =
          "INTERNAL: One or more provided sort attributes are not contained within the table/collection. .*")
  @Description("GetEpisodesByClipId - Invalid sort field")
  @TmsLink("28500969")
  public void C28500969_TEST() {
    String clipId = clipsUtils.getRandomItem(clipsUtils.getIdsOfClipsInMoreThanOneEpisode(10));

    int sortOrder = 1;
    String invalidSortField = "Peñarol & Shakhtar Donetsk";

    GetAllPagedSort idSort =
        GetAllPagedSort.newBuilder().setField(invalidSortField).setValue(sortOrder).build();

    List<GetAllPagedSort> grpcSort = List.of(idSort);

    GetEpisodesByClipIdRequest getEpisodesByClipIdRequest =
        GetEpisodesByClipIdRequest.newBuilder()
            .setId(clipId)
            .setLimit(10)
            .setOffset(0)
            .addAllSort(grpcSort)
            .build();

    episodesClient.getEpisodesByClipId(getEpisodesByClipIdRequest);
  }

  @Test
  @Description("GetEpisodesByClipId - Projection validation")
  @TmsLink("28500970")
  public void C28500970_TEST() {
    String clipId = clipsUtils.getRandomItem(clipsUtils.getIdsOfClipsInMoreThanOneEpisode(10));

    List<String> projectionFields = List.of("_id", "name", "displayName");

    Bson projection = Projections.fields(Projections.include(projectionFields));

    GetEpisodesByClipIdRequest getEpisodesByClipIdRequest =
        GetEpisodesByClipIdRequest.newBuilder()
            .setId(clipId)
            .setLimit(10)
            .setOffset(0)
            .addAllProjection(projectionFields)
            .build();

    List<Episodes> grpcEpisodes =
        episodeUtils.mapGrpcObjectsToPojo(
            episodesClient.getEpisodesByClipId(getEpisodesByClipIdRequest).getEpisodesList(),
            Episodes.class);

    List<Episodes> mongoEpisodes =
        episodeUtils.getEpisodesByClipId(clipId, 10, 0, null, projection);

    Assert.assertEquals(mongoEpisodes, grpcEpisodes);
  }

  @Test
  @Description("GetAgeRangePopulatedEpisodeById - Projection validation")
  @TmsLink("28500971")
  public void C28500971_TEST() {
    String randomEpisodeId = episodeUtils.getIdsOfEpisodesWithAgeRange(1).get(0);

    String[] projectionFieldsArray = new String[] {"_id", "name", "description"};
    List<String> projectionFields = List.of("_id", "name", "description");

    AgeRangeEpisode mongoAgeRangeEpisode =
        ageRangeEpisodeUtils.getAgeRangePopulatedEpisodeById(
            randomEpisodeId, projectionFieldsArray);

    GetAgeRangePopulatedEpisodeByIdRequest getAgeRangePopulatedEpisodeByIdRequest =
        GetAgeRangePopulatedEpisodeByIdRequest.newBuilder()
            .setId(randomEpisodeId)
            .addAllProjection(projectionFields)
            .build();

    AgeRangeEpisode grpcAgeRangeEpisode =
        episodeUtils.mapGrpcObjectToPojo(
            episodesClient
                .getAgeRangePopulatedEpisodeById(getAgeRangePopulatedEpisodeByIdRequest)
                .getDocument(),
            AgeRangeEpisode.class);

    Assert.assertEquals(
        mongoAgeRangeEpisode, grpcAgeRangeEpisode, "MongoDB and gRPC responses do not match!");
  }

  @Test
  @Description("GetChannelLibrary - Validate has_live_broadcast in gRPC output")
  @TmsLink("28500972")
  public void C28500972_TEST() {
    Series series = seriesUtils.getExistsAndSizeNotZero("channels");
    Assert.assertNotNull(series, "No series with channels found");
    String channelId = series.getChannels().get(0).toString();
    LogUtils.logInfoMessage(
        "ValidateHasLiveBroadcastInChannelLibrary_TEST channelId = " + channelId);

    tv.pluto.ip.dal.episode.Episodes.GetChannelLibraryRequest request =
        tv.pluto.ip.dal.episode.Episodes.GetChannelLibraryRequest.newBuilder()
            .setChannelId(channelId)
            .setLimit(10)
            .build();

    tv.pluto.ip.dal.episode.Episodes.GetChannelLibraryResponse grpcResponse =
        episodesClient.getChannelLibrary(request);

    Assert.assertNotNull(grpcResponse, "gRPC response is null");
    Assert.assertFalse(
        grpcResponse.getDocumentsList().isEmpty(), "No documents found in gRPC response");

    for (ChannelLibraryEpisode grpcEpisode : grpcResponse.getDocumentsList()) {
      Assert.assertNotNull(grpcEpisode, "ChannelLibraryEpisode is null");
      Assert.assertNotNull(
          grpcEpisode.getHasLiveBroadcast(),
          "has_live_broadcast field is not present for timeline draft ID: ");
    }
  }

  @Test
  @Description(
      "Episode.UpdateLastAired - Update last aired date for an episode with past timelines")
  @TmsLink("28500973")
  public void C28500973_TEST() {

    Episodes mongoEpisodeBeforeUpdate = episodeUtils.getEpisodeForPastTimelines();

    String episodeId = mongoEpisodeBeforeUpdate.getId();
    LogUtils.logInfoMessage("Episode ID: " + episodeId);

    Episodes mappedEpisodeFromGrpc =
        episodeUtils.mapGrpcObjectToPojo(
            episodesClient.updateLastAired(episodeId).getDocument(), Episodes.class);

    Episodes mongoEpisodeAfterUpdate = episodeUtils.getDocumentById(episodeId);
    Assert.assertNotNull(mongoEpisodeAfterUpdate, "Mongo episode after update is null");

    Assert.assertEquals(
        mappedEpisodeFromGrpc.getLastAired(),
        mongoEpisodeAfterUpdate.getLastAired(),
        "Mismatch in 'lastAired' field between gRPC and Mongo episode");

    Assert.assertEquals(
        mappedEpisodeFromGrpc,
        mongoEpisodeAfterUpdate,
        "Episode from gRPC does not fully match the episode in Mongo after update");
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "INTERNAL: No Timeline found associated with Episode.*")
  @Description("Episode.UpdateLastAired - Episode with no past timelines")
  @TmsLink("28500974")
  public void C28500974_TEST() {

    Episodes mongoEpisodeBeforeUpdate = episodeUtils.getSingleEpisodeWithoutTimelines();

    String episodeId = mongoEpisodeBeforeUpdate.getId();
    LogUtils.logInfoMessage("Episode ID: " + episodeId);

    episodesClient.updateLastAired(episodeId);
  }

  @Test(
      expectedExceptions = StatusRuntimeException.class,
      expectedExceptionsMessageRegExp = "INTERNAL:.*")
  @Description("Episode.UpdateLastAired - Episode with invalid ID")
  @TmsLink("28500975")
  public void C28500975_TEST() {
    String episodeId = "invalid";
    episodesClient.updateLastAired(episodeId);
  }
}
