• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

input-output-hk / atala-prism-building-blocks / 8795121910

23 Apr 2024 04:45AM UTC coverage: 31.908% (+0.4%) from 31.528%
8795121910

Pull #975

CryptoKnightIOG
feat: VC Verification test coverage (#972)

Signed-off-by: Bassam Riman <bassam.riman@iohk.io>
Pull Request #975: feat: Vc Verification Api

104 of 281 new or added lines in 15 files covered. (37.01%)

379 existing lines in 106 files now uncovered.

4619 of 14476 relevant lines covered (31.91%)

0.32 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

47.01
/connect/lib/core/src/main/scala/io/iohk/atala/connect/core/service/ConnectionServiceImpl.scala
1
package io.iohk.atala.connect.core.service
2

3
import io.iohk.atala.connect.core.model.ConnectionRecord
4
import io.iohk.atala.connect.core.model.ConnectionRecord.*
5
import io.iohk.atala.connect.core.model.error.ConnectionServiceError
6
import io.iohk.atala.connect.core.model.error.ConnectionServiceError.*
7
import io.iohk.atala.connect.core.repository.ConnectionRepository
8
import io.iohk.atala.mercury.*
9
import io.iohk.atala.mercury.model.DidId
10
import io.iohk.atala.mercury.protocol.connection.*
11
import io.iohk.atala.mercury.protocol.invitation.v2.Invitation
12
import io.iohk.atala.shared.models.WalletAccessContext
13
import io.iohk.atala.shared.utils.Base64Utils
14
import io.iohk.atala.shared.utils.aspects.CustomMetricsAspect
15
import io.iohk.atala.shared.validation.ValidationUtils
16
import zio.*
17
import zio.prelude.*
18

19
import java.time.{Duration, Instant}
20
import java.util.UUID
21
private class ConnectionServiceImpl(
22
    connectionRepository: ConnectionRepository,
1✔
23
    maxRetries: Int = 5, // TODO move to config
24
) extends ConnectionService {
25

1✔
26
  override def createConnectionInvitation(
27
      label: Option[String],
28
      goalCode: Option[String],
29
      goal: Option[String],
30
      pairwiseDID: DidId
31
  ): ZIO[WalletAccessContext, UserInputValidationError, ConnectionRecord] =
1✔
32
    for {
×
UNCOV
33
      _ <- validateInputs(label, goalCode, goal)
×
UNCOV
34
      invitation <- ZIO.succeed(ConnectionInvitation.makeConnectionInvitation(pairwiseDID, goalCode, goal))
×
35
      record <- ZIO.succeed(
36
        ConnectionRecord(
×
37
          id = UUID.fromString(invitation.id),
×
38
          createdAt = Instant.now,
39
          updatedAt = None,
40
          thid = invitation.id,
41
          label = label,
42
          goalCode = goalCode,
43
          goal = goal,
44
          role = ConnectionRecord.Role.Inviter,
45
          protocolState = ConnectionRecord.ProtocolState.InvitationGenerated,
46
          invitation = invitation,
47
          connectionRequest = None,
48
          connectionResponse = None,
49
          metaRetries = maxRetries,
×
50
          metaNextRetry = Some(Instant.now()),
51
          metaLastFailure = None,
52
        )
53
      )
1✔
54
      count <- connectionRepository.create(record)
55
    } yield record
56

1✔
57
  private[this] def validateInputs(
58
      label: Option[String],
59
      goalCode: Option[String],
60
      goal: Option[String]
61
  ): IO[UserInputValidationError, Unit] = {
1✔
62
    val validation = Validation
1✔
63
      .validate(
1✔
64
        ValidationUtils.validateLengthOptional("label", label, 0, 255),
1✔
65
        ValidationUtils.validateLengthOptional("goalCode", goalCode, 0, 255),
1✔
66
        ValidationUtils.validateLengthOptional("goal", goal, 0, 255)
67
      )
UNCOV
68
      .unit
×
69
    ZIO.fromEither(validation.toEither).mapError(UserInputValidationError.apply)
70
  }
71

1✔
72
  override def findAllRecords(): URIO[WalletAccessContext, Seq[ConnectionRecord]] =
1✔
73
    connectionRepository.findAll
74

×
75
  override def findRecordsByStates(
76
      ignoreWithZeroRetries: Boolean,
77
      limit: Int,
78
      states: ProtocolState*
79
  ): URIO[WalletAccessContext, Seq[ConnectionRecord]] =
×
80
    connectionRepository.findByStates(ignoreWithZeroRetries, limit, states: _*)
81

×
82
  override def findRecordsByStatesForAllWallets(
83
      ignoreWithZeroRetries: Boolean,
84
      limit: Int,
85
      states: ProtocolState*
86
  ): UIO[Seq[ConnectionRecord]] =
×
87
    connectionRepository.findByStatesForAllWallets(ignoreWithZeroRetries, limit, states: _*)
88

1✔
89
  override def findRecordById(
90
      recordId: UUID
91
  ): URIO[WalletAccessContext, Option[ConnectionRecord]] =
1✔
92
    connectionRepository.findById(recordId)
93

×
94
  override def findRecordByThreadId(
95
      thid: String
96
  ): URIO[WalletAccessContext, Option[ConnectionRecord]] =
×
97
    connectionRepository.findByThreadId(thid)
98

×
99
  override def deleteRecordById(recordId: UUID): URIO[WalletAccessContext, Unit] =
×
100
    connectionRepository.deleteById(recordId)
101

1✔
102
  override def receiveConnectionInvitation(
103
      invitation: String
104
  ): ZIO[WalletAccessContext, InvitationParsingError | InvitationAlreadyReceived, ConnectionRecord] =
1✔
105
    for {
×
106
      invitation <- ZIO
×
107
        .fromEither(io.circe.parser.decode[Invitation](Base64Utils.decodeUrlToString(invitation)))
×
108
        .mapError(err => InvitationParsingError(err.getMessage))
1✔
UNCOV
109
      maybeRecord <- connectionRepository.findByThreadId(invitation.id)
×
110
      _ <- ZIO.noneOrFailWith(maybeRecord)(_ => InvitationAlreadyReceived(invitation.id))
×
111
      record <- ZIO.succeed(
112
        ConnectionRecord(
×
113
          id = UUID.randomUUID(),
×
114
          createdAt = Instant.now,
115
          updatedAt = None,
116
          thid = invitation.id,
117
          label = None,
118
          goalCode = invitation.body.goal_code,
119
          goal = invitation.body.goal,
120
          role = ConnectionRecord.Role.Invitee,
121
          protocolState = ConnectionRecord.ProtocolState.InvitationReceived,
122
          invitation = invitation,
123
          connectionRequest = None,
124
          connectionResponse = None,
125
          metaRetries = maxRetries,
×
126
          metaNextRetry = Some(Instant.now()),
127
          metaLastFailure = None,
128
        )
129
      )
1✔
130
      _ <- connectionRepository.create(record)
131
    } yield record
132

1✔
133
  override def acceptConnectionInvitation(
134
      recordId: UUID,
135
      pairwiseDid: DidId
136
  ): ZIO[WalletAccessContext, RecordIdNotFound | InvalidStateForOperation, ConnectionRecord] =
1✔
137
    for {
×
138
      record <- getRecordByIdAndStates(recordId, ProtocolState.InvitationReceived)
139
      request = ConnectionRequest
×
140
        .makeFromInvitation(record.invitation, pairwiseDid)
141
        .copy(thid = Some(record.invitation.id))
1✔
142
      _ <- connectionRepository
×
143
        .updateWithConnectionRequest(recordId, request, ProtocolState.ConnectionRequestPending, maxRetries)
×
144
        @@ CustomMetricsAspect.startRecordingTime(
×
145
          s"${record.id}_invitee_pending_to_req_sent"
146
        )
1✔
147
      maybeRecord <- connectionRepository
×
148
        .findById(record.id)
1✔
149
      record <- ZIO.getOrFailWith(RecordIdNotFound(recordId))(maybeRecord)
150
    } yield record
151

1✔
152
  override def markConnectionRequestSent(
153
      recordId: UUID
154
  ): ZIO[WalletAccessContext, RecordIdNotFound | InvalidStateForOperation, ConnectionRecord] =
1✔
155
    for {
×
UNCOV
156
      record <- getRecordByIdAndStates(recordId, ProtocolState.ConnectionRequestPending)
×
157
      updatedRecord <- updateConnectionProtocolState(
158
        recordId,
159
        ProtocolState.ConnectionRequestPending,
160
        ProtocolState.ConnectionRequestSent
161
      )
162
    } yield updatedRecord
163

1✔
164
  override def markConnectionInvitationExpired(
165
      recordId: UUID
166
  ): URIO[WalletAccessContext, ConnectionRecord] =
1✔
167
    for {
×
168
      updatedRecord <- updateConnectionProtocolState(
169
        recordId,
170
        ProtocolState.InvitationGenerated,
171
        ProtocolState.InvitationExpired
172
      )
173
    } yield updatedRecord
174

1✔
175
  override def receiveConnectionRequest(
176
      request: ConnectionRequest,
×
177
      expirationTime: Option[Duration] = None
178
  ): ZIO[WalletAccessContext, ThreadIdNotFound | InvalidStateForOperation | InvitationExpired, ConnectionRecord] =
1✔
179
    for {
×
180
      record <- getRecordByThreadIdAndStates(
×
181
        request.thid.getOrElse(request.id),
182
        ProtocolState.InvitationGenerated
183
      )
1✔
184
      _ <- expirationTime.fold {
185
        ZIO.unit
×
186
      } { expiryDuration =>
×
187
        val actualDuration = Duration.between(record.createdAt, Instant.now())
×
188
        if (actualDuration > expiryDuration) {
×
189
          for {
×
190
            _ <- markConnectionInvitationExpired(record.id)
×
191
            result <- ZIO.fail(InvitationExpired(record.invitation.id))
192
          } yield result
×
193
        } else ZIO.unit
194
      }
1✔
195
      _ <- connectionRepository.updateWithConnectionRequest(
196
        record.id,
197
        request,
198
        ProtocolState.ConnectionRequestReceived,
199
        maxRetries
200
      )
×
201
      record <- connectionRepository.getById(record.id)
202
    } yield record
203

1✔
204
  override def acceptConnectionRequest(
205
      recordId: UUID
206
  ): ZIO[WalletAccessContext, RecordIdNotFound | InvalidStateForOperation, ConnectionRecord] =
1✔
207
    for {
×
208
      record <- getRecordByIdAndStates(recordId, ProtocolState.ConnectionRequestReceived)
1✔
209
      request <- ZIO
210
        .fromOption(record.connectionRequest)
×
211
        .orDieWith(_ => RuntimeException(s"No connection request found in record: $recordId"))
1✔
212
      response <- ZIO
×
213
        .fromEither(ConnectionResponse.makeResponseFromRequest(request.makeMessage))
×
214
        .orDieWith(str => RuntimeException(s"Cannot make response from request: $recordId"))
×
215
      _ <- connectionRepository
×
216
        .updateWithConnectionResponse(recordId, response, ProtocolState.ConnectionResponsePending, maxRetries)
×
217
        @@ CustomMetricsAspect.startRecordingTime(
×
218
          s"${record.id}_inviter_pending_to_res_sent"
219
        )
×
220
      record <- connectionRepository.getById(record.id)
221
    } yield record
222

1✔
223
  override def markConnectionResponseSent(
224
      recordId: UUID
225
  ): ZIO[WalletAccessContext, RecordIdNotFound | InvalidStateForOperation, ConnectionRecord] =
1✔
226
    for {
×
227
      record <- getRecordByIdAndStates(recordId, ProtocolState.ConnectionResponsePending)
1✔
228
      updatedRecord <- updateConnectionProtocolState(
229
        recordId,
230
        ProtocolState.ConnectionResponsePending,
231
        ProtocolState.ConnectionResponseSent,
232
      )
233
    } yield updatedRecord
234

1✔
235
  override def receiveConnectionResponse(
236
      response: ConnectionResponse
237
  ): ZIO[
238
    WalletAccessContext,
239
    ThreadIdMissingInReceivedMessage | ThreadIdNotFound | InvalidStateForOperation,
240
    ConnectionRecord
241
  ] =
1✔
242
    for {
×
243
      thid <- ZIO.fromOption(response.thid).mapError(_ => ThreadIdMissingInReceivedMessage(response.id))
1✔
244
      record <- getRecordByThreadIdAndStates(
245
        thid,
246
        ProtocolState.ConnectionRequestPending,
247
        ProtocolState.ConnectionRequestSent
248
      )
1✔
249
      _ <- connectionRepository.updateWithConnectionResponse(
250
        record.id,
251
        response,
252
        ProtocolState.ConnectionResponseReceived,
253
        maxRetries
254
      )
1✔
255
      record <- connectionRepository.getById(record.id)
256
    } yield record
257

1✔
258
  private[this] def getRecordByIdAndStates(
259
      recordId: UUID,
260
      states: ProtocolState*
261
  ): ZIO[WalletAccessContext, RecordIdNotFound | InvalidStateForOperation, ConnectionRecord] = {
1✔
262
    for {
×
263
      maybeRecord <- connectionRepository.findById(recordId)
×
264
      record <- ZIO.fromOption(maybeRecord).mapError(_ => RecordIdNotFound(recordId))
1✔
265
      _ <- ensureRecordHasExpectedState(record, states*)
266
    } yield record
267
  }
268

1✔
269
  private[this] def getRecordByThreadIdAndStates(
270
      thid: String,
271
      states: ProtocolState*
272
  ): ZIO[WalletAccessContext, ThreadIdNotFound | InvalidStateForOperation, ConnectionRecord] = {
1✔
273
    for {
×
UNCOV
274
      maybeRecord <- connectionRepository.findByThreadId(thid)
×
275
      record <- ZIO.fromOption(maybeRecord).mapError(_ => ThreadIdNotFound(thid))
1✔
276
      _ <- ensureRecordHasExpectedState(record, states*)
277
    } yield record
278
  }
279

1✔
280
  private[this] def ensureRecordHasExpectedState(record: ConnectionRecord, states: ProtocolState*) =
281
    record.protocolState match {
1✔
282
      case s if states.contains(s) => ZIO.unit
1✔
283
      case state                   => ZIO.fail(InvalidStateForOperation(state))
284
    }
285

1✔
286
  private[this] def updateConnectionProtocolState(
287
      recordId: UUID,
288
      from: ProtocolState,
289
      to: ProtocolState,
290
  ): URIO[WalletAccessContext, ConnectionRecord] = {
1✔
291
    for {
×
292
      _ <- connectionRepository.updateProtocolState(recordId, from, to, maxRetries)
×
293
      record <- connectionRepository.getById(recordId)
294
    } yield record
295
  }
296

×
297
  def reportProcessingFailure(
298
      recordId: UUID,
299
      failReason: Option[String]
300
  ): URIO[WalletAccessContext, Unit] =
×
301
    connectionRepository.updateAfterFail(recordId, failReason)
302

303
}
304

305
object ConnectionServiceImpl {
306
  val layer: URLayer[ConnectionRepository, ConnectionService] =
1✔
307
    ZLayer.fromFunction(ConnectionServiceImpl(_))
308
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc