diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 42f26eb8..12396644 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -100,6 +100,9 @@ jobs: - name: Integration Tests if: matrix.java == 'temurin@17' && matrix.os == 'ubuntu-latest' && startsWith(matrix.project, 'root-js') + env: + CBOR_ENABLED: ${{ matrix.cbor_enabled }} + SERVICE_PORT: ${{ matrix.service_port }} uses: nick-fields/retry@v2 with: timeout_minutes: 15 diff --git a/README.md b/README.md index 42df0275..9f8024d8 100644 --- a/README.md +++ b/README.md @@ -327,5 +327,4 @@ object MyApp { ``` # Known issues -- Does not currently support Http2 requests (https://github.com/http4s/http4s/issues/4707) - Does not currently support SubscribeToShard due to lack of push-promise support (https://github.com/http4s/http4s/issues/4624) diff --git a/kinesis-mock/src/main/scala/kinesis/mock/KinesisMockService.scala b/kinesis-mock/src/main/scala/kinesis/mock/KinesisMockService.scala index d390757f..6d262fef 100644 --- a/kinesis-mock/src/main/scala/kinesis/mock/KinesisMockService.scala +++ b/kinesis-mock/src/main/scala/kinesis/mock/KinesisMockService.scala @@ -88,12 +88,14 @@ object KinesisMockService extends IOApp { .withHost(host) .withTLS(tlsContext) .withHttpApp(app) + .withHttp2 .build plainServer = EmberServerBuilder .default[IO] .withPort(serviceConfig.plainPort) .withHost(host) .withHttpApp(app) + .withHttp2 .build _ <- logger.info( s"Starting Kinesis TLS Mock Service on port ${serviceConfig.tlsPort}" diff --git a/kinesis-mock/src/main/scala/kinesis/mock/Utils.scala b/kinesis-mock/src/main/scala/kinesis/mock/Utils.scala index add22637..2cb4540c 100644 --- a/kinesis-mock/src/main/scala/kinesis/mock/Utils.scala +++ b/kinesis-mock/src/main/scala/kinesis/mock/Utils.scala @@ -2,6 +2,7 @@ package kinesis.mock import java.time.Instant +import cats.effect.IO import cats.effect.SyncIO import cats.effect.std.UUIDGen @@ -9,6 +10,5 @@ object Utils { def randomUUID = UUIDGen.randomUUID[SyncIO].unsafeRunSync() def randomUUIDString = UUIDGen.randomString[SyncIO].unsafeRunSync() def md5(bytes: Array[Byte]): Array[Byte] = MD5.compute(bytes) - def now = - SyncIO.realTime.map(d => Instant.EPOCH.plusNanos(d.toNanos)).unsafeRunSync() + def now = IO.realTime.map(d => Instant.EPOCH.plusNanos(d.toNanos)) } diff --git a/kinesis-mock/src/main/scala/kinesis/mock/api/CreateStreamRequest.scala b/kinesis-mock/src/main/scala/kinesis/mock/api/CreateStreamRequest.scala index ea90411e..654957f6 100644 --- a/kinesis-mock/src/main/scala/kinesis/mock/api/CreateStreamRequest.scala +++ b/kinesis-mock/src/main/scala/kinesis/mock/api/CreateStreamRequest.scala @@ -38,44 +38,51 @@ final case class CreateStreamRequest( awsRegion: AwsRegion, awsAccountId: AwsAccountId ): IO[Response[Unit]] = - streamsRef.modify { streams => - val shardCountOrDefault = shardCount.getOrElse(4) - val streamArn = StreamArn(awsRegion, streamName, awsAccountId) - ( - CommonValidations.validateStreamName(streamName), - if (streams.streams.contains(streamArn)) - ResourceInUseException( - s"Stream $streamName already exists" - ).asLeft - else Right(()), - CommonValidations.validateShardCount(shardCountOrDefault), - if ( - streams.streams.count { case (_, stream) => - stream.streamStatus == StreamStatus.CREATING - } >= 5 - ) - LimitExceededException( - "Limit for streams being created concurrently exceeded" - ).asLeft - else Right(()), - CommonValidations.validateOnDemandStreamCount( - streams, - onDemandStreamCountLimit - ), - CommonValidations.validateShardLimit( - shardCountOrDefault, - streams, - shardLimit - ) - ).mapN { (_, _, _, _, _, _) => - val newStream = - StreamData.create(shardCountOrDefault, streamArn, streamModeDetails) + Utils.now.flatMap { now => + streamsRef.modify { streams => + val shardCountOrDefault = shardCount.getOrElse(4) + val streamArn = StreamArn(awsRegion, streamName, awsAccountId) ( - streams - .copy(streams = streams.streams ++ Seq(streamArn -> newStream)), - () - ) - }.sequenceWithDefault(streams) + CommonValidations.validateStreamName(streamName), + if (streams.streams.contains(streamArn)) + ResourceInUseException( + s"Stream $streamName already exists" + ).asLeft + else Right(()), + CommonValidations.validateShardCount(shardCountOrDefault), + if ( + streams.streams.count { case (_, stream) => + stream.streamStatus == StreamStatus.CREATING + } >= 5 + ) + LimitExceededException( + "Limit for streams being created concurrently exceeded" + ).asLeft + else Right(()), + CommonValidations.validateOnDemandStreamCount( + streams, + onDemandStreamCountLimit + ), + CommonValidations.validateShardLimit( + shardCountOrDefault, + streams, + shardLimit + ) + ).mapN { (_, _, _, _, _, _) => + val newStream = + StreamData.create( + shardCountOrDefault, + streamArn, + streamModeDetails, + now + ) + ( + streams + .copy(streams = streams.streams ++ Seq(streamArn -> newStream)), + () + ) + }.sequenceWithDefault(streams) + } } } diff --git a/kinesis-mock/src/main/scala/kinesis/mock/api/GetRecordsRequest.scala b/kinesis-mock/src/main/scala/kinesis/mock/api/GetRecordsRequest.scala index 226bc1ed..f1683c75 100644 --- a/kinesis-mock/src/main/scala/kinesis/mock/api/GetRecordsRequest.scala +++ b/kinesis-mock/src/main/scala/kinesis/mock/api/GetRecordsRequest.scala @@ -37,138 +37,71 @@ final case class GetRecordsRequest( streamsRef: Ref[IO, Streams], awsRegion: AwsRegion, awsAccountId: AwsAccountId - ): IO[Response[GetRecordsResponse]] = streamsRef.get.map { streams => - shardIterator.parse.flatMap { parts => - val arn = streamArn.getOrElse( - StreamArn(awsRegion, parts.streamName, awsAccountId) - ) - CommonValidations - .isStreamActiveOrUpdating(arn, streams) - .flatMap(_ => - CommonValidations - .findStream(arn, streams) - .flatMap(stream => - CommonValidations.findShard(parts.shardId, stream).flatMap { case (shard, data) => - (limit match { - case Some(l) => CommonValidations.validateLimit(l) - case None => Right(()) - }).flatMap { _ => - val allShards = stream.shards.keys.toVector - val childShards = allShards - .filter(x => - x.parentShardId.contains(shard.shardId.shardId) || - x.adjacentParentShardId - .contains(shard.shardId.shardId) - ) - .map(s => - ChildShard.fromShard( - s, - allShards.filter(x => - s.adjacentParentShardId.contains( - x.shardId.shardId - ) || s.parentShardId.contains(x.shardId.shardId) + ): IO[Response[GetRecordsResponse]] = Utils.now.flatMap { now => + streamsRef.get.map { streams => + shardIterator.parse(now).flatMap { parts => + val arn = streamArn.getOrElse( + StreamArn(awsRegion, parts.streamName, awsAccountId) + ) + CommonValidations + .isStreamActiveOrUpdating(arn, streams) + .flatMap(_ => + CommonValidations + .findStream(arn, streams) + .flatMap(stream => + CommonValidations.findShard(parts.shardId, stream).flatMap { + case (shard, data) => + (limit match { + case Some(l) => CommonValidations.validateLimit(l) + case None => Right(()) + }).flatMap { _ => + val allShards = stream.shards.keys.toVector + val childShards = allShards + .filter(x => + x.parentShardId.contains(shard.shardId.shardId) || + x.adjacentParentShardId + .contains(shard.shardId.shardId) ) - ) - ) - if (data.isEmpty) { - Right( - GetRecordsResponse( - if (childShards.nonEmpty) Some(childShards) - else None, - 0L, - if (childShards.nonEmpty) None - else - Some( - ShardIterator.create( - parts.streamName, - parts.shardId, - parts.sequenceNumber + .map(s => + ChildShard.fromShard( + s, + allShards.filter(x => + s.adjacentParentShardId.contains( + x.shardId.shardId + ) || s.parentShardId.contains(x.shardId.shardId) ) - ), - Queue.empty - ) - ) - } else { - if ( - parts.sequenceNumber == shard.sequenceNumberRange.startingSequenceNumber - ) { - val maxRecords = limit.getOrElse(10000) - - val (head, records) = GetRecordsRequest - .getRecords( - data.take(maxRecords), - Queue.empty, - data.head, - 0 - ) - - val millisBehindLatest = - data.last.approximateArrivalTimestamp.toEpochMilli - - head.approximateArrivalTimestamp.toEpochMilli - - Right( - GetRecordsResponse( - if ( - records.length == data.length && childShards.nonEmpty - ) Some(childShards) - else None, - millisBehindLatest, - if ( - records.length == data.length && childShards.nonEmpty - ) None - else - Some( - ShardIterator.create( - parts.streamName, - parts.shardId, - records.last.sequenceNumber - ) - ), - records + ) ) - ) - } else { - data - .indexWhere( - _.sequenceNumber == parts.sequenceNumber - ) match { - case -1 => - ResourceNotFoundException( - s"Record for provided SequenceNumber not found" - ).asLeft - case index if index == data.length - 1 => - Right( - GetRecordsResponse( - if (childShards.nonEmpty) Some(childShards) - else None, - 0L, - if (childShards.nonEmpty) None - else - Some( - ShardIterator.create( - parts.streamName, - parts.shardId, - parts.sequenceNumber - ) - ), - Queue.empty - ) + if (data.isEmpty) { + Right( + GetRecordsResponse( + if (childShards.nonEmpty) Some(childShards) + else None, + 0L, + if (childShards.nonEmpty) None + else + Some( + ShardIterator.create( + parts.streamName, + parts.shardId, + parts.sequenceNumber, + now + ) + ), + Queue.empty ) - - case index => + ) + } else { + if ( + parts.sequenceNumber == shard.sequenceNumberRange.startingSequenceNumber + ) { val maxRecords = limit.getOrElse(10000) - val firstIndex = index + 1 - val lastIndex = - Math.min( - firstIndex + maxRecords, - data.length - ) val (head, records) = GetRecordsRequest .getRecords( - data.slice(firstIndex, lastIndex), + data.take(maxRecords), Queue.empty, - data(firstIndex), + data.head, 0 ) @@ -179,34 +112,108 @@ final case class GetRecordsRequest( Right( GetRecordsResponse( if ( - data.lastOption == records.lastOption && data.lastOption.nonEmpty && childShards.nonEmpty - ) - Some(childShards) + records.length == data.length && childShards.nonEmpty + ) Some(childShards) else None, millisBehindLatest, if ( - data.lastOption == records.lastOption && data.lastOption.nonEmpty && childShards.nonEmpty - ) - None + records.length == data.length && childShards.nonEmpty + ) None else Some( ShardIterator.create( parts.streamName, parts.shardId, - records.last.sequenceNumber + records.last.sequenceNumber, + now ) ), records ) ) + } else { + data + .indexWhere( + _.sequenceNumber == parts.sequenceNumber + ) match { + case -1 => + ResourceNotFoundException( + s"Record for provided SequenceNumber not found" + ).asLeft + case index if index == data.length - 1 => + Right( + GetRecordsResponse( + if (childShards.nonEmpty) Some(childShards) + else None, + 0L, + if (childShards.nonEmpty) None + else + Some( + ShardIterator.create( + parts.streamName, + parts.shardId, + parts.sequenceNumber, + now + ) + ), + Queue.empty + ) + ) + + case index => + val maxRecords = limit.getOrElse(10000) + val firstIndex = index + 1 + val lastIndex = + Math.min( + firstIndex + maxRecords, + data.length + ) + + val (head, records) = GetRecordsRequest + .getRecords( + data.slice(firstIndex, lastIndex), + Queue.empty, + data(firstIndex), + 0 + ) + + val millisBehindLatest = + data.last.approximateArrivalTimestamp.toEpochMilli - + head.approximateArrivalTimestamp.toEpochMilli + + Right( + GetRecordsResponse( + if ( + data.lastOption == records.lastOption && data.lastOption.nonEmpty && childShards.nonEmpty + ) + Some(childShards) + else None, + millisBehindLatest, + if ( + data.lastOption == records.lastOption && data.lastOption.nonEmpty && childShards.nonEmpty + ) + None + else + Some( + ShardIterator.create( + parts.streamName, + parts.shardId, + records.last.sequenceNumber, + now + ) + ), + records + ) + ) + } + } } - } - } + } } - } - ) - ) + ) + ) + } } } } diff --git a/kinesis-mock/src/main/scala/kinesis/mock/api/GetShardIteratorRequest.scala b/kinesis-mock/src/main/scala/kinesis/mock/api/GetShardIteratorRequest.scala index e5ffb803..82779329 100644 --- a/kinesis-mock/src/main/scala/kinesis/mock/api/GetShardIteratorRequest.scala +++ b/kinesis-mock/src/main/scala/kinesis/mock/api/GetShardIteratorRequest.scala @@ -40,203 +40,223 @@ final case class GetShardIteratorRequest( streamsRef: Ref[IO, Streams], awsRegion: AwsRegion, awsAccountId: AwsAccountId - ): IO[Response[GetShardIteratorResponse]] = streamsRef.get.map { streams => - CommonValidations - .getStreamNameArn(streamName, streamArn, awsRegion, awsAccountId) - .flatMap { case (name, arn) => - CommonValidations - .validateStreamName(name) - .flatMap(_ => - CommonValidations - .findStream(arn, streams) - .flatMap(stream => - ( - CommonValidations - .isStreamActiveOrUpdating(arn, streams), - startingSequenceNumber match { - case Some(sequenceNumber) => - CommonValidations.validateSequenceNumber(sequenceNumber) - case None => Right(()) - }, - (shardIteratorType, startingSequenceNumber, timestamp) match { - case (ShardIteratorType.AT_SEQUENCE_NUMBER, None, _) | - (ShardIteratorType.AFTER_SEQUENCE_NUMBER, None, _) => - InvalidArgumentException( - s"StartingSequenceNumber must be provided for ShardIteratorType $shardIteratorType" - ).asLeft - case (ShardIteratorType.AT_TIMESTAMP, _, None) => - InvalidArgumentException( - s"Timestamp must be provided for ShardIteratorType $shardIteratorType" - ).asLeft - case _ => Right(()) - }, - CommonValidations.validateShardId(shardId), - CommonValidations.findShard(shardId, stream).flatMap { - case (shard, data) => - if (data.isEmpty) - Right( - GetShardIteratorResponse( - ShardIterator.create( - name, - shardId, - shard.sequenceNumberRange.startingSequenceNumber + ): IO[Response[GetShardIteratorResponse]] = Utils.now.flatMap { now => + streamsRef.get.map { streams => + CommonValidations + .getStreamNameArn(streamName, streamArn, awsRegion, awsAccountId) + .flatMap { case (name, arn) => + CommonValidations + .validateStreamName(name) + .flatMap(_ => + CommonValidations + .findStream(arn, streams) + .flatMap(stream => + ( + CommonValidations + .isStreamActiveOrUpdating(arn, streams), + startingSequenceNumber match { + case Some(sequenceNumber) => + CommonValidations.validateSequenceNumber(sequenceNumber) + case None => Right(()) + }, + ( + shardIteratorType, + startingSequenceNumber, + timestamp + ) match { + case (ShardIteratorType.AT_SEQUENCE_NUMBER, None, _) | + (ShardIteratorType.AFTER_SEQUENCE_NUMBER, None, _) => + InvalidArgumentException( + s"StartingSequenceNumber must be provided for ShardIteratorType $shardIteratorType" + ).asLeft + case (ShardIteratorType.AT_TIMESTAMP, _, None) => + InvalidArgumentException( + s"Timestamp must be provided for ShardIteratorType $shardIteratorType" + ).asLeft + case _ => Right(()) + }, + CommonValidations.validateShardId(shardId), + CommonValidations.findShard(shardId, stream).flatMap { + case (shard, data) => + if (data.isEmpty) + Right( + GetShardIteratorResponse( + ShardIterator.create( + name, + shardId, + shard.sequenceNumberRange.startingSequenceNumber, + now + ) ) ) - ) - else - ( - shardIteratorType, - startingSequenceNumber, - timestamp - ) match { - case (ShardIteratorType.TRIM_HORIZON, _, _) => - Right( - GetShardIteratorResponse( - ShardIterator.create( - name, - shardId, - shard.sequenceNumberRange.startingSequenceNumber + else + ( + shardIteratorType, + startingSequenceNumber, + timestamp + ) match { + case (ShardIteratorType.TRIM_HORIZON, _, _) => + Right( + GetShardIteratorResponse( + ShardIterator.create( + name, + shardId, + shard.sequenceNumberRange.startingSequenceNumber, + now + ) ) ) - ) - case (ShardIteratorType.LATEST, _, _) => - Right( - GetShardIteratorResponse( - ShardIterator.create( - name, - shardId, - data.last.sequenceNumber + case (ShardIteratorType.LATEST, _, _) => + Right( + GetShardIteratorResponse( + ShardIterator.create( + name, + shardId, + data.last.sequenceNumber, + now + ) ) ) - ) - case (ShardIteratorType.AT_TIMESTAMP, _, Some(ts)) => - val now = Utils.now - if (ts.toEpochMilli > now.toEpochMilli) - InvalidArgumentException( - s"Timestamp cannot be in the future" - ).asLeft - else { - val sequenceNumber = - data - .find( - _.approximateArrivalTimestamp.toEpochMilli >= ts.toEpochMilli - ) - .map(data.indexOf) - .flatMap(x => - if (x == 0) - Some( - shard.sequenceNumberRange.startingSequenceNumber - ) - else - data - .get(x.toLong - 1L) - .map(_.sequenceNumber) + case ( + ShardIteratorType.AT_TIMESTAMP, + _, + Some(ts) + ) => + if (ts.toEpochMilli > now.toEpochMilli) + InvalidArgumentException( + s"Timestamp cannot be in the future" + ).asLeft + else { + val sequenceNumber = + data + .find( + _.approximateArrivalTimestamp.toEpochMilli >= ts.toEpochMilli + ) + .map(data.indexOf) + .flatMap(x => + if (x == 0) + Some( + shard.sequenceNumberRange.startingSequenceNumber + ) + else + data + .get(x.toLong - 1L) + .map(_.sequenceNumber) + ) + .getOrElse(data.last.sequenceNumber) + Right( + GetShardIteratorResponse( + ShardIterator.create( + name, + shardId, + sequenceNumber, + now + ) ) - .getOrElse(data.last.sequenceNumber) + ) + } + case ( + ShardIteratorType.AT_SEQUENCE_NUMBER, + Some(seqNo), + _ + ) if seqNo == shard.sequenceNumberRange.startingSequenceNumber => Right( GetShardIteratorResponse( ShardIterator.create( name, shardId, - sequenceNumber + shard.sequenceNumberRange.startingSequenceNumber, + now ) ) ) - } - case ( - ShardIteratorType.AT_SEQUENCE_NUMBER, - Some(seqNo), - _ - ) if seqNo == shard.sequenceNumberRange.startingSequenceNumber => - Right( - GetShardIteratorResponse( - ShardIterator.create( - name, - shardId, - shard.sequenceNumberRange.startingSequenceNumber - ) - ) - ) - case ( - ShardIteratorType.AT_SEQUENCE_NUMBER, - Some(seqNo), - _ - ) => - data.find(_.sequenceNumber == seqNo) match { - case Some(record) => - if (record == data.head) - Right( - GetShardIteratorResponse( - ShardIterator.create( - name, - shardId, - shard.sequenceNumberRange.startingSequenceNumber + case ( + ShardIteratorType.AT_SEQUENCE_NUMBER, + Some(seqNo), + _ + ) => + data.find(_.sequenceNumber == seqNo) match { + case Some(record) => + if (record == data.head) + Right( + GetShardIteratorResponse( + ShardIterator.create( + name, + shardId, + shard.sequenceNumberRange.startingSequenceNumber, + now + ) + ) + ) + else + Right( + GetShardIteratorResponse( + ShardIterator.create( + name, + shardId, + data( + data.indexOf(record) - 1 + ).sequenceNumber, + now + ) ) ) + case None => + ResourceNotFoundException( + s"Unable to find record with provided SequenceNumber $seqNo in stream $streamName" + ).asLeft + } + case ( + ShardIteratorType.AFTER_SEQUENCE_NUMBER, + Some(seqNo), + _ + ) if seqNo == shard.sequenceNumberRange.startingSequenceNumber => + Right( + GetShardIteratorResponse( + ShardIterator.create( + name, + shardId, + shard.sequenceNumberRange.startingSequenceNumber, + now ) - else + ) + ) + case ( + ShardIteratorType.AFTER_SEQUENCE_NUMBER, + Some(seqNo), + _ + ) => + data.find(_.sequenceNumber == seqNo) match { + case Some(record) => Right( GetShardIteratorResponse( ShardIterator.create( name, shardId, data( - data.indexOf(record) - 1 - ).sequenceNumber + data.indexOf(record) + ).sequenceNumber, + now ) ) ) - case None => - ResourceNotFoundException( - s"Unable to find record with provided SequenceNumber $seqNo in stream $streamName" - ).asLeft - } - case ( - ShardIteratorType.AFTER_SEQUENCE_NUMBER, - Some(seqNo), - _ - ) if seqNo == shard.sequenceNumberRange.startingSequenceNumber => - Right( - GetShardIteratorResponse( - ShardIterator.create( - name, - shardId, - shard.sequenceNumberRange.startingSequenceNumber - ) - ) - ) - case ( - ShardIteratorType.AFTER_SEQUENCE_NUMBER, - Some(seqNo), - _ - ) => - data.find(_.sequenceNumber == seqNo) match { - case Some(record) => - Right( - GetShardIteratorResponse( - ShardIterator.create( - name, - shardId, - data(data.indexOf(record)).sequenceNumber - ) - ) - ) - case None => - ResourceNotFoundException( - s"Unable to find record with provided SequenceNumber $seqNo in stream $streamName" - ).asLeft - } + case None => + ResourceNotFoundException( + s"Unable to find record with provided SequenceNumber $seqNo in stream $streamName" + ).asLeft + } - case _ => - InvalidArgumentException( - s"Request for GetShardIterator invalid. ShardIteratorType: $shardIteratorType, StartingSequenceNumber: $startingSequenceNumber, Timestamp: $timestamp" - ).asLeft - } - } - ).mapN((_, _, _, _, res) => res) - ) - ) - } + case _ => + InvalidArgumentException( + s"Request for GetShardIterator invalid. ShardIteratorType: $shardIteratorType, StartingSequenceNumber: $startingSequenceNumber, Timestamp: $timestamp" + ).asLeft + } + } + ).mapN((_, _, _, _, res) => res) + ) + ) + } + } } } diff --git a/kinesis-mock/src/main/scala/kinesis/mock/api/ListShardsRequest.scala b/kinesis-mock/src/main/scala/kinesis/mock/api/ListShardsRequest.scala index 578e57fb..95ac096e 100644 --- a/kinesis-mock/src/main/scala/kinesis/mock/api/ListShardsRequest.scala +++ b/kinesis-mock/src/main/scala/kinesis/mock/api/ListShardsRequest.scala @@ -42,68 +42,81 @@ final case class ListShardsRequest( streamsRef: Ref[IO, Streams], awsRegion: AwsRegion, awsAccountId: AwsAccountId - ): IO[Response[ListShardsResponse]] = streamsRef.get.map { streams => - ( - exclusiveStartShardId, - nextToken, - shardFilter, - streamCreationTimestamp, - streamName, - streamArn - ) match { - case (None, Some(nt), None, None, None, None) => - CommonValidations - .validateNextToken(nt) - .flatMap(ListShardsRequest.parseNextToken) - .flatMap { case (streamName, shardId) => - val streamArn = StreamArn(awsRegion, streamName, awsAccountId) - ( - CommonValidations.validateStreamName(streamName), - CommonValidations.validateShardId(shardId), - CommonValidations.findStream(streamArn, streams), - maxResults match { - case Some(l) => CommonValidations.validateMaxResults(l) - case _ => Right(()) - }, - shardFilter match { - case Some(sf) => ListShardsRequest.validateShardFilter(sf) - case None => Right(()) + ): IO[Response[ListShardsResponse]] = Utils.now.flatMap { now => + streamsRef.get.map { streams => + ( + exclusiveStartShardId, + nextToken, + shardFilter, + streamCreationTimestamp, + streamName, + streamArn + ) match { + case (None, Some(nt), None, None, None, None) => + CommonValidations + .validateNextToken(nt) + .flatMap(ListShardsRequest.parseNextToken) + .flatMap { case (streamName, shardId) => + val streamArn = StreamArn(awsRegion, streamName, awsAccountId) + ( + CommonValidations.validateStreamName(streamName), + CommonValidations.validateShardId(shardId), + CommonValidations.findStream(streamArn, streams), + maxResults match { + case Some(l) => CommonValidations.validateMaxResults(l) + case _ => Right(()) + }, + shardFilter match { + case Some(sf) => ListShardsRequest.validateShardFilter(sf) + case None => Right(()) + } + ).mapN { (_, _, stream, _, _) => + val allShards = stream.shards.keys.toVector + val lastShardIndex = allShards.length - 1 + val limit = maxResults.map(l => Math.min(l, 100)).getOrElse(100) + val firstIndex = + allShards.indexWhere(_.shardId.shardId == shardId) + 1 + val lastIndex = Math.min(firstIndex + limit, lastShardIndex + 1) + val shards = allShards.slice(firstIndex, lastIndex) + val nextToken = + if (lastShardIndex + 1 == lastIndex) None + else + Some( + ListShardsRequest + .createNextToken( + streamName, + shards.last.shardId.shardId + ) + ) + ListShardsResponse( + nextToken, + shards.map(ShardSummary.fromShard) + ) } - ).mapN { (_, _, stream, _, _) => - val allShards = stream.shards.keys.toVector - val lastShardIndex = allShards.length - 1 - val limit = maxResults.map(l => Math.min(l, 100)).getOrElse(100) - val firstIndex = - allShards.indexWhere(_.shardId.shardId == shardId) + 1 - val lastIndex = Math.min(firstIndex + limit, lastShardIndex + 1) - val shards = allShards.slice(firstIndex, lastIndex) - val nextToken = - if (lastShardIndex + 1 == lastIndex) None - else - Some( - ListShardsRequest - .createNextToken(streamName, shards.last.shardId.shardId) - ) - ListShardsResponse(nextToken, shards.map(ShardSummary.fromShard)) } - } - case (_, None, _, _, _, Some(sArn)) => - getList(sArn, sArn.streamName, streams) - case (_, None, _, _, Some(sName), _) => - val streamArn = StreamArn(awsRegion, sName, awsAccountId) - getList(streamArn, sName, streams) - case (_, None, _, _, None, None) => - InvalidArgumentException( - "StreamName or StreamARN is required if NextToken is not provided" - ).asLeft - case _ => - InvalidArgumentException( - "Cannot define ExclusiveStartShardId, StreamCreationTimestamp or StreamName if NextToken is defined" - ).asLeft + case (_, None, _, _, _, Some(sArn)) => + getList(sArn, sArn.streamName, streams, now) + case (_, None, _, _, Some(sName), _) => + val streamArn = StreamArn(awsRegion, sName, awsAccountId) + getList(streamArn, sName, streams, now) + case (_, None, _, _, None, None) => + InvalidArgumentException( + "StreamName or StreamARN is required if NextToken is not provided" + ).asLeft + case _ => + InvalidArgumentException( + "Cannot define ExclusiveStartShardId, StreamCreationTimestamp or StreamName if NextToken is defined" + ).asLeft + } } } - def getList(streamArn: StreamArn, sName: StreamName, streams: Streams) = CommonValidations + def getList( + streamArn: StreamArn, + sName: StreamName, + streams: Streams, + now: Instant + ) = CommonValidations .findStream(streamArn, streams) .flatMap(stream => ( @@ -132,7 +145,6 @@ final case class ListShardsRequest( )) => allShards case Some(sf) if sf.`type` == ShardFilterType.FROM_TRIM_HORIZON => - val now = Utils.now allShards.filter(x => x.closedTimestamp.isEmpty || x.closedTimestamp.exists(x => x.plusSeconds(stream.retentionPeriod.toSeconds) diff --git a/kinesis-mock/src/main/scala/kinesis/mock/api/MergeShardsRequest.scala b/kinesis-mock/src/main/scala/kinesis/mock/api/MergeShardsRequest.scala index a547ceb2..c42e9d99 100644 --- a/kinesis-mock/src/main/scala/kinesis/mock/api/MergeShardsRequest.scala +++ b/kinesis-mock/src/main/scala/kinesis/mock/api/MergeShardsRequest.scala @@ -17,6 +17,8 @@ package kinesis.mock package api +import java.time.Instant + import cats.Eq import cats.effect.{IO, Ref} import cats.syntax.all._ @@ -38,85 +40,88 @@ final case class MergeShardsRequest( awsRegion: AwsRegion, awsAccountId: AwsAccountId ): IO[Response[Unit]] = - streamsRef.modify(streams => - CommonValidations - .getStreamNameArn(streamName, streamArn, awsRegion, awsAccountId) - .flatMap { case (name, arn) => - CommonValidations - .validateStreamName(name) - .flatMap(_ => - CommonValidations - .findStream(arn, streams) - .flatMap { stream => - ( - CommonValidations.isStreamActive(arn, streams), - CommonValidations.validateShardId(shardToMerge), - CommonValidations.validateShardId(adjacentShardToMerge), - CommonValidations - .findShard(adjacentShardToMerge, stream) - .flatMap { case (adjacentShard, adjacentData) => - CommonValidations.isShardOpen(adjacentShard).flatMap { - _ => - CommonValidations - .findShard(shardToMerge, stream) - .flatMap { case (shard, shardData) => - CommonValidations.isShardOpen(shard).flatMap { - _ => - if ( - adjacentShard.hashKeyRange - .isAdjacent(shard.hashKeyRange) - ) - Right( - ( - (adjacentShard, adjacentData), - (shard, shardData) - ) + Utils.now.flatMap { now => + streamsRef.modify(streams => + CommonValidations + .getStreamNameArn(streamName, streamArn, awsRegion, awsAccountId) + .flatMap { case (name, arn) => + CommonValidations + .validateStreamName(name) + .flatMap(_ => + CommonValidations + .findStream(arn, streams) + .flatMap { stream => + ( + CommonValidations.isStreamActive(arn, streams), + CommonValidations.validateShardId(shardToMerge), + CommonValidations.validateShardId(adjacentShardToMerge), + CommonValidations + .findShard(adjacentShardToMerge, stream) + .flatMap { case (adjacentShard, adjacentData) => + CommonValidations.isShardOpen(adjacentShard).flatMap { + _ => + CommonValidations + .findShard(shardToMerge, stream) + .flatMap { case (shard, shardData) => + CommonValidations.isShardOpen(shard).flatMap { + _ => + if ( + adjacentShard.hashKeyRange + .isAdjacent(shard.hashKeyRange) ) - else - InvalidArgumentException( - "Provided shards are not adjacent" - ).asLeft + Right( + ( + (adjacentShard, adjacentData), + (shard, shardData) + ) + ) + else + InvalidArgumentException( + "Provided shards are not adjacent" + ).asLeft + } } - } + } } - } - ).mapN { - case ( - _, - _, - _, - ((adjacentShard, adjacentData), (shard, shardData)) - ) => - ( - stream, - (adjacentShard, adjacentData), - (shard, shardData) - ) + ).mapN { + case ( + _, + _, + _, + ((adjacentShard, adjacentData), (shard, shardData)) + ) => + ( + stream, + (adjacentShard, adjacentData), + (shard, shardData) + ) + } } - } - ) - .map { - case ( - stream, - (adjacentShard, adjacentData), - (shard, shardData) - ) => - ( - streams.updateStream( - MergeShardsRequest.mergeShards( + ) + .map { + case ( stream, - adjacentShard, - adjacentData, - shard, - shardData - ) - ), - () - ) - } - } - .sequenceWithDefault(streams) - ) + (adjacentShard, adjacentData), + (shard, shardData) + ) => + ( + streams.updateStream( + MergeShardsRequest.mergeShards( + stream, + adjacentShard, + adjacentData, + shard, + shardData, + now + ) + ), + () + ) + } + } + .sequenceWithDefault(streams) + ) + } } object MergeShardsRequest { @@ -126,9 +131,9 @@ object MergeShardsRequest { adjacentShard: Shard, adjacentData: Vector[KinesisRecord], shard: Shard, - shardData: Vector[KinesisRecord] + shardData: Vector[KinesisRecord], + now: Instant ): StreamData = { - val now = Utils.now val newShardIndex = stream.shards.keys.map(_.shardId.index).max + 1 val newShard: (Shard, Vector[KinesisRecord]) = Shard( diff --git a/kinesis-mock/src/main/scala/kinesis/mock/api/PutRecordRequest.scala b/kinesis-mock/src/main/scala/kinesis/mock/api/PutRecordRequest.scala index 47ee6155..7650fda0 100644 --- a/kinesis-mock/src/main/scala/kinesis/mock/api/PutRecordRequest.scala +++ b/kinesis-mock/src/main/scala/kinesis/mock/api/PutRecordRequest.scala @@ -39,83 +39,84 @@ final case class PutRecordRequest( streamsRef: Ref[IO, Streams], awsRegion: AwsRegion, awsAccountId: AwsAccountId - ): IO[Response[PutRecordResponse]] = streamsRef.modify { streams => - val now = Utils.now - CommonValidations - .getStreamNameArn(streamName, streamArn, awsRegion, awsAccountId) - .flatMap { case (name, arn) => - CommonValidations - .validateStreamName(name) - .flatMap(_ => - CommonValidations - .findStream(arn, streams) - .flatMap { stream => - ( - CommonValidations - .isStreamActiveOrUpdating(arn, streams), - CommonValidations.validateData(data), - sequenceNumberForOrdering match { - case None => Right(()) - case Some(seqNo) => - seqNo.parse.flatMap { - case parts: SequenceNumberParts - if parts.seqTime.toEpochMilli > now.toEpochMilli => - InvalidArgumentException( - s"Sequence time in the future" - ).asLeft - case x => Right(x) - } - }, - CommonValidations.validatePartitionKey(partitionKey), - explicitHashKey match { - case Some(explHashKeh) => - CommonValidations.validateExplicitHashKey(explHashKeh) - case None => Right(()) - }, - CommonValidations - .computeShard(partitionKey, explicitHashKey, stream) - .flatMap { case (shard, records) => - CommonValidations - .isShardOpen(shard) - .map(_ => (shard, records)) + ): IO[Response[PutRecordResponse]] = Utils.now.flatMap { now => + streamsRef.modify { streams => + CommonValidations + .getStreamNameArn(streamName, streamArn, awsRegion, awsAccountId) + .flatMap { case (name, arn) => + CommonValidations + .validateStreamName(name) + .flatMap(_ => + CommonValidations + .findStream(arn, streams) + .flatMap { stream => + ( + CommonValidations + .isStreamActiveOrUpdating(arn, streams), + CommonValidations.validateData(data), + sequenceNumberForOrdering match { + case None => Right(()) + case Some(seqNo) => + seqNo.parse.flatMap { + case parts: SequenceNumberParts + if parts.seqTime.toEpochMilli > now.toEpochMilli => + InvalidArgumentException( + s"Sequence time in the future" + ).asLeft + case x => Right(x) + } + }, + CommonValidations.validatePartitionKey(partitionKey), + explicitHashKey match { + case Some(explHashKeh) => + CommonValidations.validateExplicitHashKey(explHashKeh) + case None => Right(()) + }, + CommonValidations + .computeShard(partitionKey, explicitHashKey, stream) + .flatMap { case (shard, records) => + CommonValidations + .isShardOpen(shard) + .map(_ => (shard, records)) - } - ).mapN { case (_, _, _, _, _, (shard, records)) => - (stream, shard, records) + } + ).mapN { case (_, _, _, _, _, (shard, records)) => + (stream, shard, records) + } } - } + ) + } + .map { case (stream, shard, records) => + val seqNo = SequenceNumber.create( + shard.createdAtTimestamp, + shard.shardId.index, + None, + Some(records.length), + Some(now) ) - } - .map { case (stream, shard, records) => - val seqNo = SequenceNumber.create( - shard.createdAtTimestamp, - shard.shardId.index, - None, - Some(records.length), - Some(now) - ) - ( - streams.updateStream { - stream.copy( - shards = stream.shards ++ Map( - shard -> (records :+ KinesisRecord( - now, - data, - stream.encryptionType, - partitionKey, - seqNo - )) + ( + streams.updateStream { + stream.copy( + shards = stream.shards ++ Map( + shard -> (records :+ KinesisRecord( + now, + data, + stream.encryptionType, + partitionKey, + seqNo + )) + ) ) + }, + PutRecordResponse( + stream.encryptionType, + seqNo, + shard.shardId.shardId ) - }, - PutRecordResponse( - stream.encryptionType, - seqNo, - shard.shardId.shardId ) - ) - } - .sequenceWithDefault(streams) + } + .sequenceWithDefault(streams) + } } } diff --git a/kinesis-mock/src/main/scala/kinesis/mock/api/PutRecordsRequest.scala b/kinesis-mock/src/main/scala/kinesis/mock/api/PutRecordsRequest.scala index 7f8fa621..9080d366 100644 --- a/kinesis-mock/src/main/scala/kinesis/mock/api/PutRecordsRequest.scala +++ b/kinesis-mock/src/main/scala/kinesis/mock/api/PutRecordsRequest.scala @@ -38,107 +38,109 @@ final case class PutRecordsRequest( awsRegion: AwsRegion, awsAccountId: AwsAccountId ): IO[Response[PutRecordsResponse]] = - streamsRef.modify[Response[PutRecordsResponse]] { streams => - val now = Utils.now - CommonValidations - .getStreamNameArn(streamName, streamArn, awsRegion, awsAccountId) - .flatMap { case (name, arn) => - CommonValidations - .validateStreamName(name) - .flatMap(_ => - CommonValidations - .findStream(arn, streams) - .flatMap { stream => - ( - CommonValidations - .isStreamActiveOrUpdating(arn, streams), - records.traverse(x => - ( - CommonValidations.validatePartitionKey(x.partitionKey), - x.explicitHashKey match { - case Some(explHashKey) => - CommonValidations - .validateExplicitHashKey(explHashKey) - case None => Right(()) - }, - CommonValidations.validateData(x.data), - CommonValidations - .computeShard( - x.partitionKey, - x.explicitHashKey, - stream - ) - .flatMap { case (shard, records) => - CommonValidations - .isShardOpen(shard) - .map(_ => (shard, records)) - } - ).mapN { case (_, _, _, (shard, records)) => - (shard, records, x) - } - ) - ).mapN((_, recs) => (stream, recs)) - } - ) - } - .map { case (stream, recs) => - val asRecords = PutRecordsRequest - .getIndexByShard(recs) - .map { case (shard, records, entry, index) => - val seqNo = SequenceNumber.create( - shard.createdAtTimestamp, - shard.shardId.index, - None, - Some(records.length + index), - Some(now) + Utils.now.flatMap { now => + streamsRef.modify[Response[PutRecordsResponse]] { streams => + CommonValidations + .getStreamNameArn(streamName, streamArn, awsRegion, awsAccountId) + .flatMap { case (name, arn) => + CommonValidations + .validateStreamName(name) + .flatMap(_ => + CommonValidations + .findStream(arn, streams) + .flatMap { stream => + ( + CommonValidations + .isStreamActiveOrUpdating(arn, streams), + records.traverse(x => + ( + CommonValidations + .validatePartitionKey(x.partitionKey), + x.explicitHashKey match { + case Some(explHashKey) => + CommonValidations + .validateExplicitHashKey(explHashKey) + case None => Right(()) + }, + CommonValidations.validateData(x.data), + CommonValidations + .computeShard( + x.partitionKey, + x.explicitHashKey, + stream + ) + .flatMap { case (shard, records) => + CommonValidations + .isShardOpen(shard) + .map(_ => (shard, records)) + } + ).mapN { case (_, _, _, (shard, records)) => + (shard, records, x) + } + ) + ).mapN((_, recs) => (stream, recs)) + } ) - - ( - shard, - records, - KinesisRecord( - now, - entry.data, - stream.encryptionType, - entry.partitionKey, - seqNo - ), - PutRecordsResultEntry( - None, + } + .map { case (stream, recs) => + val asRecords = PutRecordsRequest + .getIndexByShard(recs) + .map { case (shard, records, entry, index) => + val seqNo = SequenceNumber.create( + shard.createdAtTimestamp, + shard.shardId.index, None, - Some(seqNo), - Some(shard.shardId.shardId) + Some(records.length + index), + Some(now) ) - ) - } - val newShards = asRecords - .groupBy { case (shard, currentRecords, _, _) => - (shard, currentRecords) - } - .map { case ((shard, currentRecords), recordsToAdd) => - ( - shard, - currentRecords ++ recordsToAdd.map(_._3) - ) - } + ( + shard, + records, + KinesisRecord( + now, + entry.data, + stream.encryptionType, + entry.partitionKey, + seqNo + ), + PutRecordsResultEntry( + None, + None, + Some(seqNo), + Some(shard.shardId.shardId) + ) + ) + } - ( - streams.updateStream( - stream.copy( - shards = stream.shards ++ newShards - ) - ), - PutRecordsResponse( - stream.encryptionType, - 0, - asRecords.map { case (_, _, _, entry) => - entry + val newShards = asRecords + .groupBy { case (shard, currentRecords, _, _) => + (shard, currentRecords) + } + .map { case ((shard, currentRecords), recordsToAdd) => + ( + shard, + currentRecords ++ recordsToAdd.map(_._3) + ) } + + ( + streams.updateStream( + stream.copy( + shards = stream.shards ++ newShards + ) + ), + PutRecordsResponse( + stream.encryptionType, + 0, + asRecords.map { case (_, _, _, entry) => + entry + } + ) ) - ) - } - .sequenceWithDefault(streams) + } + .sequenceWithDefault(streams) + } } } diff --git a/kinesis-mock/src/main/scala/kinesis/mock/api/RegisterStreamConsumerRequest.scala b/kinesis-mock/src/main/scala/kinesis/mock/api/RegisterStreamConsumerRequest.scala index a7574855..1dbbd010 100644 --- a/kinesis-mock/src/main/scala/kinesis/mock/api/RegisterStreamConsumerRequest.scala +++ b/kinesis-mock/src/main/scala/kinesis/mock/api/RegisterStreamConsumerRequest.scala @@ -34,52 +34,54 @@ final case class RegisterStreamConsumerRequest( def registerStreamConsumer( streamsRef: Ref[IO, Streams] ): IO[Response[RegisterStreamConsumerResponse]] = - streamsRef.modify { streams => - CommonValidations - .validateStreamArn(streamArn) - .flatMap(_ => - CommonValidations - .findStream(streamArn, streams) - .flatMap(stream => - ( - CommonValidations.validateConsumerName(consumerName), - if (stream.consumers.size >= 20) - LimitExceededException( - s"Only 20 consumers can be registered to a stream at once" - ).asLeft - else Right(()), - if ( - stream.consumers.values - .count(_.consumerStatus == ConsumerStatus.CREATING) >= 5 - ) - LimitExceededException( - s"Only 5 consumers can be created at the same time" - ).asLeft - else Right(()), - if (stream.consumers.contains(consumerName)) - ResourceInUseException( - s"Consumer $consumerName exists" - ).asLeft - else Right(()) - ).mapN((_, _, _, _) => (stream, streamArn, consumerName)) - ) - ) - .map { case (stream, streamArn, consumerName) => - val consumer = Consumer.create(streamArn, consumerName) + Utils.now.flatMap { now => + streamsRef.modify { streams => + CommonValidations + .validateStreamArn(streamArn) + .flatMap(_ => + CommonValidations + .findStream(streamArn, streams) + .flatMap(stream => + ( + CommonValidations.validateConsumerName(consumerName), + if (stream.consumers.size >= 20) + LimitExceededException( + s"Only 20 consumers can be registered to a stream at once" + ).asLeft + else Right(()), + if ( + stream.consumers.values + .count(_.consumerStatus == ConsumerStatus.CREATING) >= 5 + ) + LimitExceededException( + s"Only 5 consumers can be created at the same time" + ).asLeft + else Right(()), + if (stream.consumers.contains(consumerName)) + ResourceInUseException( + s"Consumer $consumerName exists" + ).asLeft + else Right(()) + ).mapN((_, _, _, _) => (stream, streamArn, consumerName)) + ) + ) + .map { case (stream, streamArn, consumerName) => + val consumer = Consumer.create(streamArn, consumerName, now) - ( - streams.updateStream( - stream - .copy(consumers = - stream.consumers ++ Seq(consumerName -> consumer) - ) - ), - RegisterStreamConsumerResponse( - ConsumerSummary.fromConsumer(consumer) + ( + streams.updateStream( + stream + .copy(consumers = + stream.consumers ++ Seq(consumerName -> consumer) + ) + ), + RegisterStreamConsumerResponse( + ConsumerSummary.fromConsumer(consumer) + ) ) - ) - } - .sequenceWithDefault(streams) + } + .sequenceWithDefault(streams) + } } } diff --git a/kinesis-mock/src/main/scala/kinesis/mock/api/SplitShardRequest.scala b/kinesis-mock/src/main/scala/kinesis/mock/api/SplitShardRequest.scala index 0689bda6..c1b0b8fc 100644 --- a/kinesis-mock/src/main/scala/kinesis/mock/api/SplitShardRequest.scala +++ b/kinesis-mock/src/main/scala/kinesis/mock/api/SplitShardRequest.scala @@ -17,6 +17,8 @@ package kinesis.mock package api +import java.time.Instant + import cats.Eq import cats.effect.{IO, Ref} import cats.syntax.all._ @@ -39,68 +41,72 @@ final case class SplitShardRequest( awsRegion: AwsRegion, awsAccountId: AwsAccountId ): IO[Response[Unit]] = - streamsRef.modify { streams => - CommonValidations - .getStreamNameArn(streamName, streamArn, awsRegion, awsAccountId) - .flatMap { case (name, arn) => - CommonValidations - .validateStreamName(name) - .flatMap(_ => - CommonValidations - .findStream(arn, streams) - .flatMap { stream => - ( - CommonValidations.isStreamActive(arn, streams), - CommonValidations.validateShardId(shardToSplit), - if (!newStartingHashKey.matches("0|([1-9]\\d{0,38})")) { - InvalidArgumentException( - "NewStartingHashKey contains invalid characters" - ).asLeft - } else Right(newStartingHashKey), - if ( - streams.streams.values - .map(_.shards.size) - .sum + 1 > shardLimit - ) - LimitExceededException( - "Operation would exceed the configured shard limit for the account" - ).asLeft - else Right(()), - CommonValidations.findShard(shardToSplit, stream).flatMap { - case (shard, shardData) => - CommonValidations.isShardOpen(shard).flatMap { _ => - val newStartingHashKeyNumber = - BigInt(newStartingHashKey) - if ( - newStartingHashKeyNumber >= shard.hashKeyRange.startingHashKey && newStartingHashKeyNumber <= shard.hashKeyRange.endingHashKey - ) - Right((shard, shardData)) - else - InvalidArgumentException( - s"NewStartingHashKey is not within the hash range shard ${shard.shardId}" - ).asLeft + Utils.now.flatMap { now => + streamsRef.modify { streams => + CommonValidations + .getStreamNameArn(streamName, streamArn, awsRegion, awsAccountId) + .flatMap { case (name, arn) => + CommonValidations + .validateStreamName(name) + .flatMap(_ => + CommonValidations + .findStream(arn, streams) + .flatMap { stream => + ( + CommonValidations.isStreamActive(arn, streams), + CommonValidations.validateShardId(shardToSplit), + if (!newStartingHashKey.matches("0|([1-9]\\d{0,38})")) { + InvalidArgumentException( + "NewStartingHashKey contains invalid characters" + ).asLeft + } else Right(newStartingHashKey), + if ( + streams.streams.values + .map(_.shards.size) + .sum + 1 > shardLimit + ) + LimitExceededException( + "Operation would exceed the configured shard limit for the account" + ).asLeft + else Right(()), + CommonValidations + .findShard(shardToSplit, stream) + .flatMap { case (shard, shardData) => + CommonValidations.isShardOpen(shard).flatMap { _ => + val newStartingHashKeyNumber = + BigInt(newStartingHashKey) + if ( + newStartingHashKeyNumber >= shard.hashKeyRange.startingHashKey && newStartingHashKeyNumber <= shard.hashKeyRange.endingHashKey + ) + Right((shard, shardData)) + else + InvalidArgumentException( + s"NewStartingHashKey is not within the hash range shard ${shard.shardId}" + ).asLeft + } } + ).mapN { case (_, _, _, _, (shard, shardData)) => + (shard, shardData, stream) } - ).mapN { case (_, _, _, _, (shard, shardData)) => - (shard, shardData, stream) } - } - ) - .map { case (shard, shardData, stream) => - ( - streams.updateStream( - SplitShardRequest.splitShard( - newStartingHashKey, - shard, - shardData, - stream - ) - ), - () ) - } - } - .sequenceWithDefault(streams) + .map { case (shard, shardData, stream) => + ( + streams.updateStream( + SplitShardRequest.splitShard( + newStartingHashKey, + shard, + shardData, + stream, + now + ) + ), + () + ) + } + } + .sequenceWithDefault(streams) + } } } @@ -110,9 +116,9 @@ object SplitShardRequest { newStartingHashKey: String, shard: Shard, shardData: Vector[KinesisRecord], - stream: StreamData + stream: StreamData, + now: Instant ): StreamData = { - val now = Utils.now val newStartingHashKeyNumber = BigInt(newStartingHashKey) val newShardIndex1 = stream.shards.keys.map(_.shardId.index).max + 1 diff --git a/kinesis-mock/src/main/scala/kinesis/mock/api/UpdateShardCountRequest.scala b/kinesis-mock/src/main/scala/kinesis/mock/api/UpdateShardCountRequest.scala index c5e34121..b3a16b57 100644 --- a/kinesis-mock/src/main/scala/kinesis/mock/api/UpdateShardCountRequest.scala +++ b/kinesis-mock/src/main/scala/kinesis/mock/api/UpdateShardCountRequest.scala @@ -19,6 +19,8 @@ package api import scala.concurrent.duration._ +import java.time.Instant + import cats.Eq import cats.effect.{IO, Ref} import cats.syntax.all._ @@ -41,87 +43,92 @@ final case class UpdateShardCountRequest( awsRegion: AwsRegion, awsAccountId: AwsAccountId ): IO[Response[UpdateShardCountResponse]] = - streamsRef.modify { streams => - val now = Utils.now - CommonValidations - .getStreamNameArn(streamName, streamArn, awsRegion, awsAccountId) - .flatMap { case (name, arn) => - CommonValidations - .validateStreamName(name) - .flatMap(_ => - CommonValidations - .findStream(arn, streams) - .flatMap { stream => - ( - CommonValidations.isStreamActive(arn, streams), - if ( - targetShardCount > stream.shards.keys.count(_.isOpen) * 2 - ) - InvalidArgumentException( - "Cannot update shard count beyond 2x current shard count" - ).asLeft - else if ( - targetShardCount < stream.shards.keys.count(_.isOpen) / 2 - ) - InvalidArgumentException( - "Cannot update shard count below 50% of the current shard count" - ).asLeft - else if (targetShardCount > 10000) - InvalidArgumentException( - "Cannot scale a stream beyond 10000 shards" - ).asLeft - else if ( - streams.streams.values - .map(_.shards.size) - .sum + (targetShardCount - stream.shards.size) > shardLimit - ) - LimitExceededException( - "Operation would result more shards than the configured shard limit for this account" - ).asLeft - else Right(targetShardCount), - if ( - stream.shardCountUpdates.count(ts => - ts.toEpochMilli > now - .minusMillis(1.day.toMillis) - .toEpochMilli - ) >= 10 - ) - LimitExceededException( - "Cannot run UpdateShardCount more than 10 times in a 24 hour period" - ).asLeft - else Right(()) - ).mapN((_, _, _) => stream) + Utils.now.flatMap { now => + streamsRef.modify { streams => + CommonValidations + .getStreamNameArn(streamName, streamArn, awsRegion, awsAccountId) + .flatMap { case (name, arn) => + CommonValidations + .validateStreamName(name) + .flatMap(_ => + CommonValidations + .findStream(arn, streams) + .flatMap { stream => + ( + CommonValidations.isStreamActive(arn, streams), + if ( + targetShardCount > stream.shards.keys + .count(_.isOpen) * 2 + ) + InvalidArgumentException( + "Cannot update shard count beyond 2x current shard count" + ).asLeft + else if ( + targetShardCount < stream.shards.keys + .count(_.isOpen) / 2 + ) + InvalidArgumentException( + "Cannot update shard count below 50% of the current shard count" + ).asLeft + else if (targetShardCount > 10000) + InvalidArgumentException( + "Cannot scale a stream beyond 10000 shards" + ).asLeft + else if ( + streams.streams.values + .map(_.shards.size) + .sum + (targetShardCount - stream.shards.size) > shardLimit + ) + LimitExceededException( + "Operation would result more shards than the configured shard limit for this account" + ).asLeft + else Right(targetShardCount), + if ( + stream.shardCountUpdates.count(ts => + ts.toEpochMilli > now + .minusMillis(1.day.toMillis) + .toEpochMilli + ) >= 10 + ) + LimitExceededException( + "Cannot run UpdateShardCount more than 10 times in a 24 hour period" + ).asLeft + else Right(()) + ).mapN((_, _, _) => stream) + } + ) + .map { stream => + val openShards = stream.shards.toList.filter(_._1.isOpen) + val scalingUp = openShards.size < targetShardCount + + val newStreamData = if (scalingUp) { + UpdateShardCountRequest.splitShards( + stream, + openShards, + targetShardCount, + now + ) + } else { + UpdateShardCountRequest.mergeShards( + stream, + openShards, + targetShardCount, + now + ) } - ) - .map { stream => - val openShards = stream.shards.toList.filter(_._1.isOpen) - val scalingUp = openShards.size < targetShardCount - val newStreamData = if (scalingUp) { - UpdateShardCountRequest.splitShards( - stream, - openShards, - targetShardCount - ) - } else { - UpdateShardCountRequest.mergeShards( - stream, - openShards, - targetShardCount + ( + streams.updateStream(newStreamData), + UpdateShardCountResponse( + openShards.length, + name, + targetShardCount + ) ) } - - ( - streams.updateStream(newStreamData), - UpdateShardCountResponse( - openShards.length, - name, - targetShardCount - ) - ) - } - } - .sequenceWithDefault(streams) + } + .sequenceWithDefault(streams) + } } } @@ -130,7 +137,8 @@ object UpdateShardCountRequest { def mergeShards( streamData: StreamData, openShards: List[(Shard, Vector[KinesisRecord])], - targetShardCount: Int + targetShardCount: Int, + now: Instant ): StreamData = openShards match { case _ if streamData.shards.toList.count(_._1.isOpen) === targetShardCount => @@ -149,19 +157,21 @@ object UpdateShardCountRequest { adjacentShard, adjacentData, oldShard, - oldShardData + oldShardData, + now ), t.filterNot(_._1.shardId == adjacentShard.shardId) ) } - mergeShards(newStreamData, newOpenShards, targetShardCount) + mergeShards(newStreamData, newOpenShards, targetShardCount, now) } @annotation.tailrec def splitShards( streamData: StreamData, openShards: List[(Shard, Vector[KinesisRecord])], - targetShardCount: Int + targetShardCount: Int, + now: Instant ): StreamData = openShards match { case _ if streamData.shards.toList.count(_._1.isOpen) === targetShardCount => @@ -175,10 +185,12 @@ object UpdateShardCountRequest { .toString(), oldShard, oldShardData, - streamData + streamData, + now ), t, - targetShardCount + targetShardCount, + now ) } diff --git a/kinesis-mock/src/main/scala/kinesis/mock/models/Consumer.scala b/kinesis-mock/src/main/scala/kinesis/mock/models/Consumer.scala index 088474e5..aa275100 100644 --- a/kinesis-mock/src/main/scala/kinesis/mock/models/Consumer.scala +++ b/kinesis-mock/src/main/scala/kinesis/mock/models/Consumer.scala @@ -34,8 +34,11 @@ final case class Consumer( ) object Consumer { - def create(streamArn: StreamArn, consumerName: ConsumerName): Consumer = { - val consumerCreationTimestamp = Utils.now + def create( + streamArn: StreamArn, + consumerName: ConsumerName, + consumerCreationTimestamp: Instant + ): Consumer = Consumer( ConsumerArn(streamArn, consumerName, consumerCreationTimestamp), consumerCreationTimestamp, @@ -43,7 +46,6 @@ object Consumer { ConsumerStatus.CREATING, streamArn ) - } def consumerCirceEncoder(implicit EI: circe.Encoder[Instant] ): circe.Encoder[Consumer] = circe.Encoder.forProduct5( diff --git a/kinesis-mock/src/main/scala/kinesis/mock/models/ShardIterator.scala b/kinesis-mock/src/main/scala/kinesis/mock/models/ShardIterator.scala index 66560ac4..780dccd4 100644 --- a/kinesis-mock/src/main/scala/kinesis/mock/models/ShardIterator.scala +++ b/kinesis-mock/src/main/scala/kinesis/mock/models/ShardIterator.scala @@ -19,6 +19,7 @@ package models import scala.util.Try +import java.time.Instant import java.util.Base64 import cats.Eq @@ -28,9 +29,8 @@ import io.circe._ import kinesis.mock.validations.CommonValidations final case class ShardIterator(value: String) { - def parse: Response[ShardIteratorParts] = { + def parse(now: Instant): Response[ShardIteratorParts] = { val decoded = Base64.getDecoder.decode(value) - val now = Utils.now val decrypted = new String( AES.decrypt( @@ -98,10 +98,11 @@ object ShardIterator { def create( streamName: StreamName, shardId: String, - sequenceNumber: SequenceNumber + sequenceNumber: SequenceNumber, + now: Instant ): ShardIterator = { val encryptString = - (Vector.fill(14)("0").mkString + Utils.now.toEpochMilli) + (Vector.fill(14)("0").mkString + now.toEpochMilli) .takeRight(14) + s"/$streamName" + s"/$shardId" + diff --git a/kinesis-mock/src/main/scala/kinesis/mock/models/StreamData.scala b/kinesis-mock/src/main/scala/kinesis/mock/models/StreamData.scala index 14e8b4ec..60716cbf 100644 --- a/kinesis-mock/src/main/scala/kinesis/mock/models/StreamData.scala +++ b/kinesis-mock/src/main/scala/kinesis/mock/models/StreamData.scala @@ -152,11 +152,11 @@ object StreamData { def create( shardCount: Int, streamArn: StreamArn, - streamModeDetails: Option[StreamModeDetails] + streamModeDetails: Option[StreamModeDetails], + now: Instant ): StreamData = { - val createTime = Utils.now val shards: SortedMap[Shard, Vector[KinesisRecord]] = - Shard.newShards(shardCount, createTime, 0) + Shard.newShards(shardCount, now, 0) StreamData( SortedMap.empty, EncryptionType.NONE, @@ -165,7 +165,7 @@ object StreamData { minRetentionPeriod, shards, streamArn, - Utils.now, + now, streamModeDetails.getOrElse(StreamModeDetails(StreamMode.PROVISIONED)), streamArn.streamName, StreamStatus.CREATING, diff --git a/kinesis-mock/src/main/scala/kinesis/mock/models/Streams.scala b/kinesis-mock/src/main/scala/kinesis/mock/models/Streams.scala index 244c77ae..818a95dd 100644 --- a/kinesis-mock/src/main/scala/kinesis/mock/models/Streams.scala +++ b/kinesis-mock/src/main/scala/kinesis/mock/models/Streams.scala @@ -19,6 +19,8 @@ package models import scala.collection.SortedMap +import java.time.Instant + import cats.Eq import cats.syntax.all._ import io.circe._ @@ -39,11 +41,17 @@ final case class Streams(streams: SortedMap[StreamArn, StreamData]) { def addStream( shardCount: Int, streamArn: StreamArn, - streamModeDetails: Option[StreamModeDetails] + streamModeDetails: Option[StreamModeDetails], + now: Instant ): Streams = copy(streams = streams ++ Seq( - streamArn -> StreamData.create(shardCount, streamArn, streamModeDetails) + streamArn -> StreamData.create( + shardCount, + streamArn, + streamModeDetails, + now + ) ) ) diff --git a/project/KinesisMockPlugin.scala b/project/KinesisMockPlugin.scala index 188c28d1..06361fae 100644 --- a/project/KinesisMockPlugin.scala +++ b/project/KinesisMockPlugin.scala @@ -159,6 +159,10 @@ object KinesisMockPlugin extends AutoPlugin { "max_attempts" -> "3", "command" -> "sbt 'project ${{ matrix.project }}' integration-tests/test", "retry_on" -> "error" + ), + env = Map( + "CBOR_ENABLED" -> "${{ matrix.cbor_enabled }}", + "SERVICE_PORT" -> "${{ matrix.service_port }}" ) ), WorkflowStep.Sbt( diff --git a/testkit/src/main/scala/kinesis/mock/instances/arbitrary.scala b/testkit/src/main/scala/kinesis/mock/instances/arbitrary.scala index 419efbdc..2a9b99b6 100644 --- a/testkit/src/main/scala/kinesis/mock/instances/arbitrary.scala +++ b/testkit/src/main/scala/kinesis/mock/instances/arbitrary.scala @@ -69,7 +69,7 @@ object arbitrary { if (streamName.isEmpty) streamArnGen.map(Some(_)) else Gen.const(None) } yield (streamName, streamArn) - val nowGen: Gen[Instant] = Gen.delay(Gen.const(Utils.now)) + val nowGen: Gen[Instant] = Gen.delay(Gen.const(Instant.now)) implicit val sequenceNumberArbitrary: Arbitrary[SequenceNumber] = Arbitrary( Gen.option(Arbitrary.arbitrary[SequenceNumberConstant]).flatMap { @@ -568,16 +568,18 @@ object arbitrary { streamName <- streamNameGen shardId <- shardIdArbitrary.arbitrary sequenceNumber <- sequenceNumberArbitrary.arbitrary - } yield ShardIterator.create(streamName, shardId.shardId, sequenceNumber) + now <- nowGen + } yield ShardIterator.create(streamName, shardId.shardId, sequenceNumber, now) implicit val getRecordsRequestArb: Arbitrary[GetRecordsRequest] = Arbitrary( for { limit <- Gen.option(limitGen) shardIterator <- shardIteratorGen + now <- nowGen streamArn <- Gen.option( streamArnGen.map(x => x.copy(streamName = - shardIterator.parse.map(_.streamName).getOrElse(x.streamName) + shardIterator.parse(now).map(_.streamName).getOrElse(x.streamName) ) ) ) diff --git a/unit-tests/src/test/scala/kinesis/mock/api/AddTagsToStreamTests.scala b/unit-tests/src/test/scala/kinesis/mock/api/AddTagsToStreamTests.scala index 911a94d3..8aa1b224 100644 --- a/unit-tests/src/test/scala/kinesis/mock/api/AddTagsToStreamTests.scala +++ b/unit-tests/src/test/scala/kinesis/mock/api/AddTagsToStreamTests.scala @@ -35,10 +35,9 @@ class AddTagsToStreamTests streamArn: StreamArn, tags: Tags ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) streamsRef <- Ref.of[IO, Streams](streams) req = AddTagsToStreamRequest(None, Some(streamArn), tags) res <- req.addTagsToStream( @@ -59,19 +58,17 @@ class AddTagsToStreamTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val tagKey = tagKeyGen.one - val tagValue = tagValueGen.one - val tags = Tags(SortedMap(tagKey -> tagValue)) - val initialTags = Tags(SortedMap(tagKey -> "initial")) - - val streamsWithTag = streams.findAndUpdateStream(streamArn)(stream => - stream.copy(tags = initialTags) - ) - for { + now <- Utils.now + streams = + Streams.empty.addStream(1, streamArn, None, now) + tagKey = tagKeyGen.one + tagValue = tagValueGen.one + tags = Tags(SortedMap(tagKey -> tagValue)) + initialTags = Tags(SortedMap(tagKey -> "initial")) + streamsWithTag = streams.findAndUpdateStream(streamArn)(stream => + stream.copy(tags = initialTags) + ) streamsRef <- Ref.of[IO, Streams](streamsWithTag) req = AddTagsToStreamRequest(None, Some(streamArn), tags) res <- req.addTagsToStream( diff --git a/unit-tests/src/test/scala/kinesis/mock/api/DecreaseStreamRetentionPeriodTests.scala b/unit-tests/src/test/scala/kinesis/mock/api/DecreaseStreamRetentionPeriodTests.scala index 6b0ec34f..5f9d6d69 100644 --- a/unit-tests/src/test/scala/kinesis/mock/api/DecreaseStreamRetentionPeriodTests.scala +++ b/unit-tests/src/test/scala/kinesis/mock/api/DecreaseStreamRetentionPeriodTests.scala @@ -22,6 +22,7 @@ import cats.effect.{IO, Ref} import enumeratum.scalacheck._ import org.scalacheck.effect.PropF +import kinesis.mock.Utils import kinesis.mock.instances.arbitrary._ import kinesis.mock.models._ @@ -32,16 +33,16 @@ class DecreaseStreamRetentionPeriodTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - val withIncreasedRetention = - streams.findAndUpdateStream(streamArn)(stream => - stream.copy( - retentionPeriod = 48.hours, - streamStatus = StreamStatus.ACTIVE - ) - ) for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + withIncreasedRetention = streams.findAndUpdateStream(streamArn)( + stream => + stream.copy( + retentionPeriod = 48.hours, + streamStatus = StreamStatus.ACTIVE + ) + ) streamsRef <- Ref.of[IO, Streams](withIncreasedRetention) req = DecreaseStreamRetentionPeriodRequest(24, None, Some(streamArn)) res <- req.decreaseStreamRetention( @@ -64,10 +65,9 @@ class DecreaseStreamRetentionPeriodTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) streamsRef <- Ref.of[IO, Streams](streams) req = DecreaseStreamRetentionPeriodRequest(48, None, Some(streamArn)) res <- req.decreaseStreamRetention( diff --git a/unit-tests/src/test/scala/kinesis/mock/api/DeleteStreamTests.scala b/unit-tests/src/test/scala/kinesis/mock/api/DeleteStreamTests.scala index 18274450..8854fa88 100644 --- a/unit-tests/src/test/scala/kinesis/mock/api/DeleteStreamTests.scala +++ b/unit-tests/src/test/scala/kinesis/mock/api/DeleteStreamTests.scala @@ -22,6 +22,7 @@ import cats.effect.{IO, Ref} import enumeratum.scalacheck._ import org.scalacheck.effect.PropF +import kinesis.mock.Utils import kinesis.mock.instances.arbitrary._ import kinesis.mock.models._ @@ -32,14 +33,12 @@ class DeleteStreamTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val asActive = streams.findAndUpdateStream(streamArn)(x => - x.copy(streamStatus = StreamStatus.ACTIVE) - ) - for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + asActive = streams.findAndUpdateStream(streamArn)(x => + x.copy(streamStatus = StreamStatus.ACTIVE) + ) streamsRef <- Ref.of[IO, Streams](asActive) req = DeleteStreamRequest(None, Some(streamArn), None) res <- req.deleteStream( @@ -75,10 +74,9 @@ class DeleteStreamTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) streamsRef <- Ref.of[IO, Streams](streams) req = DeleteStreamRequest(None, Some(streamArn), None) res <- req @@ -95,21 +93,18 @@ class DeleteStreamTests ( consumerArn: ConsumerArn ) => - val streams = - Streams.empty.addStream(1, consumerArn.streamArn, None) - - val withConsumers = - streams.findAndUpdateStream(consumerArn.streamArn)(x => + for { + now <- Utils.now + streams = Streams.empty.addStream(1, consumerArn.streamArn, None, now) + withConsumers = streams.findAndUpdateStream(consumerArn.streamArn)(x => x.copy( consumers = SortedMap( consumerArn.consumerName -> Consumer - .create(x.streamArn, consumerArn.consumerName) + .create(x.streamArn, consumerArn.consumerName, now) ), streamStatus = StreamStatus.ACTIVE ) ) - - for { streamsRef <- Ref.of[IO, Streams](withConsumers) req = DeleteStreamRequest(None, Some(consumerArn.streamArn), None) res <- req.deleteStream( @@ -129,21 +124,18 @@ class DeleteStreamTests ( consumerArn: ConsumerArn ) => - val streams = - Streams.empty.addStream(1, consumerArn.streamArn, None) - - val withConsumers = - streams.findAndUpdateStream(consumerArn.streamArn)(x => + for { + now <- Utils.now + streams = Streams.empty.addStream(1, consumerArn.streamArn, None, now) + withConsumers = streams.findAndUpdateStream(consumerArn.streamArn)(x => x.copy( consumers = SortedMap( consumerArn.consumerName -> Consumer - .create(x.streamArn, consumerArn.consumerName) + .create(x.streamArn, consumerArn.consumerName, now) ), streamStatus = StreamStatus.ACTIVE ) ) - - for { streamsRef <- Ref.of[IO, Streams](withConsumers) req = DeleteStreamRequest( None, diff --git a/unit-tests/src/test/scala/kinesis/mock/api/DeregisterStreamConsumerTests.scala b/unit-tests/src/test/scala/kinesis/mock/api/DeregisterStreamConsumerTests.scala index 24eb0a23..30da0986 100644 --- a/unit-tests/src/test/scala/kinesis/mock/api/DeregisterStreamConsumerTests.scala +++ b/unit-tests/src/test/scala/kinesis/mock/api/DeregisterStreamConsumerTests.scala @@ -33,23 +33,20 @@ class DeregisterStreamConsumerTests ( consumerArn: ConsumerArn ) => - val streams = - Streams.empty.addStream(1, consumerArn.streamArn, None) - val updated = - streams.findAndUpdateStream(consumerArn.streamArn) { stream => + for { + now <- Utils.now + streams = Streams.empty.addStream(1, consumerArn.streamArn, None, now) + updated = streams.findAndUpdateStream(consumerArn.streamArn) { stream => stream.copy( streamStatus = StreamStatus.ACTIVE, consumers = SortedMap( consumerArn.consumerName -> Consumer - .create(stream.streamArn, consumerArn.consumerName) + .create(stream.streamArn, consumerArn.consumerName, now) .copy(consumerStatus = ConsumerStatus.ACTIVE) ) ) } - val streamArn = - streams.streams.get(consumerArn.streamArn).map(_.streamArn) - - for { + streamArn = streams.streams.get(consumerArn.streamArn).map(_.streamArn) streamsRef <- Ref.of[IO, Streams](updated) req = DeregisterStreamConsumerRequest( None, @@ -72,22 +69,19 @@ class DeregisterStreamConsumerTests ( consumerArn: ConsumerArn ) => - val streams = - Streams.empty.addStream(1, consumerArn.streamArn, None) - - val updated = - streams.findAndUpdateStream(consumerArn.streamArn) { stream => + for { + now <- Utils.now + streams = Streams.empty.addStream(1, consumerArn.streamArn, None, now) + updated = streams.findAndUpdateStream(consumerArn.streamArn) { stream => stream.copy( streamStatus = StreamStatus.ACTIVE, consumers = SortedMap( consumerArn.consumerName -> Consumer - .create(stream.streamArn, consumerArn.consumerName) + .create(stream.streamArn, consumerArn.consumerName, now) .copy(consumerStatus = ConsumerStatus.ACTIVE) ) ) } - - for { streamsRef <- Ref.of[IO, Streams](updated) req = DeregisterStreamConsumerRequest(Some(consumerArn), None, None) res <- req.deregisterStreamConsumer(streamsRef) @@ -106,21 +100,18 @@ class DeregisterStreamConsumerTests ( consumerArn: ConsumerArn ) => - val streams = - Streams.empty.addStream(1, consumerArn.streamArn, None) - - val updated = - streams.findAndUpdateStream(consumerArn.streamArn) { stream => + for { + now <- Utils.now + streams = Streams.empty.addStream(1, consumerArn.streamArn, None, now) + updated = streams.findAndUpdateStream(consumerArn.streamArn) { stream => stream.copy( streamStatus = StreamStatus.ACTIVE, consumers = SortedMap( consumerArn.consumerName -> Consumer - .create(stream.streamArn, consumerArn.consumerName) + .create(stream.streamArn, consumerArn.consumerName, now) ) ) } - - for { streamsRef <- Ref.of[IO, Streams](updated) req = DeregisterStreamConsumerRequest(Some(consumerArn), None, None) res <- req.deregisterStreamConsumer(streamsRef) @@ -134,10 +125,9 @@ class DeregisterStreamConsumerTests ( consumerArn: ConsumerArn ) => - val streams = - Streams.empty.addStream(1, consumerArn.streamArn, None) - for { + now <- Utils.now + streams = Streams.empty.addStream(1, consumerArn.streamArn, None, now) streamsRef <- Ref.of[IO, Streams](streams) req = DeregisterStreamConsumerRequest( None, @@ -157,21 +147,19 @@ class DeregisterStreamConsumerTests ( consumerArn: ConsumerArn ) => - val streams = - Streams.empty.addStream(1, consumerArn.streamArn, None) - val updated = - streams.findAndUpdateStream(consumerArn.streamArn) { stream => + for { + now <- Utils.now + streams = Streams.empty.addStream(1, consumerArn.streamArn, None, now) + updated = streams.findAndUpdateStream(consumerArn.streamArn) { stream => stream.copy( streamStatus = StreamStatus.ACTIVE, consumers = SortedMap( consumerArn.consumerName -> Consumer - .create(stream.streamArn, consumerArn.consumerName) + .create(stream.streamArn, consumerArn.consumerName, now) .copy(consumerStatus = ConsumerStatus.ACTIVE) ) ) } - - for { streamsRef <- Ref.of[IO, Streams](updated) req = DeregisterStreamConsumerRequest( None, @@ -191,21 +179,19 @@ class DeregisterStreamConsumerTests ( consumerArn: ConsumerArn ) => - val streams = - Streams.empty.addStream(1, consumerArn.streamArn, None) - val updated = - streams.findAndUpdateStream(consumerArn.streamArn) { stream => + for { + now <- Utils.now + streams = Streams.empty.addStream(1, consumerArn.streamArn, None, now) + updated = streams.findAndUpdateStream(consumerArn.streamArn) { stream => stream.copy( streamStatus = StreamStatus.ACTIVE, consumers = SortedMap( consumerArn.consumerName -> Consumer - .create(stream.streamArn, consumerArn.consumerName) + .create(stream.streamArn, consumerArn.consumerName, now) .copy(consumerStatus = ConsumerStatus.ACTIVE) ) ) } - - for { streamsRef <- Ref.of[IO, Streams](updated) req = DeregisterStreamConsumerRequest( None, diff --git a/unit-tests/src/test/scala/kinesis/mock/api/DescribeStreamConsumerTests.scala b/unit-tests/src/test/scala/kinesis/mock/api/DescribeStreamConsumerTests.scala index 1738c1ee..60ac0808 100644 --- a/unit-tests/src/test/scala/kinesis/mock/api/DescribeStreamConsumerTests.scala +++ b/unit-tests/src/test/scala/kinesis/mock/api/DescribeStreamConsumerTests.scala @@ -33,24 +33,22 @@ class DescribeStreamConsumerTests ( consumerArn: ConsumerArn ) => - val streams = - Streams.empty.addStream(1, consumerArn.streamArn, None) - val updated = - streams.findAndUpdateStream(consumerArn.streamArn) { stream => + for { + now <- Utils.now + streams = Streams.empty.addStream(1, consumerArn.streamArn, None, now) + updated = streams.findAndUpdateStream(consumerArn.streamArn) { stream => stream.copy( streamStatus = StreamStatus.ACTIVE, consumers = SortedMap( consumerArn.consumerName -> Consumer - .create(stream.streamArn, consumerArn.consumerName) + .create(stream.streamArn, consumerArn.consumerName, now) .copy(consumerStatus = ConsumerStatus.ACTIVE) ) ) } - val consumer = updated.streams - .get(consumerArn.streamArn) - .flatMap(s => s.consumers.get(consumerArn.consumerName)) - - for { + consumer = updated.streams + .get(consumerArn.streamArn) + .flatMap(s => s.consumers.get(consumerArn.consumerName)) streamsRef <- Ref.of[IO, Streams](updated) req = DescribeStreamConsumerRequest( None, @@ -70,26 +68,23 @@ class DescribeStreamConsumerTests ( consumerArn: ConsumerArn ) => - val streams = - Streams.empty.addStream(1, consumerArn.streamArn, None) - - val updated = - streams.findAndUpdateStream(consumerArn.streamArn) { stream => + for { + now <- Utils.now + streams = + Streams.empty.addStream(1, consumerArn.streamArn, None, now) + updated = streams.findAndUpdateStream(consumerArn.streamArn) { stream => stream.copy( streamStatus = StreamStatus.ACTIVE, consumers = SortedMap( consumerArn.consumerName -> Consumer - .create(stream.streamArn, consumerArn.consumerName) + .create(stream.streamArn, consumerArn.consumerName, now) .copy(consumerStatus = ConsumerStatus.ACTIVE) ) ) } - - val consumer = updated.streams - .get(consumerArn.streamArn) - .flatMap(s => s.consumers.get(consumerArn.consumerName)) - - for { + consumer = updated.streams + .get(consumerArn.streamArn) + .flatMap(s => s.consumers.get(consumerArn.consumerName)) streamsRef <- Ref.of[IO, Streams](updated) req = DescribeStreamConsumerRequest(Some(consumerArn), None, None) res <- req.describeStreamConsumer(streamsRef) @@ -105,13 +100,10 @@ class DescribeStreamConsumerTests ( consumerArn: ConsumerArn ) => - val streams = - Streams.empty.addStream(1, consumerArn.streamArn, None) - - val streamArn = - streams.streams.get(consumerArn.streamArn).map(_.streamArn) - for { + now <- Utils.now + streams = Streams.empty.addStream(1, consumerArn.streamArn, None, now) + streamArn = streams.streams.get(consumerArn.streamArn).map(_.streamArn) streamsRef <- Ref.of[IO, Streams](streams) req = DescribeStreamConsumerRequest( None, @@ -128,21 +120,19 @@ class DescribeStreamConsumerTests ( consumerArn: ConsumerArn ) => - val streams = - Streams.empty.addStream(1, consumerArn.streamArn, None) - val updated = - streams.findAndUpdateStream(consumerArn.streamArn) { stream => + for { + now <- Utils.now + streams = Streams.empty.addStream(1, consumerArn.streamArn, None, now) + updated = streams.findAndUpdateStream(consumerArn.streamArn) { stream => stream.copy( streamStatus = StreamStatus.ACTIVE, consumers = SortedMap( consumerArn.consumerName -> Consumer - .create(stream.streamArn, consumerArn.consumerName) + .create(stream.streamArn, consumerArn.consumerName, now) .copy(consumerStatus = ConsumerStatus.ACTIVE) ) ) } - - for { streamsRef <- Ref.of[IO, Streams](updated) req = DescribeStreamConsumerRequest( None, @@ -159,21 +149,19 @@ class DescribeStreamConsumerTests ( consumerArn: ConsumerArn ) => - val streams = - Streams.empty.addStream(1, consumerArn.streamArn, None) - val updated = - streams.findAndUpdateStream(consumerArn.streamArn) { stream => + for { + now <- Utils.now + streams = Streams.empty.addStream(1, consumerArn.streamArn, None, now) + updated = streams.findAndUpdateStream(consumerArn.streamArn) { stream => stream.copy( streamStatus = StreamStatus.ACTIVE, consumers = SortedMap( consumerArn.consumerName -> Consumer - .create(stream.streamArn, consumerArn.consumerName) + .create(stream.streamArn, consumerArn.consumerName, now) .copy(consumerStatus = ConsumerStatus.ACTIVE) ) ) } - - for { streamsRef <- Ref.of[IO, Streams](updated) req = DescribeStreamConsumerRequest( None, diff --git a/unit-tests/src/test/scala/kinesis/mock/api/DescribeStreamSummaryTests.scala b/unit-tests/src/test/scala/kinesis/mock/api/DescribeStreamSummaryTests.scala index c55cccd7..3a1905c9 100644 --- a/unit-tests/src/test/scala/kinesis/mock/api/DescribeStreamSummaryTests.scala +++ b/unit-tests/src/test/scala/kinesis/mock/api/DescribeStreamSummaryTests.scala @@ -31,10 +31,9 @@ class DescribeStreamSummaryTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) streamsRef <- Ref.of[IO, Streams](streams) req = DescribeStreamSummaryRequest(None, Some(streamArn)) res <- req.describeStreamSummary( diff --git a/unit-tests/src/test/scala/kinesis/mock/api/DescribeStreamTests.scala b/unit-tests/src/test/scala/kinesis/mock/api/DescribeStreamTests.scala index 4ff7b9df..953d6e26 100644 --- a/unit-tests/src/test/scala/kinesis/mock/api/DescribeStreamTests.scala +++ b/unit-tests/src/test/scala/kinesis/mock/api/DescribeStreamTests.scala @@ -31,10 +31,9 @@ class DescribeStreamTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) streamsRef <- Ref.of[IO, Streams](streams) req = DescribeStreamRequest(None, None, None, Some(streamArn)) res <- req.describeStream( @@ -57,12 +56,10 @@ class DescribeStreamTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(2, streamArn, None) - - val limit = Some(1) - for { + now <- Utils.now + streams = Streams.empty.addStream(2, streamArn, None, now) + limit = Some(1) streamsRef <- Ref.of[IO, Streams](streams) req = DescribeStreamRequest(None, limit, None, Some(streamArn)) res <- req.describeStream( @@ -87,14 +84,12 @@ class DescribeStreamTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(4, streamArn, None) - - val exclusiveStartShardId = streams.streams - .get(streamArn) - .flatMap(_.shards.headOption.map(_._1.shardId.shardId)) - for { + now <- Utils.now + streams = Streams.empty.addStream(4, streamArn, None, now) + exclusiveStartShardId = streams.streams + .get(streamArn) + .flatMap(_.shards.headOption.map(_._1.shardId.shardId)) streamsRef <- Ref.of[IO, Streams](streams) req = DescribeStreamRequest( exclusiveStartShardId, diff --git a/unit-tests/src/test/scala/kinesis/mock/api/DisableEnhancedMonitoringTests.scala b/unit-tests/src/test/scala/kinesis/mock/api/DisableEnhancedMonitoringTests.scala index 4d7d2407..abd8114c 100644 --- a/unit-tests/src/test/scala/kinesis/mock/api/DisableEnhancedMonitoringTests.scala +++ b/unit-tests/src/test/scala/kinesis/mock/api/DisableEnhancedMonitoringTests.scala @@ -32,28 +32,26 @@ class DisableEnhancedMonitoringTests streamArn: StreamArn, shardLevelMetrics: ShardLevelMetrics ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val updated = streams.findAndUpdateStream(streamArn)(stream => - stream.copy(enhancedMonitoring = - Vector( - ShardLevelMetrics( - Vector( - ShardLevelMetric.IncomingBytes, - ShardLevelMetric.IncomingRecords, - ShardLevelMetric.OutgoingBytes, - ShardLevelMetric.OutgoingRecords, - ShardLevelMetric.WriteProvisionedThroughputExceeded, - ShardLevelMetric.ReadProvisionedThroughputExceeded, - ShardLevelMetric.IteratorAgeMilliseconds + for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + updated = streams.findAndUpdateStream(streamArn)(stream => + stream.copy(enhancedMonitoring = + Vector( + ShardLevelMetrics( + Vector( + ShardLevelMetric.IncomingBytes, + ShardLevelMetric.IncomingRecords, + ShardLevelMetric.OutgoingBytes, + ShardLevelMetric.OutgoingRecords, + ShardLevelMetric.WriteProvisionedThroughputExceeded, + ShardLevelMetric.ReadProvisionedThroughputExceeded, + ShardLevelMetric.IteratorAgeMilliseconds + ) ) ) ) ) - ) - - for { streamsRef <- Ref.of[IO, Streams](updated) req = DisableEnhancedMonitoringRequest( shardLevelMetrics.shardLevelMetrics, diff --git a/unit-tests/src/test/scala/kinesis/mock/api/EnableEnhancedMonitoringTests.scala b/unit-tests/src/test/scala/kinesis/mock/api/EnableEnhancedMonitoringTests.scala index 436502d0..575003af 100644 --- a/unit-tests/src/test/scala/kinesis/mock/api/EnableEnhancedMonitoringTests.scala +++ b/unit-tests/src/test/scala/kinesis/mock/api/EnableEnhancedMonitoringTests.scala @@ -32,10 +32,9 @@ class EnableEnhancedMonitoringTests streamArn: StreamArn, shardLevelMetrics: ShardLevelMetrics ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) streamsRef <- Ref.of[IO, Streams](streams) req = EnableEnhancedMonitoringRequest( shardLevelMetrics.shardLevelMetrics, diff --git a/unit-tests/src/test/scala/kinesis/mock/api/GetRecordsTests.scala b/unit-tests/src/test/scala/kinesis/mock/api/GetRecordsTests.scala index 7eca67d3..92eba20e 100644 --- a/unit-tests/src/test/scala/kinesis/mock/api/GetRecordsTests.scala +++ b/unit-tests/src/test/scala/kinesis/mock/api/GetRecordsTests.scala @@ -35,39 +35,35 @@ class GetRecordsTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val shard = streams.streams(streamArn).shards.head._1 - - val records: Vector[KinesisRecord] = - kinesisRecordArbitrary.arbitrary.take(100).toVector.zipWithIndex.map { - case (record, index) => - record.copy(sequenceNumber = - SequenceNumber.create( - shard.createdAtTimestamp, - shard.shardId.index, - None, - Some(index), - Some(record.approximateArrivalTimestamp) + for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + shard = streams.streams(streamArn).shards.head._1 + records = + kinesisRecordArbitrary.arbitrary.take(100).toVector.zipWithIndex.map { + case (record, index) => + record.copy(sequenceNumber = + SequenceNumber.create( + shard.createdAtTimestamp, + shard.shardId.index, + None, + Some(index), + Some(record.approximateArrivalTimestamp) + ) ) - ) + } + withRecords = streams.findAndUpdateStream(streamArn) { s => + s.copy( + shards = SortedMap(s.shards.head._1 -> records), + streamStatus = StreamStatus.ACTIVE + ) } - - val withRecords = streams.findAndUpdateStream(streamArn) { s => - s.copy( - shards = SortedMap(s.shards.head._1 -> records), - streamStatus = StreamStatus.ACTIVE + shardIterator = ShardIterator.create( + streamArn.streamName, + shard.shardId.shardId, + shard.sequenceNumberRange.startingSequenceNumber, + now ) - } - - val shardIterator = ShardIterator.create( - streamArn.streamName, - shard.shardId.shardId, - shard.sequenceNumberRange.startingSequenceNumber - ) - - for { streamsRef <- Ref.of[IO, Streams](withRecords) req = GetRecordsRequest(None, shardIterator, None) res <- req.getRecords( @@ -92,14 +88,15 @@ class GetRecordsTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val shard = streams.streams(streamArn).shards.head._1 - - val records: Vector[KinesisRecord] = - kinesisRecordArbitrary.arbitrary.take(100).toVector.zipWithIndex.map { - case (record, index) => + for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + shard = streams.streams(streamArn).shards.head._1 + records = kinesisRecordArbitrary.arbitrary + .take(100) + .toVector + .zipWithIndex + .map { case (record, index) => record.copy(sequenceNumber = SequenceNumber.create( shard.createdAtTimestamp, @@ -109,22 +106,19 @@ class GetRecordsTests Some(record.approximateArrivalTimestamp) ) ) + } + withRecords = streams.findAndUpdateStream(streamArn) { s => + s.copy( + shards = SortedMap(s.shards.head._1 -> records), + streamStatus = StreamStatus.ACTIVE + ) } - - val withRecords = streams.findAndUpdateStream(streamArn) { s => - s.copy( - shards = SortedMap(s.shards.head._1 -> records), - streamStatus = StreamStatus.ACTIVE + shardIterator = ShardIterator.create( + streamArn.streamName, + shard.shardId.shardId, + shard.sequenceNumberRange.startingSequenceNumber, + now ) - } - - val shardIterator = ShardIterator.create( - streamArn.streamName, - shard.shardId.shardId, - shard.sequenceNumberRange.startingSequenceNumber - ) - - for { streamsRef <- Ref.of[IO, Streams](withRecords) req = GetRecordsRequest(Some(50), shardIterator, None) res <- req.getRecords( @@ -149,14 +143,15 @@ class GetRecordsTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val shard = streams.streams(streamArn).shards.head._1 - - val records: Vector[KinesisRecord] = - kinesisRecordArbitrary.arbitrary.take(100).toVector.zipWithIndex.map { - case (record, index) => + for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + shard = streams.streams(streamArn).shards.head._1 + records = kinesisRecordArbitrary.arbitrary + .take(100) + .toVector + .zipWithIndex + .map { case (record, index) => record.copy(sequenceNumber = SequenceNumber.create( shard.createdAtTimestamp, @@ -166,22 +161,19 @@ class GetRecordsTests Some(record.approximateArrivalTimestamp) ) ) + } + withRecords = streams.findAndUpdateStream(streamArn) { s => + s.copy( + shards = SortedMap(s.shards.head._1 -> records), + streamStatus = StreamStatus.ACTIVE + ) } - - val withRecords = streams.findAndUpdateStream(streamArn) { s => - s.copy( - shards = SortedMap(s.shards.head._1 -> records), - streamStatus = StreamStatus.ACTIVE + shardIterator = ShardIterator.create( + streamArn.streamName, + shard.shardId.shardId, + shard.sequenceNumberRange.startingSequenceNumber, + now ) - } - - val shardIterator = ShardIterator.create( - streamArn.streamName, - shard.shardId.shardId, - shard.sequenceNumberRange.startingSequenceNumber - ) - - for { streamsRef <- Ref.of[IO, Streams](withRecords) req1 = GetRecordsRequest(Some(50), shardIterator, None) res1 <- req1.getRecords( @@ -230,14 +222,15 @@ class GetRecordsTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val shard = streams.streams(streamArn).shards.head._1 - - val records: Vector[KinesisRecord] = - kinesisRecordArbitrary.arbitrary.take(50).toVector.zipWithIndex.map { - case (record, index) => + for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + shard = streams.streams(streamArn).shards.head._1 + records = kinesisRecordArbitrary.arbitrary + .take(50) + .toVector + .zipWithIndex + .map { case (record, index) => record.copy(sequenceNumber = SequenceNumber.create( shard.createdAtTimestamp, @@ -247,22 +240,19 @@ class GetRecordsTests Some(record.approximateArrivalTimestamp) ) ) + } + withRecords = streams.findAndUpdateStream(streamArn) { s => + s.copy( + shards = SortedMap(s.shards.head._1 -> records), + streamStatus = StreamStatus.ACTIVE + ) } - - val withRecords = streams.findAndUpdateStream(streamArn) { s => - s.copy( - shards = SortedMap(s.shards.head._1 -> records), - streamStatus = StreamStatus.ACTIVE + shardIterator = ShardIterator.create( + streamArn.streamName, + shard.shardId.shardId, + shard.sequenceNumberRange.startingSequenceNumber, + now ) - } - - val shardIterator = ShardIterator.create( - streamArn.streamName, - shard.shardId.shardId, - shard.sequenceNumberRange.startingSequenceNumber - ) - - for { streamsRef <- Ref.of[IO, Streams](withRecords) req1 = GetRecordsRequest(Some(50), shardIterator, None) res1 <- req1.getRecords( @@ -297,14 +287,15 @@ class GetRecordsTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val shard = streams.streams(streamArn).shards.head._1 - - val records: Vector[KinesisRecord] = - kinesisRecordArbitrary.arbitrary.take(100).toVector.zipWithIndex.map { - case (record, index) => + for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + shard = streams.streams(streamArn).shards.head._1 + records = kinesisRecordArbitrary.arbitrary + .take(100) + .toVector + .zipWithIndex + .map { case (record, index) => record.copy(sequenceNumber = SequenceNumber.create( shard.createdAtTimestamp, @@ -314,22 +305,19 @@ class GetRecordsTests Some(record.approximateArrivalTimestamp) ) ) + } + withRecords = streams.findAndUpdateStream(streamArn) { s => + s.copy( + shards = SortedMap(s.shards.head._1 -> records), + streamStatus = StreamStatus.ACTIVE + ) } - - val withRecords = streams.findAndUpdateStream(streamArn) { s => - s.copy( - shards = SortedMap(s.shards.head._1 -> records), - streamStatus = StreamStatus.ACTIVE + shardIterator = ShardIterator.create( + streamArn.streamName, + shard.shardId.shardId, + shard.sequenceNumberRange.startingSequenceNumber, + now ) - } - - val shardIterator = ShardIterator.create( - streamArn.streamName, - shard.shardId.shardId, - shard.sequenceNumberRange.startingSequenceNumber - ) - - for { streamsRef <- Ref.of[IO, Streams](withRecords) scaleReq = UpdateShardCountRequest( ScalingType.UNIFORM_SCALING, diff --git a/unit-tests/src/test/scala/kinesis/mock/api/GetShardIteratorTests.scala b/unit-tests/src/test/scala/kinesis/mock/api/GetShardIteratorTests.scala index 0be86fda..804a249c 100644 --- a/unit-tests/src/test/scala/kinesis/mock/api/GetShardIteratorTests.scala +++ b/unit-tests/src/test/scala/kinesis/mock/api/GetShardIteratorTests.scala @@ -36,14 +36,15 @@ class GetShardIteratorTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val shard = streams.streams(streamArn).shards.head._1 - - val records: Vector[KinesisRecord] = - kinesisRecordArbitrary.arbitrary.take(50).toVector.zipWithIndex.map { - case (record, index) => + for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + shard = streams.streams(streamArn).shards.head._1 + records = kinesisRecordArbitrary.arbitrary + .take(50) + .toVector + .zipWithIndex + .map { case (record, index) => record.copy(sequenceNumber = SequenceNumber.create( shard.createdAtTimestamp, @@ -53,16 +54,13 @@ class GetShardIteratorTests Some(record.approximateArrivalTimestamp) ) ) + } + withRecords = streams.findAndUpdateStream(streamArn) { s => + s.copy( + shards = SortedMap(s.shards.head._1 -> records), + streamStatus = StreamStatus.ACTIVE + ) } - - val withRecords = streams.findAndUpdateStream(streamArn) { s => - s.copy( - shards = SortedMap(s.shards.head._1 -> records), - streamStatus = StreamStatus.ACTIVE - ) - } - - for { streamsRef <- Ref.of[IO, Streams](withRecords) req = GetShardIteratorRequest( shard.shardId.shardId, @@ -77,7 +75,7 @@ class GetShardIteratorTests streamArn.awsRegion, streamArn.awsAccountId ) - parsed = res.flatMap(_.shardIterator.parse) + parsed = res.flatMap(_.shardIterator.parse(now.plusSeconds(2))) } yield assert( parsed.isRight && parsed.exists { parts => parts.sequenceNumber == shard.sequenceNumberRange.startingSequenceNumber && @@ -92,14 +90,15 @@ class GetShardIteratorTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val shard = streams.streams(streamArn).shards.head._1 - - val records: Vector[KinesisRecord] = - kinesisRecordArbitrary.arbitrary.take(50).toVector.zipWithIndex.map { - case (record, index) => + for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + shard = streams.streams(streamArn).shards.head._1 + records = kinesisRecordArbitrary.arbitrary + .take(50) + .toVector + .zipWithIndex + .map { case (record, index) => record.copy(sequenceNumber = SequenceNumber.create( shard.createdAtTimestamp, @@ -109,16 +108,13 @@ class GetShardIteratorTests Some(record.approximateArrivalTimestamp) ) ) + } + withRecords = streams.findAndUpdateStream(streamArn) { s => + s.copy( + shards = SortedMap(s.shards.head._1 -> records), + streamStatus = StreamStatus.ACTIVE + ) } - - val withRecords = streams.findAndUpdateStream(streamArn) { s => - s.copy( - shards = SortedMap(s.shards.head._1 -> records), - streamStatus = StreamStatus.ACTIVE - ) - } - - for { streamsRef <- Ref.of[IO, Streams](withRecords) req = GetShardIteratorRequest( shard.shardId.shardId, @@ -133,7 +129,7 @@ class GetShardIteratorTests streamArn.awsRegion, streamArn.awsAccountId ) - parsed = res.flatMap(_.shardIterator.parse) + parsed = res.flatMap(_.shardIterator.parse(now.plusSeconds(2))) } yield assert( parsed.isRight && parsed.exists { parts => parts.sequenceNumber == records.last.sequenceNumber && @@ -148,37 +144,32 @@ class GetShardIteratorTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val shard = streams.streams(streamArn).shards.head._1 - - val startingInstant = Instant.parse("2021-01-01T00:00:00.00Z") - - val records: Vector[KinesisRecord] = - kinesisRecordArbitrary.arbitrary.take(50).toVector.zipWithIndex.map { - case (record, index) => - val newTimestamp = startingInstant.plusSeconds(index.toLong) - record.copy( - approximateArrivalTimestamp = newTimestamp, - sequenceNumber = SequenceNumber.create( - shard.createdAtTimestamp, - shard.shardId.index, - None, - Some(index), - Some(newTimestamp) + for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + shard = streams.streams(streamArn).shards.head._1 + startingInstant = Instant.parse("2021-01-01T00:00:00.00Z") + records = + kinesisRecordArbitrary.arbitrary.take(50).toVector.zipWithIndex.map { + case (record, index) => + val newTimestamp = startingInstant.plusSeconds(index.toLong) + record.copy( + approximateArrivalTimestamp = newTimestamp, + sequenceNumber = SequenceNumber.create( + shard.createdAtTimestamp, + shard.shardId.index, + None, + Some(index), + Some(newTimestamp) + ) ) - ) + } + withRecords = streams.findAndUpdateStream(streamArn) { s => + s.copy( + shards = SortedMap(s.shards.head._1 -> records), + streamStatus = StreamStatus.ACTIVE + ) } - - val withRecords = streams.findAndUpdateStream(streamArn) { s => - s.copy( - shards = SortedMap(s.shards.head._1 -> records), - streamStatus = StreamStatus.ACTIVE - ) - } - - for { streamsRef <- Ref.of[IO, Streams](withRecords) req = GetShardIteratorRequest( shard.shardId.shardId, @@ -193,7 +184,7 @@ class GetShardIteratorTests streamArn.awsRegion, streamArn.awsAccountId ) - parsed = res.flatMap(_.shardIterator.parse) + parsed = res.flatMap(_.shardIterator.parse(now.plusSeconds(2))) } yield assert( parsed.isRight && parsed.exists { parts => parts.sequenceNumber == records(24).sequenceNumber && @@ -212,14 +203,15 @@ class GetShardIteratorTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val shard = streams.streams(streamArn).shards.head._1 - - val records: Vector[KinesisRecord] = - kinesisRecordArbitrary.arbitrary.take(50).toVector.zipWithIndex.map { - case (record, index) => + for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + shard = streams.streams(streamArn).shards.head._1 + records = kinesisRecordArbitrary.arbitrary + .take(50) + .toVector + .zipWithIndex + .map { case (record, index) => record.copy(sequenceNumber = SequenceNumber.create( shard.createdAtTimestamp, @@ -229,16 +221,13 @@ class GetShardIteratorTests Some(record.approximateArrivalTimestamp) ) ) + } + withRecords = streams.findAndUpdateStream(streamArn) { s => + s.copy( + shards = SortedMap(s.shards.head._1 -> records), + streamStatus = StreamStatus.ACTIVE + ) } - - val withRecords = streams.findAndUpdateStream(streamArn) { s => - s.copy( - shards = SortedMap(s.shards.head._1 -> records), - streamStatus = StreamStatus.ACTIVE - ) - } - - for { streamsRef <- Ref.of[IO, Streams](withRecords) req = GetShardIteratorRequest( shard.shardId.shardId, @@ -253,7 +242,7 @@ class GetShardIteratorTests streamArn.awsRegion, streamArn.awsAccountId ) - parsed = res.flatMap(_.shardIterator.parse) + parsed = res.flatMap(_.shardIterator.parse(now.plusSeconds(2))) } yield assert( parsed.isRight && parsed.exists { parts => parts.sequenceNumber == records(24).sequenceNumber && @@ -270,14 +259,15 @@ class GetShardIteratorTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val shard = streams.streams(streamArn).shards.head._1 - - val records: Vector[KinesisRecord] = - kinesisRecordArbitrary.arbitrary.take(50).toVector.zipWithIndex.map { - case (record, index) => + for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + shard = streams.streams(streamArn).shards.head._1 + records = kinesisRecordArbitrary.arbitrary + .take(50) + .toVector + .zipWithIndex + .map { case (record, index) => record.copy(sequenceNumber = SequenceNumber.create( shard.createdAtTimestamp, @@ -287,16 +277,13 @@ class GetShardIteratorTests Some(record.approximateArrivalTimestamp) ) ) + } + withRecords = streams.findAndUpdateStream(streamArn) { s => + s.copy( + shards = SortedMap(s.shards.head._1 -> records), + streamStatus = StreamStatus.ACTIVE + ) } - - val withRecords = streams.findAndUpdateStream(streamArn) { s => - s.copy( - shards = SortedMap(s.shards.head._1 -> records), - streamStatus = StreamStatus.ACTIVE - ) - } - - for { streamsRef <- Ref.of[IO, Streams](withRecords) req = GetShardIteratorRequest( shard.shardId.shardId, @@ -311,7 +298,7 @@ class GetShardIteratorTests streamArn.awsRegion, streamArn.awsAccountId ) - parsed = res.flatMap(_.shardIterator.parse) + parsed = res.flatMap(_.shardIterator.parse(now.plusSeconds(2))) } yield assert( parsed.isRight && parsed.exists { parts => parts.sequenceNumber == shard.sequenceNumberRange.startingSequenceNumber && @@ -327,14 +314,15 @@ class GetShardIteratorTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val shard = streams.streams(streamArn).shards.head._1 - - val records: Vector[KinesisRecord] = - kinesisRecordArbitrary.arbitrary.take(50).toVector.zipWithIndex.map { - case (record, index) => + for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + shard = streams.streams(streamArn).shards.head._1 + records = kinesisRecordArbitrary.arbitrary + .take(50) + .toVector + .zipWithIndex + .map { case (record, index) => record.copy(sequenceNumber = SequenceNumber.create( shard.createdAtTimestamp, @@ -344,16 +332,13 @@ class GetShardIteratorTests Some(record.approximateArrivalTimestamp) ) ) + } + withRecords = streams.findAndUpdateStream(streamArn) { s => + s.copy( + shards = SortedMap(s.shards.head._1 -> records), + streamStatus = StreamStatus.ACTIVE + ) } - - val withRecords = streams.findAndUpdateStream(streamArn) { s => - s.copy( - shards = SortedMap(s.shards.head._1 -> records), - streamStatus = StreamStatus.ACTIVE - ) - } - - for { streamsRef <- Ref.of[IO, Streams](withRecords) req = GetShardIteratorRequest( shard.shardId.shardId, @@ -368,7 +353,7 @@ class GetShardIteratorTests streamArn.awsRegion, streamArn.awsAccountId ) - parsed = res.flatMap(_.shardIterator.parse) + parsed = res.flatMap(_.shardIterator.parse(now.plusSeconds(2))) } yield assert( parsed.isRight && parsed.exists { parts => parts.sequenceNumber == records(25).sequenceNumber && @@ -387,14 +372,15 @@ class GetShardIteratorTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val shard = streams.streams(streamArn).shards.head._1 - - val records: Vector[KinesisRecord] = - kinesisRecordArbitrary.arbitrary.take(50).toVector.zipWithIndex.map { - case (record, index) => + for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + shard = streams.streams(streamArn).shards.head._1 + records = kinesisRecordArbitrary.arbitrary + .take(50) + .toVector + .zipWithIndex + .map { case (record, index) => record.copy(sequenceNumber = SequenceNumber.create( shard.createdAtTimestamp, @@ -404,16 +390,13 @@ class GetShardIteratorTests Some(record.approximateArrivalTimestamp) ) ) + } + withRecords = streams.findAndUpdateStream(streamArn) { s => + s.copy( + shards = SortedMap(s.shards.head._1 -> records), + streamStatus = StreamStatus.ACTIVE + ) } - - val withRecords = streams.findAndUpdateStream(streamArn) { s => - s.copy( - shards = SortedMap(s.shards.head._1 -> records), - streamStatus = StreamStatus.ACTIVE - ) - } - - for { streamsRef <- Ref.of[IO, Streams](withRecords) req = GetShardIteratorRequest( shard.shardId.shardId, @@ -428,7 +411,7 @@ class GetShardIteratorTests streamArn.awsRegion, streamArn.awsAccountId ) - parsed = res.flatMap(_.shardIterator.parse) + parsed = res.flatMap(_.shardIterator.parse(now.plusSeconds(2))) } yield assert( parsed.isRight && parsed.exists { parts => parts.sequenceNumber == shard.sequenceNumberRange.startingSequenceNumber && @@ -444,12 +427,10 @@ class GetShardIteratorTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val shard = streams.streams(streamArn).shards.head._1 - for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + shard = streams.streams(streamArn).shards.head._1 streamsRef <- Ref.of[IO, Streams](streams) req = GetShardIteratorRequest( shard.shardId.shardId, @@ -472,12 +453,10 @@ class GetShardIteratorTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val shard = streams.streams(streamArn).shards.head._1 - for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + shard = streams.streams(streamArn).shards.head._1 streamsRef <- Ref.of[IO, Streams](streams) req = GetShardIteratorRequest( shard.shardId.shardId, @@ -485,7 +464,7 @@ class GetShardIteratorTests None, None, Some(streamArn), - Some(Utils.now.plusSeconds(30)) + Some(now.plusSeconds(30)) ) res <- req.getShardIterator( streamsRef, @@ -501,12 +480,10 @@ class GetShardIteratorTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val shard = streams.streams(streamArn).shards.head._1 - for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + shard = streams.streams(streamArn).shards.head._1 streamsRef <- Ref.of[IO, Streams](streams) req = GetShardIteratorRequest( shard.shardId.shardId, @@ -530,12 +507,10 @@ class GetShardIteratorTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val shard = streams.streams(streamArn).shards.head._1 - for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + shard = streams.streams(streamArn).shards.head._1 streamsRef <- Ref.of[IO, Streams](streams) req = GetShardIteratorRequest( shard.shardId.shardId, diff --git a/unit-tests/src/test/scala/kinesis/mock/api/IncreaseStreamRetentionPeriodTests.scala b/unit-tests/src/test/scala/kinesis/mock/api/IncreaseStreamRetentionPeriodTests.scala index b3556b57..923ce442 100644 --- a/unit-tests/src/test/scala/kinesis/mock/api/IncreaseStreamRetentionPeriodTests.scala +++ b/unit-tests/src/test/scala/kinesis/mock/api/IncreaseStreamRetentionPeriodTests.scala @@ -22,6 +22,7 @@ import cats.effect.{IO, Ref} import enumeratum.scalacheck._ import org.scalacheck.effect.PropF +import kinesis.mock.Utils import kinesis.mock.instances.arbitrary._ import kinesis.mock.models._ @@ -32,14 +33,12 @@ class IncreaseStreamRetentionPeriodTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val active = streams.findAndUpdateStream(streamArn)( - _.copy(streamStatus = StreamStatus.ACTIVE) - ) - for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + active = streams.findAndUpdateStream(streamArn)( + _.copy(streamStatus = StreamStatus.ACTIVE) + ) streamsRef <- Ref.of[IO, Streams](active) req = IncreaseStreamRetentionPeriodRequest(48, None, Some(streamArn)) res <- req.increaseStreamRetention( @@ -63,14 +62,12 @@ class IncreaseStreamRetentionPeriodTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val withUpdatedRetention = streams.findAndUpdateStream(streamArn)(s => - s.copy(retentionPeriod = 72.hours, streamStatus = StreamStatus.ACTIVE) - ) - for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + withUpdatedRetention = streams.findAndUpdateStream(streamArn)(s => + s.copy(retentionPeriod = 72.hours, streamStatus = StreamStatus.ACTIVE) + ) streamsRef <- Ref.of[IO, Streams](withUpdatedRetention) req = IncreaseStreamRetentionPeriodRequest(48, None, Some(streamArn)) res <- req.increaseStreamRetention( diff --git a/unit-tests/src/test/scala/kinesis/mock/api/ListShardsTests.scala b/unit-tests/src/test/scala/kinesis/mock/api/ListShardsTests.scala index 91a169a0..ad0a77d7 100644 --- a/unit-tests/src/test/scala/kinesis/mock/api/ListShardsTests.scala +++ b/unit-tests/src/test/scala/kinesis/mock/api/ListShardsTests.scala @@ -36,10 +36,9 @@ class ListShardsTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(100, streamArn, None) - for { + now <- Utils.now + streams = Streams.empty.addStream(100, streamArn, None, now) streamsRef <- Ref.of[IO, Streams](streams) req = ListShardsRequest( None, @@ -70,10 +69,9 @@ class ListShardsTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(100, streamArn, None) - for { + now <- Utils.now + streams = Streams.empty.addStream(100, streamArn, None, now) streamsRef <- Ref.of[IO, Streams](streams) req = ListShardsRequest( None, @@ -105,10 +103,9 @@ class ListShardsTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(100, streamArn, None) - for { + now <- Utils.now + streams = Streams.empty.addStream(100, streamArn, None, now) streamsRef <- Ref.of[IO, Streams](streams) req = ListShardsRequest( None, @@ -140,10 +137,9 @@ class ListShardsTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(100, streamArn, None) - for { + now <- Utils.now + streams = Streams.empty.addStream(100, streamArn, None, now) streamsRef <- Ref.of[IO, Streams](streams) req = ListShardsRequest( None, @@ -203,12 +199,10 @@ class ListShardsTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(100, streamArn, None) - - val exclusiveStartShardId = ShardId.create(10) - for { + now <- Utils.now + streams = Streams.empty.addStream(100, streamArn, None, now) + exclusiveStartShardId = ShardId.create(10) streamsRef <- Ref.of[IO, Streams](streams) req = ListShardsRequest( Some(exclusiveStartShardId.shardId), @@ -240,27 +234,25 @@ class ListShardsTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(100, streamArn, None) - - val updated = streams.findAndUpdateStream(streamArn) { s => - val shards = s.shards.toList - s.copy( - shards = SortedMap.from(shards.takeRight(95) ++ shards.take(5).map { - case (shard, recs) => - shard.copy(sequenceNumberRange = - shard.sequenceNumberRange.copy( - Some(SequenceNumber.shardEnd), - shard.sequenceNumberRange.startingSequenceNumber - ) - ) -> recs - }) - ) - } - - val filter = ShardFilter(None, None, ShardFilterType.AT_LATEST) - for { + now <- Utils.now + streams = Streams.empty.addStream(100, streamArn, None, now) + updated = streams.findAndUpdateStream(streamArn) { s => + val shards = s.shards.toList + s.copy( + shards = SortedMap.from(shards.takeRight(95) ++ shards.take(5).map { + case (shard, recs) => + shard.copy(sequenceNumberRange = + shard.sequenceNumberRange.copy( + Some(SequenceNumber.shardEnd), + shard.sequenceNumberRange.startingSequenceNumber + ) + ) -> recs + }) + ) + } + + filter = ShardFilter(None, None, ShardFilterType.AT_LATEST) streamsRef <- Ref.of[IO, Streams](updated) req = ListShardsRequest( None, @@ -295,27 +287,24 @@ class ListShardsTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(100, streamArn, None) - - val updated = streams.findAndUpdateStream(streamArn) { s => - val shards = s.shards.toList - s.copy( - shards = SortedMap.from(shards.takeRight(95) ++ shards.take(5).map { - case (shard, recs) => - shard.copy(sequenceNumberRange = - shard.sequenceNumberRange.copy( - Some(SequenceNumber.shardEnd), - shard.sequenceNumberRange.startingSequenceNumber - ) - ) -> recs - }) - ) - } - - val filter = ShardFilter(None, None, ShardFilterType.AT_TRIM_HORIZON) - for { + now <- Utils.now + streams = Streams.empty.addStream(100, streamArn, None, now) + updated = streams.findAndUpdateStream(streamArn) { s => + val shards = s.shards.toList + s.copy( + shards = SortedMap.from(shards.takeRight(95) ++ shards.take(5).map { + case (shard, recs) => + shard.copy(sequenceNumberRange = + shard.sequenceNumberRange.copy( + Some(SequenceNumber.shardEnd), + shard.sequenceNumberRange.startingSequenceNumber + ) + ) -> recs + }) + ) + } + filter = ShardFilter(None, None, ShardFilterType.AT_TRIM_HORIZON) streamsRef <- Ref.of[IO, Streams](updated) req = ListShardsRequest( None, @@ -349,30 +338,27 @@ class ListShardsTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(100, streamArn, None) - - val updated = streams.findAndUpdateStream(streamArn) { s => - val shards = s.shards.toList - s.copy(shards = - SortedMap.from(shards.takeRight(95) ++ shards.take(5).map { - case (shard, recs) => - shard.copy( - sequenceNumberRange = shard.sequenceNumberRange.copy( - Some(SequenceNumber.shardEnd), - shard.sequenceNumberRange.startingSequenceNumber - ), - closedTimestamp = Some( - Utils.now.minusSeconds(s.retentionPeriod.toSeconds + 2) - ) - ) -> recs - }) - ) - } - - val filter = ShardFilter(None, None, ShardFilterType.FROM_TRIM_HORIZON) - for { + now <- Utils.now + streams = Streams.empty.addStream(100, streamArn, None, now) + updated = streams.findAndUpdateStream(streamArn) { s => + val shards = s.shards.toList + s.copy(shards = + SortedMap.from(shards.takeRight(95) ++ shards.take(5).map { + case (shard, recs) => + shard.copy( + sequenceNumberRange = shard.sequenceNumberRange.copy( + Some(SequenceNumber.shardEnd), + shard.sequenceNumberRange.startingSequenceNumber + ), + closedTimestamp = Some( + now.minusSeconds(s.retentionPeriod.toSeconds + 2) + ) + ) -> recs + }) + ) + } + filter = ShardFilter(None, None, ShardFilterType.FROM_TRIM_HORIZON) streamsRef <- Ref.of[IO, Streams](updated) req = ListShardsRequest( None, @@ -407,15 +393,17 @@ class ListShardsTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(100, streamArn, None) - - val shards = streams.streams(streamArn).shards.keys.toVector - val shardId = shards(4).shardId - val filter = - ShardFilter(Some(shardId.shardId), None, ShardFilterType.AFTER_SHARD_ID) - for { + now <- Utils.now + streams = Streams.empty.addStream(100, streamArn, None, now) + shards = streams.streams(streamArn).shards.keys.toVector + shardId = shards(4).shardId + filter = + ShardFilter( + Some(shardId.shardId), + None, + ShardFilterType.AFTER_SHARD_ID + ) streamsRef <- Ref.of[IO, Streams](streams) req = ListShardsRequest( None, @@ -452,46 +440,43 @@ class ListShardsTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(100, streamArn, None) - - val requestTimestamp = Instant.parse("2021-01-30T00:00:00.00Z") - val updated = streams.findAndUpdateStream(streamArn) { s => - val shards = s.shards.toVector - s.copy( - streamCreationTimestamp = requestTimestamp.minusSeconds(600), - shards = SortedMap.from(shards.takeRight(90) ++ shards.take(5).map { - case (shard, recs) => + for { + now <- Utils.now + streams = Streams.empty.addStream(100, streamArn, None, now) + requestTimestamp = Instant.parse("2021-01-30T00:00:00.00Z") + updated = streams.findAndUpdateStream(streamArn) { s => + val shards = s.shards.toVector + s.copy( + streamCreationTimestamp = requestTimestamp.minusSeconds(600), + shards = SortedMap.from(shards.takeRight(90) ++ shards.take(5).map { + case (shard, recs) => + shard.copy( + sequenceNumberRange = shard.sequenceNumberRange.copy( + Some(SequenceNumber.shardEnd), + shard.sequenceNumberRange.startingSequenceNumber + ), + closedTimestamp = Some( + requestTimestamp.minusSeconds(5) + ) + ) -> recs + } ++ shards.slice(5, 10).map { case (shard, recs) => shard.copy( sequenceNumberRange = shard.sequenceNumberRange.copy( Some(SequenceNumber.shardEnd), shard.sequenceNumberRange.startingSequenceNumber ), closedTimestamp = Some( - requestTimestamp.minusSeconds(5) + requestTimestamp.plusSeconds(5) ) ) -> recs - } ++ shards.slice(5, 10).map { case (shard, recs) => - shard.copy( - sequenceNumberRange = shard.sequenceNumberRange.copy( - Some(SequenceNumber.shardEnd), - shard.sequenceNumberRange.startingSequenceNumber - ), - closedTimestamp = Some( - requestTimestamp.plusSeconds(5) - ) - ) -> recs - }) + }) + ) + } + filter = ShardFilter( + None, + Some(requestTimestamp), + ShardFilterType.FROM_TIMESTAMP ) - } - - val filter = ShardFilter( - None, - Some(requestTimestamp), - ShardFilterType.FROM_TIMESTAMP - ) - - for { streamsRef <- Ref.of[IO, Streams](updated) req = ListShardsRequest( None, @@ -525,50 +510,47 @@ class ListShardsTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(100, streamArn, None) - - val requestTimestamp = Instant.parse("2021-01-30T00:00:00.00Z") - val updated = streams.findAndUpdateStream(streamArn) { s => - val shards = s.shards.toVector - s.copy( - streamCreationTimestamp = requestTimestamp.minusSeconds(600), - shards = - SortedMap.from(shards.takeRight(90).map { case (shard, data) => - shard.copy(createdAtTimestamp = requestTimestamp) -> data - } ++ shards.take(5).map { case (shard, recs) => - shard.copy( - sequenceNumberRange = shard.sequenceNumberRange.copy( - Some(SequenceNumber.shardEnd), - shard.sequenceNumberRange.startingSequenceNumber - ), - closedTimestamp = Some( - requestTimestamp.minusSeconds(5) - ) - ) -> recs + for { + now <- Utils.now + streams = Streams.empty.addStream(100, streamArn, None, now) + requestTimestamp = Instant.parse("2021-01-30T00:00:00.00Z") + updated = streams.findAndUpdateStream(streamArn) { s => + val shards = s.shards.toVector + s.copy( + streamCreationTimestamp = requestTimestamp.minusSeconds(600), + shards = + SortedMap.from(shards.takeRight(90).map { case (shard, data) => + shard.copy(createdAtTimestamp = requestTimestamp) -> data + } ++ shards.take(5).map { case (shard, recs) => + shard.copy( + sequenceNumberRange = shard.sequenceNumberRange.copy( + Some(SequenceNumber.shardEnd), + shard.sequenceNumberRange.startingSequenceNumber + ), + closedTimestamp = Some( + requestTimestamp.minusSeconds(5) + ) + ) -> recs - } ++ shards.slice(5, 10).map { case (shard, recs) => - shard.copy( - sequenceNumberRange = shard.sequenceNumberRange.copy( - Some(SequenceNumber.shardEnd), - shard.sequenceNumberRange.startingSequenceNumber - ), - closedTimestamp = Some( - requestTimestamp.plusSeconds(5) - ), - createdAtTimestamp = requestTimestamp - ) -> recs - }) + } ++ shards.slice(5, 10).map { case (shard, recs) => + shard.copy( + sequenceNumberRange = shard.sequenceNumberRange.copy( + Some(SequenceNumber.shardEnd), + shard.sequenceNumberRange.startingSequenceNumber + ), + closedTimestamp = Some( + requestTimestamp.plusSeconds(5) + ), + createdAtTimestamp = requestTimestamp + ) -> recs + }) + ) + } + filter = ShardFilter( + None, + Some(requestTimestamp), + ShardFilterType.AT_TIMESTAMP ) - } - - val filter = ShardFilter( - None, - Some(requestTimestamp), - ShardFilterType.AT_TIMESTAMP - ) - - for { streamsRef <- Ref.of[IO, Streams](updated) req = ListShardsRequest( None, @@ -604,10 +586,9 @@ class ListShardsTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(100, streamArn, None) - for { + now <- Utils.now + streams = Streams.empty.addStream(100, streamArn, None, now) streamsRef <- Ref.of[IO, Streams](streams) req = ListShardsRequest( None, @@ -632,10 +613,9 @@ class ListShardsTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(100, streamArn, None) - for { + now <- Utils.now + streams = Streams.empty.addStream(100, streamArn, None, now) streamsRef <- Ref.of[IO, Streams](streams) req = ListShardsRequest( None, @@ -660,10 +640,9 @@ class ListShardsTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(100, streamArn, None) - for { + now <- Utils.now + streams = Streams.empty.addStream(100, streamArn, None, now) streamsRef <- Ref.of[IO, Streams](streams) req = ListShardsRequest( None, diff --git a/unit-tests/src/test/scala/kinesis/mock/api/ListStreamConsumersTests.scala b/unit-tests/src/test/scala/kinesis/mock/api/ListStreamConsumersTests.scala index 8e61b696..40bf1748 100644 --- a/unit-tests/src/test/scala/kinesis/mock/api/ListStreamConsumersTests.scala +++ b/unit-tests/src/test/scala/kinesis/mock/api/ListStreamConsumersTests.scala @@ -36,26 +36,23 @@ class ListStreamConsumersTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(100, streamArn, None) - - val consumers = SortedMap.from( - Gen - .listOfN(5, consumerArbitrary.arbitrary) - .suchThat(x => - x.groupBy(_.consumerName) - .collect { case (_, y) if y.length > 1 => x } - .isEmpty - ) - .one - .map(c => c.consumerName -> c) - ) - - val withConsumers = streams.findAndUpdateStream(streamArn)(s => - s.copy(consumers = consumers) - ) - for { + now <- Utils.now + streams = Streams.empty.addStream(100, streamArn, None, now) + consumers = SortedMap.from( + Gen + .listOfN(5, consumerArbitrary.arbitrary) + .suchThat(x => + x.groupBy(_.consumerName) + .collect { case (_, y) if y.length > 1 => x } + .isEmpty + ) + .one + .map(c => c.consumerName -> c) + ) + withConsumers = streams.findAndUpdateStream(streamArn)(s => + s.copy(consumers = consumers) + ) streamsRef <- Ref.of[IO, Streams](withConsumers) req = ListStreamConsumersRequest(None, None, streamArn, None) res <- req.listStreamConsumers(streamsRef) @@ -73,9 +70,9 @@ class ListStreamConsumersTests ( streamArn: StreamArn ) => - val streams = Streams.empty.addStream(100, streamArn, None) - for { + now <- Utils.now + streams = Streams.empty.addStream(100, streamArn, None, now) streamsRef <- Ref.of[IO, Streams](streams) req = ListStreamConsumersRequest(None, None, streamArn, None) res <- req.listStreamConsumers(streamsRef) @@ -90,25 +87,23 @@ class ListStreamConsumersTests ( streamArn: StreamArn ) => - val streams = Streams.empty.addStream(100, streamArn, None) - - val consumers = SortedMap.from( - Gen - .listOfN(10, consumerArbitrary.arbitrary) - .suchThat(x => - x.groupBy(_.consumerName) - .collect { case (_, y) if y.length > 1 => x } - .isEmpty - ) - .one - .map(c => c.consumerName -> c) - ) - - val withConsumers = streams.findAndUpdateStream(streamArn)(s => - s.copy(consumers = consumers) - ) - for { + now <- Utils.now + streams = Streams.empty.addStream(100, streamArn, None, now) + consumers = SortedMap.from( + Gen + .listOfN(10, consumerArbitrary.arbitrary) + .suchThat(x => + x.groupBy(_.consumerName) + .collect { case (_, y) if y.length > 1 => x } + .isEmpty + ) + .one + .map(c => c.consumerName -> c) + ) + withConsumers = streams.findAndUpdateStream(streamArn)(s => + s.copy(consumers = consumers) + ) streamsRef <- Ref.of[IO, Streams](withConsumers) req = ListStreamConsumersRequest(Some(5), None, streamArn, None) res <- req.listStreamConsumers(streamsRef) diff --git a/unit-tests/src/test/scala/kinesis/mock/api/ListStreamsTests.scala b/unit-tests/src/test/scala/kinesis/mock/api/ListStreamsTests.scala index 6b586266..4e3f128d 100644 --- a/unit-tests/src/test/scala/kinesis/mock/api/ListStreamsTests.scala +++ b/unit-tests/src/test/scala/kinesis/mock/api/ListStreamsTests.scala @@ -34,26 +34,26 @@ class ListStreamsTests awsRegion: AwsRegion, awsAccountId: AwsAccountId ) => - val streamNames = Gen - .listOfN(10, Arbitrary.arbitrary[StreamName]) - .suchThat(x => - x.groupBy(_.streamName) - .collect { case (_, y) if y.length > 1 => y } - .isEmpty - ) - .one - .sorted - - val streams = streamNames.foldLeft(Streams.empty) { - case (streams, streamName) => - streams.addStream( - 1, - StreamArn(awsRegion, streamName, awsAccountId), - None - ) - } - for { + now <- Utils.now + streamNames = Gen + .listOfN(10, Arbitrary.arbitrary[StreamName]) + .suchThat(x => + x.groupBy(_.streamName) + .collect { case (_, y) if y.length > 1 => y } + .isEmpty + ) + .one + .sorted + streams = streamNames.foldLeft(Streams.empty) { + case (streams, streamName) => + streams.addStream( + 1, + StreamArn(awsRegion, streamName, awsAccountId), + None, + now + ) + } streamsRef <- Ref.of[IO, Streams](streams) req = ListStreamsRequest(None, None) res <- req.listStreams(streamsRef, awsRegion, awsAccountId) @@ -70,28 +70,27 @@ class ListStreamsTests awsRegion: AwsRegion, awsAccountId: AwsAccountId ) => - val streamNames = Gen - .listOfN(10, Arbitrary.arbitrary[StreamName]) - .suchThat(x => - x.groupBy(_.streamName) - .collect { case (_, y) if y.length > 1 => y } - .isEmpty - ) - .one - .sorted - - val streams = streamNames.foldLeft(Streams.empty) { - case (streams, streamName) => - streams.addStream( - 1, - StreamArn(awsRegion, streamName, awsAccountId), - None - ) - } - - val exclusiveStartStreamName = streamNames(3) - for { + now <- Utils.now + streamNames = Gen + .listOfN(10, Arbitrary.arbitrary[StreamName]) + .suchThat(x => + x.groupBy(_.streamName) + .collect { case (_, y) if y.length > 1 => y } + .isEmpty + ) + .one + .sorted + streams = streamNames.foldLeft(Streams.empty) { + case (streams, streamName) => + streams.addStream( + 1, + StreamArn(awsRegion, streamName, awsAccountId), + None, + now + ) + } + exclusiveStartStreamName = streamNames(3) streamsRef <- Ref.of[IO, Streams](streams) req = ListStreamsRequest(Some(exclusiveStartStreamName), None) res <- req.listStreams(streamsRef, awsRegion, awsAccountId) @@ -108,26 +107,26 @@ class ListStreamsTests awsRegion: AwsRegion, awsAccountId: AwsAccountId ) => - val streamNames = Gen - .listOfN(10, Arbitrary.arbitrary[StreamName]) - .suchThat(x => - x.groupBy(_.streamName) - .collect { case (_, y) if y.length > 1 => y } - .isEmpty - ) - .one - .sorted - - val streams = streamNames.foldLeft(Streams.empty) { - case (streams, streamName) => - streams.addStream( - 1, - StreamArn(awsRegion, streamName, awsAccountId), - None - ) - } - for { + now <- Utils.now + streamNames = Gen + .listOfN(10, Arbitrary.arbitrary[StreamName]) + .suchThat(x => + x.groupBy(_.streamName) + .collect { case (_, y) if y.length > 1 => y } + .isEmpty + ) + .one + .sorted + streams = streamNames.foldLeft(Streams.empty) { + case (streams, streamName) => + streams.addStream( + 1, + StreamArn(awsRegion, streamName, awsAccountId), + None, + now + ) + } streamsRef <- Ref.of[IO, Streams](streams) req = ListStreamsRequest(None, Some(5)) res <- req.listStreams(streamsRef, awsRegion, awsAccountId) diff --git a/unit-tests/src/test/scala/kinesis/mock/api/ListTagsForStreamTests.scala b/unit-tests/src/test/scala/kinesis/mock/api/ListTagsForStreamTests.scala index 8b7f8bda..b88b0dbc 100644 --- a/unit-tests/src/test/scala/kinesis/mock/api/ListTagsForStreamTests.scala +++ b/unit-tests/src/test/scala/kinesis/mock/api/ListTagsForStreamTests.scala @@ -36,13 +36,12 @@ class ListTagsForStreamTests streamArn: StreamArn, tags: Tags ) => - val streams = - Streams.empty.addStream(100, streamArn, None) - - val withTags = - streams.findAndUpdateStream(streamArn)(s => s.copy(tags = tags)) - for { + now <- Utils.now + streams = Streams.empty.addStream(100, streamArn, None, now) + withTags = streams.findAndUpdateStream(streamArn)(s => + s.copy(tags = tags) + ) streamsRef <- Ref.of[IO, Streams](withTags) req = ListTagsForStreamRequest(None, None, None, Some(streamArn)) res <- req.listTagsForStream( @@ -62,21 +61,19 @@ class ListTagsForStreamTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(100, streamArn, None) - - val tags: Tags = Gen - .mapOfN(10, Gen.zip(tagKeyGen, tagValueGen)) - .map(x => SortedMap.from(x)) - .map(Tags.apply) - .one - - val exclusiveStartTagKey = tags.tags.keys.toVector(3) - - val withTags = - streams.findAndUpdateStream(streamArn)(s => s.copy(tags = tags)) - for { + now <- Utils.now + streams = + Streams.empty.addStream(100, streamArn, None, now) + tags = Gen + .mapOfN(10, Gen.zip(tagKeyGen, tagValueGen)) + .map(x => SortedMap.from(x)) + .map(Tags.apply) + .one + exclusiveStartTagKey = tags.tags.keys.toVector(3) + withTags = streams.findAndUpdateStream(streamArn)(s => + s.copy(tags = tags) + ) streamsRef <- Ref.of[IO, Streams](withTags) req = ListTagsForStreamRequest( Some(exclusiveStartTagKey), @@ -103,19 +100,18 @@ class ListTagsForStreamTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(100, streamArn, None) - - val tags: Tags = Gen - .mapOfN(10, Gen.zip(tagKeyGen, tagValueGen)) - .map(x => SortedMap.from(x)) - .map(Tags.apply) - .one - - val withTags = - streams.findAndUpdateStream(streamArn)(s => s.copy(tags = tags)) - for { + now <- Utils.now + streams = + Streams.empty.addStream(100, streamArn, None, now) + tags = Gen + .mapOfN(10, Gen.zip(tagKeyGen, tagValueGen)) + .map(x => SortedMap.from(x)) + .map(Tags.apply) + .one + withTags = streams.findAndUpdateStream(streamArn)(s => + s.copy(tags = tags) + ) streamsRef <- Ref.of[IO, Streams](withTags) req = ListTagsForStreamRequest(None, Some(5), None, Some(streamArn)) res <- req.listTagsForStream( diff --git a/unit-tests/src/test/scala/kinesis/mock/api/MergeShardsTests.scala b/unit-tests/src/test/scala/kinesis/mock/api/MergeShardsTests.scala index 54807a4c..82a2b9f0 100644 --- a/unit-tests/src/test/scala/kinesis/mock/api/MergeShardsTests.scala +++ b/unit-tests/src/test/scala/kinesis/mock/api/MergeShardsTests.scala @@ -31,18 +31,16 @@ class MergeShardsTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(5, streamArn, None) - val active = - streams.findAndUpdateStream(streamArn)(s => - s.copy(streamStatus = StreamStatus.ACTIVE) - ) - val shards = active.streams(streamArn).shards.keys.toVector - - val shardToMerge = shards.head - val adjacentShardToMerge = shards(1) - for { + now <- Utils.now + streams = Streams.empty.addStream(5, streamArn, None, now) + active = + streams.findAndUpdateStream(streamArn)(s => + s.copy(streamStatus = StreamStatus.ACTIVE) + ) + shards = active.streams(streamArn).shards.keys.toVector + shardToMerge = shards.head + adjacentShardToMerge = shards(1) streamsRef <- Ref.of[IO, Streams](active) req = MergeShardsRequest( adjacentShardToMerge.shardId.shardId, @@ -75,23 +73,18 @@ class MergeShardsTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(5, streamArn, None) - - val shards = streams.streams(streamArn).shards.keys.toVector - - val shardToMerge = shards.head - val adjacentShardToMerge = shards(1) - - val req = - MergeShardsRequest( + for { + now <- Utils.now + streams = Streams.empty.addStream(5, streamArn, None, now) + shards = streams.streams(streamArn).shards.keys.toVector + shardToMerge = shards.head + adjacentShardToMerge = shards(1) + req = MergeShardsRequest( adjacentShardToMerge.shardId.shardId, shardToMerge.shardId.shardId, None, Some(streamArn) ) - - for { streamsRef <- Ref.of[IO, Streams](streams) res <- req .mergeShards(streamsRef, streamArn.awsRegion, streamArn.awsAccountId) @@ -105,25 +98,22 @@ class MergeShardsTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(5, streamArn, None) - val active = streams.findAndUpdateStream(streamArn)(s => - s.copy(streamStatus = StreamStatus.ACTIVE) - ) - val shards = active.streams(streamArn).shards.keys.toVector - val shardToMerge = shards.head - val adjacentShardToMerge = - ShardId.create(shards.map(_.shardId.index).max + 1) - - val req = - MergeShardsRequest( + for { + now <- Utils.now + streams = Streams.empty.addStream(5, streamArn, None, now) + active = streams.findAndUpdateStream(streamArn)(s => + s.copy(streamStatus = StreamStatus.ACTIVE) + ) + shards = active.streams(streamArn).shards.keys.toVector + shardToMerge = shards.head + adjacentShardToMerge = + ShardId.create(shards.map(_.shardId.index).max + 1) + req = MergeShardsRequest( adjacentShardToMerge.shardId, shardToMerge.shardId.shardId, None, Some(streamArn) ) - - for { streamsRef <- Ref.of[IO, Streams](active) res <- req .mergeShards(streamsRef, streamArn.awsRegion, streamArn.awsAccountId) @@ -137,26 +127,22 @@ class MergeShardsTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(5, streamArn, None) - val active = streams.findAndUpdateStream(streamArn)(s => - s.copy(streamStatus = StreamStatus.ACTIVE) - ) - val shards = active.streams(streamArn).shards.keys.toVector - val shardToMerge = - ShardId.create(shards.map(_.shardId.index).max + 1) - - val adjacentShardToMerge = shards(1) - - val req = - MergeShardsRequest( + for { + now <- Utils.now + streams = Streams.empty.addStream(5, streamArn, None, now) + active = streams.findAndUpdateStream(streamArn)(s => + s.copy(streamStatus = StreamStatus.ACTIVE) + ) + shards = active.streams(streamArn).shards.keys.toVector + shardToMerge = + ShardId.create(shards.map(_.shardId.index).max + 1) + adjacentShardToMerge = shards(1) + req = MergeShardsRequest( adjacentShardToMerge.shardId.shardId, shardToMerge.shardId, None, Some(streamArn) ) - - for { streamsRef <- Ref.of[IO, Streams](active) res <- req .mergeShards(streamsRef, streamArn.awsRegion, streamArn.awsAccountId) @@ -170,24 +156,21 @@ class MergeShardsTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(5, streamArn, None) - val active = streams.findAndUpdateStream(streamArn)(s => - s.copy(streamStatus = StreamStatus.ACTIVE) - ) - val shards = streams.streams(streamArn).shards.keys.toVector - val shardToMerge = shards.head - val adjacentShardToMerge = shards(2) - - val req = - MergeShardsRequest( + for { + now <- Utils.now + streams = Streams.empty.addStream(5, streamArn, None, now) + active = streams.findAndUpdateStream(streamArn)(s => + s.copy(streamStatus = StreamStatus.ACTIVE) + ) + shards = streams.streams(streamArn).shards.keys.toVector + shardToMerge = shards.head + adjacentShardToMerge = shards(2) + req = MergeShardsRequest( adjacentShardToMerge.shardId.shardId, shardToMerge.shardId.shardId, None, Some(streamArn) ) - - for { streamsRef <- Ref.of[IO, Streams](active) res <- req .mergeShards(streamsRef, streamArn.awsRegion, streamArn.awsAccountId) diff --git a/unit-tests/src/test/scala/kinesis/mock/api/PutRecordTests.scala b/unit-tests/src/test/scala/kinesis/mock/api/PutRecordTests.scala index 6b7ccde8..24acb81c 100644 --- a/unit-tests/src/test/scala/kinesis/mock/api/PutRecordTests.scala +++ b/unit-tests/src/test/scala/kinesis/mock/api/PutRecordTests.scala @@ -32,19 +32,17 @@ class PutRecordTests streamArn: StreamArn, initReq: PutRecordRequest ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - val active = - streams.findAndUpdateStream(streamArn)(s => - s.copy(streamStatus = StreamStatus.ACTIVE) - ) - - val req = initReq.copy( - streamArn = Some(streamArn), - streamName = None - ) - for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + active = + streams.findAndUpdateStream(streamArn)(s => + s.copy(streamStatus = StreamStatus.ACTIVE) + ) + req = initReq.copy( + streamArn = Some(streamArn), + streamName = None + ) streamsRef <- Ref.of[IO, Streams](active) res <- req.putRecord( streamsRef, @@ -68,15 +66,13 @@ class PutRecordTests streamArn: StreamArn, initReq: PutRecordRequest ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val req = initReq.copy( - streamArn = Some(streamArn), - streamName = None - ) - for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + req = initReq.copy( + streamArn = Some(streamArn), + streamName = None + ) streamsRef <- Ref.of[IO, Streams](streams) res <- req.putRecord( streamsRef, @@ -93,25 +89,22 @@ class PutRecordTests streamArn: StreamArn, initReq: PutRecordRequest ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val updated = streams.findAndUpdateStream(streamArn)(s => - s.copy(shards = s.shards.map { case (shard, recs) => - shard.copy(sequenceNumberRange = - shard.sequenceNumberRange.copy(endingSequenceNumber = - Some(SequenceNumber.shardEnd) - ) - ) -> recs - }) - ) - - val req = initReq.copy( - streamArn = Some(streamArn), - streamName = None - ) - for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + updated = streams.findAndUpdateStream(streamArn)(s => + s.copy(shards = s.shards.map { case (shard, recs) => + shard.copy(sequenceNumberRange = + shard.sequenceNumberRange.copy(endingSequenceNumber = + Some(SequenceNumber.shardEnd) + ) + ) -> recs + }) + ) + req = initReq.copy( + streamArn = Some(streamArn), + streamName = None + ) streamsRef <- Ref.of[IO, Streams](updated) res <- req.putRecord( streamsRef, diff --git a/unit-tests/src/test/scala/kinesis/mock/api/PutRecordsTests.scala b/unit-tests/src/test/scala/kinesis/mock/api/PutRecordsTests.scala index f2db5f35..c4b466b1 100644 --- a/unit-tests/src/test/scala/kinesis/mock/api/PutRecordsTests.scala +++ b/unit-tests/src/test/scala/kinesis/mock/api/PutRecordsTests.scala @@ -33,30 +33,27 @@ class PutRecordsTests streamArn: StreamArn, initReq: PutRecordsRequest ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - val active = - streams.findAndUpdateStream(streamArn)(s => - s.copy(streamStatus = StreamStatus.ACTIVE) - ) - - val req = initReq.copy( - streamArn = Some(streamArn), - streamName = None - ) - - val predictedShards = req.records.map(entry => - CommonValidations - .computeShard( - entry.partitionKey, - entry.explicitHashKey, - active.streams(streamArn) - ) - .toOption - .map(_._1.shardId.shardId) - ) - for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + active = + streams.findAndUpdateStream(streamArn)(s => + s.copy(streamStatus = StreamStatus.ACTIVE) + ) + req = initReq.copy( + streamArn = Some(streamArn), + streamName = None + ) + predictedShards = req.records.map(entry => + CommonValidations + .computeShard( + entry.partitionKey, + entry.explicitHashKey, + active.streams(streamArn) + ) + .toOption + .map(_._1.shardId.shardId) + ) streamsRef <- Ref.of[IO, Streams](active) res <- req.putRecords( streamsRef, @@ -82,15 +79,13 @@ class PutRecordsTests streamArn: StreamArn, initReq: PutRecordsRequest ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val req = initReq.copy( - streamArn = Some(streamArn), - streamName = None - ) - for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + req = initReq.copy( + streamArn = Some(streamArn), + streamName = None + ) streamsRef <- Ref.of[IO, Streams](streams) res <- req.putRecords( streamsRef, @@ -107,25 +102,22 @@ class PutRecordsTests streamArn: StreamArn, initReq: PutRecordsRequest ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val updated = streams.findAndUpdateStream(streamArn)(s => - s.copy(shards = s.shards.map { case (shard, recs) => - shard.copy(sequenceNumberRange = - shard.sequenceNumberRange.copy(endingSequenceNumber = - Some(SequenceNumber.shardEnd) - ) - ) -> recs - }) - ) - - val req = initReq.copy( - streamArn = Some(streamArn), - streamName = None - ) - for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + updated = streams.findAndUpdateStream(streamArn)(s => + s.copy(shards = s.shards.map { case (shard, recs) => + shard.copy(sequenceNumberRange = + shard.sequenceNumberRange.copy(endingSequenceNumber = + Some(SequenceNumber.shardEnd) + ) + ) -> recs + }) + ) + req = initReq.copy( + streamArn = Some(streamArn), + streamName = None + ) streamsRef <- Ref.of[IO, Streams](updated) res <- req .putRecords(streamsRef, streamArn.awsRegion, streamArn.awsAccountId) diff --git a/unit-tests/src/test/scala/kinesis/mock/api/RegisterStreamConsumerTests.scala b/unit-tests/src/test/scala/kinesis/mock/api/RegisterStreamConsumerTests.scala index 651e625a..8b9ed29e 100644 --- a/unit-tests/src/test/scala/kinesis/mock/api/RegisterStreamConsumerTests.scala +++ b/unit-tests/src/test/scala/kinesis/mock/api/RegisterStreamConsumerTests.scala @@ -35,10 +35,9 @@ class RegisterStreamConsumerTests ( consumerArn: ConsumerArn ) => - val streams = - Streams.empty.addStream(1, consumerArn.streamArn, None) - for { + now <- Utils.now + streams = Streams.empty.addStream(1, consumerArn.streamArn, None, now) streamsRef <- Ref.of[IO, Streams](streams) req = RegisterStreamConsumerRequest( consumerArn.consumerName, @@ -58,26 +57,23 @@ class RegisterStreamConsumerTests ( consumerArn: ConsumerArn ) => - val streams = - Streams.empty.addStream(1, consumerArn.streamArn, None) - - val consumers = SortedMap.from( - Gen - .listOfN(20, consumerArbitrary.arbitrary) - .suchThat(x => - x.groupBy(_.consumerName) - .collect { case (_, y) if y.length > 1 => x } - .isEmpty - ) - .one - .map(c => c.consumerName -> c) - ) - - val updated = streams.findAndUpdateStream(consumerArn.streamArn)(s => - s.copy(consumers = consumers) - ) - for { + now <- Utils.now + streams = Streams.empty.addStream(1, consumerArn.streamArn, None, now) + consumers = SortedMap.from( + Gen + .listOfN(20, consumerArbitrary.arbitrary) + .suchThat(x => + x.groupBy(_.consumerName) + .collect { case (_, y) if y.length > 1 => x } + .isEmpty + ) + .one + .map(c => c.consumerName -> c) + ) + updated = streams.findAndUpdateStream(consumerArn.streamArn)(s => + s.copy(consumers = consumers) + ) streamsRef <- Ref.of[IO, Streams](updated) req = RegisterStreamConsumerRequest( consumerArn.consumerName, @@ -92,27 +88,24 @@ class RegisterStreamConsumerTests ( consumerArn: ConsumerArn ) => - val streams = - Streams.empty.addStream(1, consumerArn.streamArn, None) - - val consumers = SortedMap.from( - Gen - .listOfN(5, consumerArbitrary.arbitrary) - .suchThat(x => - x.groupBy(_.consumerName) - .collect { case (_, y) if y.length > 1 => x } - .isEmpty - ) - .map(_.map(c => c.copy(consumerStatus = ConsumerStatus.CREATING))) - .one - .map(c => c.consumerName -> c) - ) - - val updated = streams.findAndUpdateStream(consumerArn.streamArn)(s => - s.copy(consumers = consumers) - ) - for { + now <- Utils.now + streams = Streams.empty.addStream(1, consumerArn.streamArn, None, now) + consumers = SortedMap.from( + Gen + .listOfN(5, consumerArbitrary.arbitrary) + .suchThat(x => + x.groupBy(_.consumerName) + .collect { case (_, y) if y.length > 1 => x } + .isEmpty + ) + .map(_.map(c => c.copy(consumerStatus = ConsumerStatus.CREATING))) + .one + .map(c => c.consumerName -> c) + ) + updated = streams.findAndUpdateStream(consumerArn.streamArn)(s => + s.copy(consumers = consumers) + ) streamsRef <- Ref.of[IO, Streams](updated) req = RegisterStreamConsumerRequest( consumerArn.consumerName, diff --git a/unit-tests/src/test/scala/kinesis/mock/api/RemoveTagsFromStreamTests.scala b/unit-tests/src/test/scala/kinesis/mock/api/RemoveTagsFromStreamTests.scala index d757316f..16b44326 100644 --- a/unit-tests/src/test/scala/kinesis/mock/api/RemoveTagsFromStreamTests.scala +++ b/unit-tests/src/test/scala/kinesis/mock/api/RemoveTagsFromStreamTests.scala @@ -35,21 +35,16 @@ class RemoveTagsFromStreamTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val tags: Tags = Gen - .mapOfN(10, Gen.zip(tagKeyGen, tagValueGen)) - .map(x => SortedMap.from(x)) - .map(Tags.apply) - .one - - val withTags = - streams.findAndUpdateStream(streamArn)(_.copy(tags = tags)) - - val removedTags = tags.tags.keys.take(3).toVector - for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + tags = Gen + .mapOfN(10, Gen.zip(tagKeyGen, tagValueGen)) + .map(x => SortedMap.from(x)) + .map(Tags.apply) + .one + withTags = streams.findAndUpdateStream(streamArn)(_.copy(tags = tags)) + removedTags = tags.tags.keys.take(3).toVector streamsRef <- Ref.of[IO, Streams](withTags) req = RemoveTagsFromStreamRequest(None, Some(streamArn), removedTags) res <- req.removeTagsFromStream( diff --git a/unit-tests/src/test/scala/kinesis/mock/api/SplitShardTests.scala b/unit-tests/src/test/scala/kinesis/mock/api/SplitShardTests.scala index c0151902..c1b71223 100644 --- a/unit-tests/src/test/scala/kinesis/mock/api/SplitShardTests.scala +++ b/unit-tests/src/test/scala/kinesis/mock/api/SplitShardTests.scala @@ -33,32 +33,26 @@ class SplitShardTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(5, streamArn, None) - val active = - streams.findAndUpdateStream(streamArn)(s => + for { + now <- Utils.now + streams = Streams.empty.addStream(5, streamArn, None, now) + active = streams.findAndUpdateStream(streamArn)(s => s.copy(streamStatus = StreamStatus.ACTIVE) ) - val shardToSplit = - active.streams(streamArn).shards.keys.head - - val newStartingHashKey = Gen - .choose( - shardToSplit.hashKeyRange.startingHashKey + BigInt(1), - shardToSplit.hashKeyRange.endingHashKey - BigInt(1) - ) - .one - .toString - - val req = - SplitShardRequest( + shardToSplit = active.streams(streamArn).shards.keys.head + newStartingHashKey = Gen + .choose( + shardToSplit.hashKeyRange.startingHashKey + BigInt(1), + shardToSplit.hashKeyRange.endingHashKey - BigInt(1) + ) + .one + .toString + req = SplitShardRequest( newStartingHashKey, shardToSplit.shardId.shardId, None, Some(streamArn) ) - - for { streamsRef <- Ref.of[IO, Streams](active) res <- req.splitShard( streamsRef, @@ -82,29 +76,23 @@ class SplitShardTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(5, streamArn, None) - - val shardToSplit = - streams.streams(streamArn).shards.keys.head - - val newStartingHashKey = Gen - .choose( - shardToSplit.hashKeyRange.startingHashKey + BigInt(1), - shardToSplit.hashKeyRange.endingHashKey - BigInt(1) - ) - .one - .toString - - val req = - SplitShardRequest( + for { + now <- Utils.now + streams = Streams.empty.addStream(5, streamArn, None, now) + shardToSplit = streams.streams(streamArn).shards.keys.head + newStartingHashKey = Gen + .choose( + shardToSplit.hashKeyRange.startingHashKey + BigInt(1), + shardToSplit.hashKeyRange.endingHashKey - BigInt(1) + ) + .one + .toString + req = SplitShardRequest( newStartingHashKey, shardToSplit.shardId.shardId, None, Some(streamArn) ) - - for { streamsRef <- Ref.of[IO, Streams](streams) res <- req.splitShard( streamsRef, @@ -119,13 +107,13 @@ class SplitShardTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(5, streamArn, None) - val active = streams.findAndUpdateStream(streamArn)(s => - s.copy(streamStatus = StreamStatus.ACTIVE) - ) - val shardToSplit = - ShardId.create( + for { + now <- Utils.now + streams = Streams.empty.addStream(5, streamArn, None, now) + active = streams.findAndUpdateStream(streamArn)(s => + s.copy(streamStatus = StreamStatus.ACTIVE) + ) + shardToSplit = ShardId.create( active .streams(streamArn) .shards @@ -134,16 +122,12 @@ class SplitShardTests .map(_.shardId.index) .max + 1 ) - - val req = - SplitShardRequest( + req = SplitShardRequest( "0", shardToSplit.shardId, None, Some(streamArn) ) - - for { streamsRef <- Ref.of[IO, Streams](active) res <- req.splitShard( streamsRef, @@ -159,32 +143,26 @@ class SplitShardTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(5, streamArn, None) - val active = - streams.findAndUpdateStream(streamArn)(s => + for { + now <- Utils.now + streams = Streams.empty.addStream(5, streamArn, None, now) + active = streams.findAndUpdateStream(streamArn)(s => s.copy(streamStatus = StreamStatus.ACTIVE) ) - val shardToSplit = - active.streams(streamArn).shards.keys.head - - val newStartingHashKey = Gen - .choose( - shardToSplit.hashKeyRange.startingHashKey + BigInt(1), - shardToSplit.hashKeyRange.endingHashKey - BigInt(1) - ) - .one - .toString - - val req = - SplitShardRequest( + shardToSplit = active.streams(streamArn).shards.keys.head + newStartingHashKey = Gen + .choose( + shardToSplit.hashKeyRange.startingHashKey + BigInt(1), + shardToSplit.hashKeyRange.endingHashKey - BigInt(1) + ) + .one + .toString + req = SplitShardRequest( newStartingHashKey, shardToSplit.shardId.shardId, None, Some(streamArn) ) - - for { streamsRef <- Ref.of[IO, Streams](active) res <- req.splitShard( streamsRef, diff --git a/unit-tests/src/test/scala/kinesis/mock/api/StartStreamEncryptionTests.scala b/unit-tests/src/test/scala/kinesis/mock/api/StartStreamEncryptionTests.scala index 200e6001..c68f234d 100644 --- a/unit-tests/src/test/scala/kinesis/mock/api/StartStreamEncryptionTests.scala +++ b/unit-tests/src/test/scala/kinesis/mock/api/StartStreamEncryptionTests.scala @@ -14,7 +14,8 @@ * limitations under the License. */ -package kinesis.mock.api +package kinesis.mock +package api import cats.effect.{IO, Ref} import enumeratum.scalacheck._ @@ -31,16 +32,13 @@ class StartStreamEncryptionTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val asActive = streams.findAndUpdateStream(streamArn)(x => - x.copy(streamStatus = StreamStatus.ACTIVE) - ) - - val keyId = keyIdGen.one - for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + asActive = streams.findAndUpdateStream(streamArn)(x => + x.copy(streamStatus = StreamStatus.ACTIVE) + ) + keyId = keyIdGen.one streamsRef <- Ref.of[IO, Streams](asActive) req = StartStreamEncryptionRequest( EncryptionType.KMS, @@ -71,16 +69,13 @@ class StartStreamEncryptionTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val asActive = streams.findAndUpdateStream(streamArn)(x => - x.copy(streamStatus = StreamStatus.ACTIVE) - ) - - val keyId = keyIdGen.one - for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + asActive = streams.findAndUpdateStream(streamArn)(x => + x.copy(streamStatus = StreamStatus.ACTIVE) + ) + keyId = keyIdGen.one streamsRef <- Ref.of[IO, Streams](asActive) req = StartStreamEncryptionRequest( EncryptionType.NONE, @@ -104,12 +99,10 @@ class StartStreamEncryptionTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val keyId = keyIdGen.one - for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + keyId = keyIdGen.one streamsRef <- Ref.of[IO, Streams](streams) req = StartStreamEncryptionRequest( EncryptionType.KMS, diff --git a/unit-tests/src/test/scala/kinesis/mock/api/StopStreamEncryptionTests.scala b/unit-tests/src/test/scala/kinesis/mock/api/StopStreamEncryptionTests.scala index d93aa5c8..c2b096db 100644 --- a/unit-tests/src/test/scala/kinesis/mock/api/StopStreamEncryptionTests.scala +++ b/unit-tests/src/test/scala/kinesis/mock/api/StopStreamEncryptionTests.scala @@ -14,7 +14,8 @@ * limitations under the License. */ -package kinesis.mock.api +package kinesis.mock +package api import cats.effect.{IO, Ref} import enumeratum.scalacheck._ @@ -31,16 +32,13 @@ class StopStreamEncryptionTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val asActive = streams.findAndUpdateStream(streamArn)(x => - x.copy(streamStatus = StreamStatus.ACTIVE) - ) - - val keyId = keyIdGen.one - for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + asActive = streams.findAndUpdateStream(streamArn)(x => + x.copy(streamStatus = StreamStatus.ACTIVE) + ) + keyId = keyIdGen.one streamsRef <- Ref.of[IO, Streams](asActive) req = StopStreamEncryptionRequest( EncryptionType.KMS, @@ -71,16 +69,13 @@ class StopStreamEncryptionTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val asActive = streams.findAndUpdateStream(streamArn)(x => - x.copy(streamStatus = StreamStatus.ACTIVE) - ) - - val keyId = keyIdGen.one - for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + asActive = streams.findAndUpdateStream(streamArn)(x => + x.copy(streamStatus = StreamStatus.ACTIVE) + ) + keyId = keyIdGen.one streamsRef <- Ref.of[IO, Streams](asActive) req = StopStreamEncryptionRequest( EncryptionType.NONE, @@ -104,12 +99,10 @@ class StopStreamEncryptionTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val keyId = keyIdGen.one - for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + keyId = keyIdGen.one streamsRef <- Ref.of[IO, Streams](streams) req = StopStreamEncryptionRequest( EncryptionType.KMS, diff --git a/unit-tests/src/test/scala/kinesis/mock/api/UpdateShardCountTests.scala b/unit-tests/src/test/scala/kinesis/mock/api/UpdateShardCountTests.scala index 658fd3a4..b2154806 100644 --- a/unit-tests/src/test/scala/kinesis/mock/api/UpdateShardCountTests.scala +++ b/unit-tests/src/test/scala/kinesis/mock/api/UpdateShardCountTests.scala @@ -31,23 +31,20 @@ class UpdateShardCountTests ( streamArn: StreamArn ) => - val initialShardCount = 5 - val streams = - Streams.empty.addStream(initialShardCount, streamArn, None) - val active = - streams.findAndUpdateStream(streamArn)(s => + for { + now <- Utils.now + initialShardCount = 5 + streams = Streams.empty + .addStream(initialShardCount, streamArn, None, now) + active = streams.findAndUpdateStream(streamArn)(s => s.copy(streamStatus = StreamStatus.ACTIVE) ) - - val req = - UpdateShardCountRequest( + req = UpdateShardCountRequest( ScalingType.UNIFORM_SCALING, None, Some(streamArn), 10 ) - - for { streamsRef <- Ref.of[IO, Streams](active) res <- req.updateShardCount( streamsRef, @@ -85,23 +82,20 @@ class UpdateShardCountTests ( streamArn: StreamArn ) => - val initialShardCount = 10 - val streams = - Streams.empty.addStream(initialShardCount, streamArn, None) - val active = - streams.findAndUpdateStream(streamArn)(s => + for { + now <- Utils.now + initialShardCount = 10 + streams = Streams.empty + .addStream(initialShardCount, streamArn, None, now) + active = streams.findAndUpdateStream(streamArn)(s => s.copy(streamStatus = StreamStatus.ACTIVE) ) - - val req = - UpdateShardCountRequest( + req = UpdateShardCountRequest( ScalingType.UNIFORM_SCALING, None, Some(streamArn), 5 ) - - for { streamsRef <- Ref.of[IO, Streams](active) res <- req.updateShardCount( streamsRef, @@ -141,26 +135,23 @@ class UpdateShardCountTests ( streamArn: StreamArn ) => - val initialShardCount = 10 - val streams = - Streams.empty.addStream(initialShardCount, streamArn, None) - val active: Streams = - streams.findAndUpdateStream(streamArn)(s => + for { + now <- Utils.now + initialShardCount = 10 + streams = Streams.empty + .addStream(initialShardCount, streamArn, None, now) + active = streams.findAndUpdateStream(streamArn)(s => s.copy(streamStatus = StreamStatus.ACTIVE) ) - - val firstRoundShardCount = 6 - val req = - UpdateShardCountRequest( + firstRoundShardCount = 6 + req = UpdateShardCountRequest( ScalingType.UNIFORM_SCALING, None, Some(streamArn), firstRoundShardCount ) - val finalShardCount = req.targetShardCount / 2 - val req2 = req.copy(targetShardCount = finalShardCount) - - for { + finalShardCount = req.targetShardCount / 2 + req2 = req.copy(targetShardCount = finalShardCount) streamsRef <- Ref.of[IO, Streams](active) res <- req.updateShardCount( streamsRef, @@ -235,26 +226,23 @@ class UpdateShardCountTests ( streamArn: StreamArn ) => - val initialShardCount = 10 - val streams = - Streams.empty.addStream(initialShardCount, streamArn, None) - val active = - streams.findAndUpdateStream(streamArn)(s => + for { + now <- Utils.now + initialShardCount = 10 + streams = Streams.empty + .addStream(initialShardCount, streamArn, None, now) + active = streams.findAndUpdateStream(streamArn)(s => s.copy(streamStatus = StreamStatus.ACTIVE) ) - - val firstRoundShardCount = 20 - val req = - UpdateShardCountRequest( + firstRoundShardCount = 20 + req = UpdateShardCountRequest( ScalingType.UNIFORM_SCALING, None, Some(streamArn), firstRoundShardCount ) - val finalShardCount = req.targetShardCount * 2 - val req2 = req.copy(targetShardCount = finalShardCount) - - for { + finalShardCount = req.targetShardCount * 2 + req2 = req.copy(targetShardCount = finalShardCount) streamsRef <- Ref.of[IO, Streams](active) res <- req.updateShardCount( streamsRef, @@ -329,22 +317,18 @@ class UpdateShardCountTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(5, streamArn, None) - val active = - streams.findAndUpdateStream(streamArn)(s => + for { + now <- Utils.now + streams = Streams.empty.addStream(5, streamArn, None, now) + active = streams.findAndUpdateStream(streamArn)(s => s.copy(streamStatus = StreamStatus.ACTIVE) ) - - val req = - UpdateShardCountRequest( + req = UpdateShardCountRequest( ScalingType.UNIFORM_SCALING, None, Some(streamArn), 11 ) - - for { streamsRef <- Ref.of[IO, Streams](active) res <- req.updateShardCount( streamsRef, @@ -361,22 +345,18 @@ class UpdateShardCountTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(10, streamArn, None) - val active = - streams.findAndUpdateStream(streamArn)(s => + for { + now <- Utils.now + streams = Streams.empty.addStream(10, streamArn, None, now) + active = streams.findAndUpdateStream(streamArn)(s => s.copy(streamStatus = StreamStatus.ACTIVE) ) - - val req = - UpdateShardCountRequest( + req = UpdateShardCountRequest( ScalingType.UNIFORM_SCALING, None, Some(streamArn), 4 ) - - for { streamsRef <- Ref.of[IO, Streams](active) res <- req.updateShardCount( streamsRef, @@ -392,22 +372,18 @@ class UpdateShardCountTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(5, streamArn, None) - val active = - streams.findAndUpdateStream(streamArn)(s => + for { + now <- Utils.now + streams = Streams.empty.addStream(5, streamArn, None, now) + active = streams.findAndUpdateStream(streamArn)(s => s.copy(streamStatus = StreamStatus.ACTIVE) ) - - val req = - UpdateShardCountRequest( + req = UpdateShardCountRequest( ScalingType.UNIFORM_SCALING, None, Some(streamArn), 10 ) - - for { streamsRef <- Ref.of[IO, Streams](active) res <- req.updateShardCount( streamsRef, @@ -422,18 +398,15 @@ class UpdateShardCountTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(5, streamArn, None) - - val req = - UpdateShardCountRequest( + for { + now <- Utils.now + streams = Streams.empty.addStream(5, streamArn, None, now) + req = UpdateShardCountRequest( ScalingType.UNIFORM_SCALING, None, Some(streamArn), 10 ) - - for { streamsRef <- Ref.of[IO, Streams](streams) res <- req.updateShardCount( streamsRef, diff --git a/unit-tests/src/test/scala/kinesis/mock/api/UpdateStreamModeTests.scala b/unit-tests/src/test/scala/kinesis/mock/api/UpdateStreamModeTests.scala index e39ac0b6..8f5f46c7 100644 --- a/unit-tests/src/test/scala/kinesis/mock/api/UpdateStreamModeTests.scala +++ b/unit-tests/src/test/scala/kinesis/mock/api/UpdateStreamModeTests.scala @@ -31,15 +31,12 @@ class UpdateStreamModeTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val active: Streams = - streams.findAndUpdateStream(streamArn)(s => + for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + active = streams.findAndUpdateStream(streamArn)(s => s.copy(streamStatus = StreamStatus.ACTIVE) ) - - for { streamsRef <- Ref.of[IO, Streams](active) streamModeDetails = StreamModeDetails(StreamMode.ON_DEMAND) req = UpdateStreamModeRequest(streamArn, streamModeDetails) @@ -58,15 +55,12 @@ class UpdateStreamModeTests ( streamArn: StreamArn ) => - val streams = - Streams.empty.addStream(1, streamArn, None) - - val active: Streams = - streams.findAndUpdateStream(streamArn)(s => + for { + now <- Utils.now + streams = Streams.empty.addStream(1, streamArn, None, now) + active = streams.findAndUpdateStream(streamArn)(s => s.copy(streamStatus = StreamStatus.ACTIVE) ) - - for { streamsRef <- Ref.of[IO, Streams](active) streamModeDetails = StreamModeDetails(StreamMode.ON_DEMAND) req = UpdateStreamModeRequest(streamArn, streamModeDetails) diff --git a/unit-tests/src/test/scala/kinesis/mock/cache/GetShardIteratorTests.scala b/unit-tests/src/test/scala/kinesis/mock/cache/GetShardIteratorTests.scala index ded2bc04..c3857c7e 100644 --- a/unit-tests/src/test/scala/kinesis/mock/cache/GetShardIteratorTests.scala +++ b/unit-tests/src/test/scala/kinesis/mock/cache/GetShardIteratorTests.scala @@ -24,6 +24,7 @@ import org.scalacheck.Test import org.scalacheck.effect.PropF import kinesis.mock.LoggingContext +import kinesis.mock.Utils import kinesis.mock.api._ import kinesis.mock.instances.arbitrary._ import kinesis.mock.models._ @@ -86,8 +87,9 @@ class GetShardIteratorTests ) .rethrow .map(_.shardIterator) + now <- Utils.now } yield assert( - res.parse.isRight, + res.parse(now).isRight, s"$res" ) }) diff --git a/unit-tests/src/test/scala/kinesis/mock/models/ConsumerArnSpec.scala b/unit-tests/src/test/scala/kinesis/mock/models/ConsumerArnSpec.scala index b7135a8b..ed2bc91a 100644 --- a/unit-tests/src/test/scala/kinesis/mock/models/ConsumerArnSpec.scala +++ b/unit-tests/src/test/scala/kinesis/mock/models/ConsumerArnSpec.scala @@ -17,38 +17,46 @@ package kinesis.mock.models import enumeratum.scalacheck._ -import org.scalacheck.Prop._ +import org.scalacheck.effect.PropF import kinesis.mock.Utils import kinesis.mock.instances.arbitrary._ -class ConsumerArnSpec extends munit.ScalaCheckSuite { - property("It should convert to a proper ARN format")(forAll { +class ConsumerArnSpec extends munit.ScalaCheckEffectSuite { + test("It should convert to a proper ARN format")(PropF.forAllF { ( streamArn: StreamArn, consumerName: ConsumerName ) => - val creationTime = Utils.now - val consumerArn = ConsumerArn(streamArn, consumerName, creationTime) + Utils.now.map { now => + val consumerArn = ConsumerArn(streamArn, consumerName, now) - val expected = - s"arn:${streamArn.awsRegion.awsArnPiece}:kinesis:${streamArn.awsRegion.entryName}:${streamArn.awsAccountId}:stream/${streamArn.streamName}/consumer/$consumerName:${creationTime.getEpochSecond}" + val expected = + s"arn:${streamArn.awsRegion.awsArnPiece}:kinesis:${streamArn.awsRegion.entryName}:${streamArn.awsAccountId}:stream/${streamArn.streamName}/consumer/$consumerName:${now.getEpochSecond}" - (consumerArn.consumerArn == expected) :| s"Calculated: ${consumerArn}\nExpected: ${expected}" + assert( + consumerArn.consumerArn == expected, + s"Calculated: ${consumerArn}\nExpected: ${expected}" + ) + } }) - property("It should be constructed by an ARN string")(forAll { + test("It should be constructed by an ARN string")(PropF.forAllF { ( streamArn: StreamArn, consumerName: ConsumerName ) => - val creationTime = Utils.now - val expected = - s"arn:${streamArn.awsRegion.awsArnPiece}:kinesis:${streamArn.awsRegion.entryName}:${streamArn.awsAccountId}:stream/${streamArn.streamName}/consumer/$consumerName:${creationTime.getEpochSecond}" - val consumerArn = ConsumerArn.fromArn(expected) - - (consumerArn.exists( - _.consumerArn == expected - )) :| s"Calculated: ${consumerArn}\nExpected: ${expected}\n" + Utils.now.map { now => + val expected = + s"arn:${streamArn.awsRegion.awsArnPiece}:kinesis:${streamArn.awsRegion.entryName}:${streamArn.awsAccountId}:stream/${streamArn.streamName}/consumer/$consumerName:${now.getEpochSecond}" + val consumerArn = ConsumerArn.fromArn(expected) + + assert( + consumerArn.exists( + _.consumerArn == expected + ), + s"Calculated: ${consumerArn}\nExpected: ${expected}\n" + ) + } }) } diff --git a/unit-tests/src/test/scala/kinesis/mock/models/ShardIteratorTests.scala b/unit-tests/src/test/scala/kinesis/mock/models/ShardIteratorTests.scala index 8b46db59..c67469e0 100644 --- a/unit-tests/src/test/scala/kinesis/mock/models/ShardIteratorTests.scala +++ b/unit-tests/src/test/scala/kinesis/mock/models/ShardIteratorTests.scala @@ -16,32 +16,39 @@ package kinesis.mock.models -import org.scalacheck.Prop._ +import org.scalacheck.effect.PropF +import kinesis.mock.Utils import kinesis.mock.instances.arbitrary._ -class ShardIteratorTests extends munit.ScalaCheckSuite { - property("It should createt and parse correctly")(forAll { +class ShardIteratorTests extends munit.ScalaCheckEffectSuite { + test("It should createt and parse correctly")(PropF.forAllF { ( streamName: StreamName, shardId: ShardId, sequenceNumber: SequenceNumber ) => - val iterator: ShardIterator = ShardIterator.create( - streamName, - shardId.shardId, - sequenceNumber - ) - val parsed = iterator.parse + Utils.now.map { now => + val iterator: ShardIterator = ShardIterator.create( + streamName, + shardId.shardId, + sequenceNumber, + now + ) + val parsed = iterator.parse(now) - parsed.exists { parts => - parts.sequenceNumber == sequenceNumber && - parts.shardId == shardId.shardId && - parts.streamName == streamName - } :| s"streamName: $streamName\n" + - s"shardId: $shardId\n" + - s"sequenceNumber: $sequenceNumber\n" + - s"shardIterator: $iterator\n" + - s"parsed: $parsed" + assert( + parsed.exists { parts => + parts.sequenceNumber == sequenceNumber && + parts.shardId == shardId.shardId && + parts.streamName == streamName + }, + s"streamName: $streamName\n" + + s"shardId: $shardId\n" + + s"sequenceNumber: $sequenceNumber\n" + + s"shardIterator: $iterator\n" + + s"parsed: $parsed" + ) + } }) }