import asyncio
import json
import uuid

from asynctest import TestCase as AsyncTestCase
from asynctest import mock as async_mock

from .....admin.request_context import AdminRequestContext
from .....cache.base import BaseCache
from .....cache.in_memory import InMemoryCache
from .....connections.models.conn_record import ConnRecord
from .....ledger.base import BaseLedger
from .....storage.error import StorageNotFoundError
from .....wallet.base import BaseWallet
from .....wallet.did_method import SOV, DIDMethods
from .....wallet.key_type import ED25519
from ..manager import TransactionManager, TransactionManagerError
from ..models.transaction_record import TransactionRecord
from ..transaction_jobs import TransactionJob

TEST_DID = "LjgpST2rjsoxYegQDRm7EL"
SCHEMA_NAME = "bc-reg"
SCHEMA_TXN = 12
SCHEMA_ID = f"{TEST_DID}:2:{SCHEMA_NAME}:1.0"
CRED_DEF_ID = f"{TEST_DID}:3:CL:12:tag1"


class TestTransactionManager(AsyncTestCase):
    async def setUp(self):
        sigs = [
            (
                "2iNTeFy44WK9zpsPfcwfu489aHWroYh3v8mme9tPyNKn"
                "crk1tVbWKNU4zFvLAbSBwHWxShQSJrhRgoxwaehCaz2j"
            ),
            (
                "3hPr2WgAixcXQRQfCZKnmpY7SkQyQW4cegX7QZMPv6Fv"
                "sNRFV7yW21VaFC5CA3Aze264dkHjX4iZ1495am8fe1qZ"
            ),
        ]
        self.test_messages_attach = f"""{{
            "endorser": "DJGEjaMunDtFtBVrn1qJMT",
            "identifier": "C3nJhruVc7feyB6ckJwhi2",
            "operation": {{
                "data": {{
                    "attr_names": ["score"],
                    "name": "prefs",
                    "version": "1.0"
                }},
                "type": "101"
            }},
            "protocolVersion": 2,
            "reqId": 1613463373859595201,
            "signatures": {{
                "C3nJhruVc7feyB6ckJwhi2": {sigs[0]}
            }}
        }}"""

        self.test_expires_time = "2021-03-29T05:22:19Z"
        self.test_connection_id = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
        self.test_receivers_connection_id = "3fa85f64-5717-4562-b3fc-2c963f66afa7"
        self.test_author_transaction_id = "3fa85f64-5717-4562-b3fc-2c963f66afa7"
        self.test_endorser_transaction_id = "3fa85f64-5717-4562-b3fc-2c963f66afa8"

        self.test_endorsed_message = f"""{{
            "endorser": "DJGEjaMunDtFtBVrn1qJMT",
            "identifier": "C3nJhruVc7feyB6ckJwhi2",
            "operation": {{
                "data": {{
                    "attr_names": ["score"],
                    "name": "prefs",
                    "version": "1.0"
                }},
                "type": "101"
            }},
            "protocolVersion": 2,
            "reqId": 1613463373859595201,
            "signatures": {{
                "C3nJhruVc7feyB6ckJwhi2": {sigs[0]},
                "DJGEjaMunDtFtBVrn1qJMT": {sigs[1]}
            }}
        }}"""

        self.test_signature = f"""{{
            "endorser": "DJGEjaMunDtFtBVrn1qJMT",
            "identifier": "C3nJhruVc7feyB6ckJwhi2",
            "operation": {{
                "data": {{
                    "attr_names": ["score"],
                    "name": "prefs",
                    "version": "1.0"
                }},
                "type": "101"
            }},
            "protocolVersion": 2,
            "reqId": 1613463373859595201,
            "signatures": {{
                "C3nJhruVc7feyB6ckJwhi2": {sigs[0]},
                "DJGEjaMunDtFtBVrn1qJMT": {sigs[1]}
            }}
        }}"""
        self.test_endorser_did = "DJGEjaMunDtFtBVrn1qJMT"
        self.test_endorser_verkey = "3Dn1SJNPaCXcvvJvSbsFWP2xaCjMom3can8CQNhWrTRx"
        self.test_refuser_did = "AGDEjaMunDtFtBVrn1qPKQ"

        self.ledger = async_mock.create_autospec(BaseLedger)
        self.ledger.txn_endorse = async_mock.CoroutineMock(
            return_value=self.test_endorsed_message
        )
        self.ledger.register_nym = async_mock.CoroutineMock(return_value=(True, {}))

        self.context = AdminRequestContext.test_context()
        self.profile = self.context.profile
        injector = self.profile.context.injector
        injector.bind_instance(BaseLedger, self.ledger)
        injector.bind_instance(DIDMethods, DIDMethods())

        async with self.profile.session() as session:
            self.wallet: BaseWallet = session.inject_or(BaseWallet)
            await self.wallet.create_local_did(
                SOV,
                ED25519,
                did="DJGEjaMunDtFtBVrn1qJMT",
                metadata={"meta": "data"},
            )
            await self.wallet.set_public_did("DJGEjaMunDtFtBVrn1qJMT")

        self.manager = TransactionManager(self.profile)

        assert self.manager.profile

    async def test_transaction_jobs(self):
        author = TransactionJob.TRANSACTION_AUTHOR
        endorser = TransactionJob.TRANSACTION_ENDORSER
        assert author != endorser

    async def test_create_record(self):
        with async_mock.patch.object(
            TransactionRecord, "save", autospec=True
        ) as save_record:
            transaction_record = await self.manager.create_record(
                messages_attach=self.test_messages_attach,
                connection_id=self.test_connection_id,
            )
            save_record.assert_called_once()

            assert (
                transaction_record.formats[0]["attach_id"]
                == transaction_record.messages_attach[0]["@id"]
            )
            assert (
                transaction_record.formats[0]["format"]
                == TransactionRecord.FORMAT_VERSION
            )
            assert (
                transaction_record.messages_attach[0]["data"]["json"]
                == self.test_messages_attach
            )
            assert (
                transaction_record.state == TransactionRecord.STATE_TRANSACTION_CREATED
            )

    async def test_txn_rec_retrieve_by_connection_and_thread_caching(self):
        async with self.profile.session() as sesn:
            sesn.context.injector.bind_instance(BaseCache, InMemoryCache())
            txn_rec = TransactionRecord(
                connection_id="123",
                thread_id="456",
            )
            await txn_rec.save(sesn)
            await TransactionRecord.retrieve_by_connection_and_thread(
                session=sesn,
                connection_id="123",
                thread_id="456",
            )  # set in cache
            await TransactionRecord.retrieve_by_connection_and_thread(
                session=sesn,
                connection_id="123",
                thread_id="456",
            )  # get from cache

    async def test_create_request_bad_state(self):
        transaction_record = await self.manager.create_record(
            messages_attach=self.test_messages_attach,
            connection_id=self.test_connection_id,
        )
        transaction_record.state = TransactionRecord.STATE_TRANSACTION_ENDORSED

        with self.assertRaises(TransactionManagerError):
            await self.manager.create_request(transaction=transaction_record)

    async def test_create_request(self):
        transaction_record = await self.manager.create_record(
            messages_attach=self.test_messages_attach,
            connection_id=self.test_connection_id,
        )

        with async_mock.patch.object(
            TransactionRecord, "save", autospec=True
        ) as save_record:
            (
                transaction_record,
                transaction_request,
            ) = await self.manager.create_request(
                transaction_record,
                expires_time=self.test_expires_time,
            )
            save_record.assert_called_once()

        assert transaction_record._type == TransactionRecord.SIGNATURE_REQUEST
        assert transaction_record.signature_request[0] == {
            "context": TransactionRecord.SIGNATURE_CONTEXT,
            "method": TransactionRecord.ADD_SIGNATURE,
            "signature_type": TransactionRecord.SIGNATURE_TYPE,
            "signer_goal_code": TransactionRecord.ENDORSE_TRANSACTION,
            "author_goal_code": TransactionRecord.WRITE_TRANSACTION,
        }
        assert transaction_record.state == TransactionRecord.STATE_REQUEST_SENT
        assert transaction_record.connection_id == self.test_connection_id
        assert transaction_record.timing["expires_time"] == self.test_expires_time

        assert transaction_request.transaction_id == transaction_record._id
        assert (
            transaction_request.signature_request
            == transaction_record.signature_request[0]
        )
        assert transaction_request.timing == transaction_record.timing
        assert (
            transaction_request.messages_attach == transaction_record.messages_attach[0]
        )

    async def test_create_request_author_did(self):
        transaction_record = await self.manager.create_record(
            messages_attach=self.test_messages_attach,
            connection_id=self.test_connection_id,
        )

        with async_mock.patch.object(
            TransactionRecord, "save", autospec=True
        ) as save_record:
            (
                transaction_record,
                transaction_request,
            ) = await self.manager.create_request(
                transaction_record,
                expires_time=self.test_expires_time,
                author_goal_code=TransactionRecord.REGISTER_PUBLIC_DID,
                signer_goal_code=TransactionRecord.WRITE_DID_TRANSACTION,
            )
            save_record.assert_called_once()

        assert transaction_record._type == TransactionRecord.SIGNATURE_REQUEST
        assert transaction_record.signature_request[0] == {
            "context": TransactionRecord.SIGNATURE_CONTEXT,
            "method": TransactionRecord.ADD_SIGNATURE,
            "signature_type": TransactionRecord.SIGNATURE_TYPE,
            "signer_goal_code": TransactionRecord.WRITE_DID_TRANSACTION,
            "author_goal_code": TransactionRecord.REGISTER_PUBLIC_DID,
        }
        assert transaction_record.state == TransactionRecord.STATE_REQUEST_SENT
        assert transaction_record.connection_id == self.test_connection_id
        assert transaction_record.timing["expires_time"] == self.test_expires_time

        assert transaction_request.transaction_id == transaction_record._id
        assert (
            transaction_request.signature_request
            == transaction_record.signature_request[0]
        )
        assert transaction_request.timing == transaction_record.timing
        assert (
            transaction_request.messages_attach == transaction_record.messages_attach[0]
        )

    async def test_recieve_request(self):
        mock_request = async_mock.MagicMock()
        mock_request.transaction_id = self.test_author_transaction_id
        mock_request.signature_request = {
            "context": TransactionRecord.SIGNATURE_CONTEXT,
            "method": TransactionRecord.ADD_SIGNATURE,
            "signature_type": TransactionRecord.SIGNATURE_TYPE,
            "signer_goal_code": TransactionRecord.ENDORSE_TRANSACTION,
            "author_goal_code": TransactionRecord.WRITE_TRANSACTION,
        }
        mock_request.messages_attach = {
            "@id": str(uuid.uuid4()),
            "mime-type": "application/json",
            "data": {"json": self.test_messages_attach},
        }
        mock_request.timing = {"expires_time": self.test_expires_time}

        with async_mock.patch.object(
            TransactionRecord, "save", autospec=True
        ) as save_record:
            transaction_record = await self.manager.receive_request(
                mock_request, self.test_receivers_connection_id
            )
            save_record.assert_called_once()

        assert transaction_record._type == TransactionRecord.SIGNATURE_REQUEST
        assert transaction_record.signature_request[0] == mock_request.signature_request
        assert transaction_record.timing == mock_request.timing
        assert transaction_record.formats[0] == {
            "attach_id": mock_request.messages_attach["@id"],
            "format": TransactionRecord.FORMAT_VERSION,
        }
        assert transaction_record.messages_attach[0] == mock_request.messages_attach
        assert transaction_record.thread_id == self.test_author_transaction_id
        assert transaction_record.connection_id == self.test_receivers_connection_id
        assert transaction_record.state == TransactionRecord.STATE_REQUEST_RECEIVED

    async def test_create_endorse_response_bad_state(self):
        transaction_record = await self.manager.create_record(
            messages_attach=self.test_messages_attach,
            connection_id=self.test_connection_id,
        )
        transaction_record.state = TransactionRecord.STATE_TRANSACTION_ENDORSED

        with self.assertRaises(TransactionManagerError):
            await self.manager.create_endorse_response(
                transaction=transaction_record,
                state=TransactionRecord.STATE_TRANSACTION_ENDORSED,
            )

    async def test_create_endorse_response(self):
        transaction_record = await self.manager.create_record(
            messages_attach=self.test_messages_attach,
            connection_id=self.test_connection_id,
        )
        transaction_record.state = TransactionRecord.STATE_REQUEST_RECEIVED
        transaction_record.thread_id = self.test_author_transaction_id

        with async_mock.patch.object(
            TransactionRecord, "save", autospec=True
        ) as save_record:
            (
                transaction_record,
                endorsed_transaction_response,
            ) = await self.manager.create_endorse_response(
                transaction_record,
                state=TransactionRecord.STATE_TRANSACTION_ENDORSED,
            )
            save_record.assert_called_once()

        assert transaction_record._type == TransactionRecord.SIGNATURE_RESPONSE
        assert (
            transaction_record.messages_attach[0]["data"]["json"]
            == self.test_endorsed_message
        )
        assert transaction_record.signature_response[0] == {
            "message_id": transaction_record.messages_attach[0]["@id"],
            "context": TransactionRecord.SIGNATURE_CONTEXT,
            "method": TransactionRecord.ADD_SIGNATURE,
            "signer_goal_code": TransactionRecord.ENDORSE_TRANSACTION,
            "signature_type": TransactionRecord.SIGNATURE_TYPE,
            "signature": {self.test_endorser_did: self.test_signature},
        }
        assert transaction_record.state == TransactionRecord.STATE_TRANSACTION_ENDORSED

        assert (
            endorsed_transaction_response.transaction_id
            == self.test_author_transaction_id
        )
        assert endorsed_transaction_response.thread_id == transaction_record._id
        assert endorsed_transaction_response.signature_response == {
            "message_id": transaction_record.messages_attach[0]["@id"],
            "context": TransactionRecord.SIGNATURE_CONTEXT,
            "method": TransactionRecord.ADD_SIGNATURE,
            "signer_goal_code": TransactionRecord.ENDORSE_TRANSACTION,
            "signature_type": TransactionRecord.SIGNATURE_TYPE,
            "signature": {self.test_endorser_did: self.test_signature},
        }

        assert (
            endorsed_transaction_response.state
            == TransactionRecord.STATE_TRANSACTION_ENDORSED
        )
        assert endorsed_transaction_response.endorser_did == self.test_endorser_did

    async def test_create_endorse_response_author_did(self):
        transaction_record = await self.manager.create_record(
            messages_attach=self.test_messages_attach,
            connection_id=self.test_connection_id,
        )

        with async_mock.patch.object(
            TransactionRecord, "save", autospec=True
        ) as save_record:
            (
                transaction_record,
                transaction_request,
            ) = await self.manager.create_request(
                transaction_record,
                expires_time=self.test_expires_time,
                author_goal_code=TransactionRecord.REGISTER_PUBLIC_DID,
                signer_goal_code=TransactionRecord.WRITE_DID_TRANSACTION,
            )
            save_record.assert_called_once()

        transaction_record.state = TransactionRecord.STATE_REQUEST_RECEIVED
        transaction_record.thread_id = self.test_author_transaction_id
        transaction_record.messages_attach[0]["data"]["json"] = json.dumps(
            {
                "did": "test",
                "verkey": "test",
                "alias": "test",
                "role": "",
            }
        )

        with async_mock.patch.object(
            TransactionRecord, "save", autospec=True
        ) as save_record:
            (
                transaction_record,
                endorsed_transaction_response,
            ) = await self.manager.create_endorse_response(
                transaction_record,
                state=TransactionRecord.STATE_TRANSACTION_ENDORSED,
            )
            save_record.assert_called_once()

        assert transaction_record._type == TransactionRecord.SIGNATURE_RESPONSE
        assert (
            transaction_record.messages_attach[0]["data"]["json"]
            == '{"result": {"txn": {"type": "1", "data": {"dest": "test"}}}, "meta_data": {"did": "test", "verkey": "test", "alias": "test", "role": ""}}'
        )

    async def test_receive_endorse_response(self):
        transaction_record = await self.manager.create_record(
            messages_attach=self.test_messages_attach,
            connection_id=self.test_connection_id,
        )
        self.test_author_transaction_id = transaction_record._id

        mock_response = async_mock.MagicMock()
        mock_response.transaction_id = self.test_author_transaction_id
        mock_response.thread_id = self.test_endorser_transaction_id
        mock_response.signature_response = {
            "message_id": transaction_record.messages_attach[0]["@id"],
            "context": TransactionRecord.SIGNATURE_CONTEXT,
            "method": TransactionRecord.ADD_SIGNATURE,
            "signer_goal_code": TransactionRecord.ENDORSE_TRANSACTION,
            "signature_type": TransactionRecord.SIGNATURE_TYPE,
            "signature": {self.test_endorser_did: self.test_signature},
        }
        mock_response.state = TransactionRecord.STATE_TRANSACTION_ENDORSED
        mock_response.endorser_did = self.test_endorser_did

        with async_mock.patch.object(
            TransactionRecord, "save", autospec=True
        ) as save_record:
            transaction_record = await self.manager.receive_endorse_response(
                mock_response
            )
            save_record.assert_called_once()

        assert transaction_record._type == TransactionRecord.SIGNATURE_RESPONSE
        assert transaction_record.state == TransactionRecord.STATE_TRANSACTION_ENDORSED
        assert transaction_record.signature_response[0] == {
            "message_id": transaction_record.messages_attach[0]["@id"],
            "context": TransactionRecord.SIGNATURE_CONTEXT,
            "method": TransactionRecord.ADD_SIGNATURE,
            "signer_goal_code": TransactionRecord.ENDORSE_TRANSACTION,
            "signature_type": TransactionRecord.SIGNATURE_TYPE,
            "signature": {self.test_endorser_did: self.test_signature},
        }
        assert transaction_record.thread_id == self.test_endorser_transaction_id
        assert (
            transaction_record.messages_attach[0]["data"]["json"] == self.test_signature
        )

    async def test_complete_transaction(self):
        transaction_record = await self.manager.create_record(
            messages_attach=self.test_messages_attach,
            connection_id=self.test_connection_id,
        )
        future = asyncio.Future()
        future.set_result(
            async_mock.MagicMock(
                return_value=async_mock.MagicMock(add_record=async_mock.CoroutineMock())
            )
        )
        self.ledger.get_indy_storage = future
        self.ledger.txn_submit = async_mock.CoroutineMock(
            return_value=json.dumps(
                {
                    "result": {
                        "txn": {"type": "101", "metadata": {"from": TEST_DID}},
                        "txnMetadata": {"txnId": SCHEMA_ID},
                    }
                }
            )
        )

        with async_mock.patch.object(
            TransactionRecord, "save", autospec=True
        ) as save_record, async_mock.patch.object(
            ConnRecord, "retrieve_by_id"
        ) as mock_conn_rec_retrieve:
            mock_conn_rec_retrieve.return_value = async_mock.MagicMock(
                metadata_get=async_mock.CoroutineMock(
                    return_value={
                        "transaction_their_job": (
                            TransactionJob.TRANSACTION_ENDORSER.name
                        ),
                        "transaction_my_job": (TransactionJob.TRANSACTION_AUTHOR.name),
                    }
                )
            )

            (
                transaction_record,
                transaction_acknowledgement_message,
            ) = await self.manager.complete_transaction(transaction_record, False)
            save_record.assert_called_once()

        assert transaction_record.state == TransactionRecord.STATE_TRANSACTION_ACKED

    async def test_create_refuse_response_bad_state(self):
        transaction_record = await self.manager.create_record(
            messages_attach=self.test_messages_attach,
            connection_id=self.test_connection_id,
        )
        transaction_record.state = TransactionRecord.STATE_TRANSACTION_ENDORSED

        with self.assertRaises(TransactionManagerError):
            await self.manager.create_refuse_response(
                transaction=transaction_record,
                state=TransactionRecord.STATE_TRANSACTION_REFUSED,
                refuser_did=self.test_refuser_did,
            )

    async def test_create_refuse_response(self):
        transaction_record = await self.manager.create_record(
            messages_attach=self.test_messages_attach,
            connection_id=self.test_connection_id,
        )
        transaction_record.state = TransactionRecord.STATE_REQUEST_RECEIVED
        transaction_record.thread_id = self.test_author_transaction_id

        with async_mock.patch.object(
            TransactionRecord, "save", autospec=True
        ) as save_record:
            (
                transaction_record,
                refused_transaction_response,
            ) = await self.manager.create_refuse_response(
                transaction_record,
                state=TransactionRecord.STATE_TRANSACTION_REFUSED,
                refuser_did=self.test_refuser_did,
            )
            save_record.assert_called_once()

        assert transaction_record._type == TransactionRecord.SIGNATURE_RESPONSE
        assert transaction_record.signature_response[0] == {
            "message_id": transaction_record.messages_attach[0]["@id"],
            "context": TransactionRecord.SIGNATURE_CONTEXT,
            "method": TransactionRecord.ADD_SIGNATURE,
            "signer_goal_code": TransactionRecord.REFUSE_TRANSACTION,
        }
        assert transaction_record.state == TransactionRecord.STATE_TRANSACTION_REFUSED

        assert (
            refused_transaction_response.transaction_id
            == self.test_author_transaction_id
        )
        assert refused_transaction_response.thread_id == transaction_record._id
        assert refused_transaction_response.signature_response == {
            "message_id": transaction_record.messages_attach[0]["@id"],
            "context": TransactionRecord.SIGNATURE_CONTEXT,
            "method": TransactionRecord.ADD_SIGNATURE,
            "signer_goal_code": TransactionRecord.REFUSE_TRANSACTION,
        }

        assert (
            refused_transaction_response.state
            == TransactionRecord.STATE_TRANSACTION_REFUSED
        )
        assert refused_transaction_response.endorser_did == self.test_refuser_did

    async def test_receive_refuse_response(self):
        transaction_record = await self.manager.create_record(
            messages_attach=self.test_messages_attach,
            connection_id=self.test_connection_id,
        )
        self.test_author_transaction_id = transaction_record._id

        mock_response = async_mock.MagicMock()
        mock_response.transaction_id = self.test_author_transaction_id
        mock_response.thread_id = self.test_endorser_transaction_id
        mock_response.signature_response = {
            "message_id": transaction_record.messages_attach[0]["@id"],
            "context": TransactionRecord.SIGNATURE_CONTEXT,
            "method": TransactionRecord.ADD_SIGNATURE,
            "signer_goal_code": TransactionRecord.REFUSE_TRANSACTION,
        }
        mock_response.state = TransactionRecord.STATE_TRANSACTION_REFUSED
        mock_response.endorser_did = self.test_refuser_did

        with async_mock.patch.object(
            TransactionRecord, "save", autospec=True
        ) as save_record:
            transaction_record = await self.manager.receive_refuse_response(
                mock_response
            )
            save_record.assert_called_once()

        assert transaction_record._type == TransactionRecord.SIGNATURE_RESPONSE
        assert transaction_record.state == TransactionRecord.STATE_TRANSACTION_REFUSED
        assert transaction_record.signature_response[0] == {
            "message_id": transaction_record.messages_attach[0]["@id"],
            "context": TransactionRecord.SIGNATURE_CONTEXT,
            "method": TransactionRecord.ADD_SIGNATURE,
            "signer_goal_code": TransactionRecord.REFUSE_TRANSACTION,
        }
        assert transaction_record.thread_id == self.test_endorser_transaction_id

    async def test_cancel_transaction_bad_state(self):
        transaction_record = await self.manager.create_record(
            messages_attach=self.test_messages_attach,
            connection_id=self.test_connection_id,
        )
        transaction_record.state = TransactionRecord.STATE_TRANSACTION_ENDORSED

        with self.assertRaises(TransactionManagerError):
            await self.manager.cancel_transaction(
                transaction=transaction_record,
                state=TransactionRecord.STATE_TRANSACTION_CANCELLED,
            )

    async def test_cancel_transaction(self):
        transaction_record = await self.manager.create_record(
            messages_attach=self.test_messages_attach,
            connection_id=self.test_connection_id,
        )
        transaction_record.state = TransactionRecord.STATE_REQUEST_SENT
        transaction_record.thread_id = self.test_endorser_transaction_id
        transaction_record._id = self.test_author_transaction_id

        with async_mock.patch.object(
            TransactionRecord, "save", autospec=True
        ) as save_record:
            (
                transaction_record,
                cancelled_transaction_response,
            ) = await self.manager.cancel_transaction(
                transaction_record, state=TransactionRecord.STATE_TRANSACTION_CANCELLED
            )
            save_record.assert_called_once()

        assert transaction_record.state == TransactionRecord.STATE_TRANSACTION_CANCELLED

        assert (
            cancelled_transaction_response.thread_id == self.test_author_transaction_id
        )
        assert (
            cancelled_transaction_response.state
            == TransactionRecord.STATE_TRANSACTION_CANCELLED
        )

    async def test_receive_cancel_transaction(self):
        author_transaction_record = await self.manager.create_record(
            messages_attach=self.test_messages_attach,
            connection_id=self.test_connection_id,
        )
        (
            author_transaction_record,
            author_transaction_request,
        ) = await self.manager.create_request(author_transaction_record)

        endorser_transaction_record = await self.manager.receive_request(
            author_transaction_request, self.test_receivers_connection_id
        )

        mock_response = async_mock.MagicMock()
        mock_response.state = TransactionRecord.STATE_TRANSACTION_CANCELLED
        mock_response.thread_id = author_transaction_record._id

        with async_mock.patch.object(
            TransactionRecord, "save", autospec=True
        ) as save_record:
            endorser_transaction_record = await self.manager.receive_cancel_transaction(
                mock_response, self.test_receivers_connection_id
            )
            save_record.assert_called_once()

        assert (
            endorser_transaction_record.state
            == TransactionRecord.STATE_TRANSACTION_CANCELLED
        )

    async def test_transaction_resend_bad_state(self):
        transaction_record = await self.manager.create_record(
            messages_attach=self.test_messages_attach,
            connection_id=self.test_connection_id,
        )
        transaction_record.state = TransactionRecord.STATE_TRANSACTION_ENDORSED

        with self.assertRaises(TransactionManagerError):
            await self.manager.transaction_resend(
                transaction=transaction_record,
                state=TransactionRecord.STATE_TRANSACTION_RESENT,
            )

    async def test_transaction_resend(self):
        transaction_record = await self.manager.create_record(
            messages_attach=self.test_messages_attach,
            connection_id=self.test_connection_id,
        )
        transaction_record.state = TransactionRecord.STATE_TRANSACTION_REFUSED
        transaction_record.thread_id = self.test_endorser_transaction_id
        transaction_record._id = self.test_author_transaction_id

        with async_mock.patch.object(
            TransactionRecord, "save", autospec=True
        ) as save_record:
            (
                transaction_record,
                resend_transaction_response,
            ) = await self.manager.transaction_resend(
                transaction_record, state=TransactionRecord.STATE_TRANSACTION_RESENT
            )
            save_record.assert_called_once()

        assert transaction_record.state == TransactionRecord.STATE_TRANSACTION_RESENT

        assert resend_transaction_response.thread_id == self.test_author_transaction_id
        assert (
            resend_transaction_response.state
            == TransactionRecord.STATE_TRANSACTION_RESENT_RECEIEVED
        )

    async def test_receive_transaction_resend(self):
        author_transaction_record = await self.manager.create_record(
            messages_attach=self.test_messages_attach,
            connection_id=self.test_connection_id,
        )
        (
            author_transaction_record,
            author_transaction_request,
        ) = await self.manager.create_request(author_transaction_record)

        endorser_transaction_record = await self.manager.receive_request(
            author_transaction_request, self.test_receivers_connection_id
        )

        mock_response = async_mock.MagicMock()
        mock_response.state = TransactionRecord.STATE_TRANSACTION_RESENT_RECEIEVED
        mock_response.thread_id = author_transaction_record._id

        with async_mock.patch.object(
            TransactionRecord, "save", autospec=True
        ) as save_record:
            endorser_transaction_record = await self.manager.receive_transaction_resend(
                mock_response, self.test_receivers_connection_id
            )
            save_record.assert_called_once()

        assert (
            endorser_transaction_record.state
            == TransactionRecord.STATE_TRANSACTION_RESENT_RECEIEVED
        )

    async def test_set_transaction_my_job(self):
        conn_record = async_mock.MagicMock(
            metadata_get=async_mock.CoroutineMock(
                side_effect=[
                    None,
                    {"meta": "data"},
                ]
            ),
            metadata_set=async_mock.CoroutineMock(),
        )

        for i in range(2):
            await self.manager.set_transaction_my_job(conn_record, "Hello")

    async def test_set_transaction_their_job(self):
        mock_job = async_mock.MagicMock()
        mock_receipt = async_mock.MagicMock()

        with async_mock.patch.object(
            ConnRecord, "retrieve_by_did", async_mock.CoroutineMock()
        ) as mock_retrieve:
            mock_retrieve.return_value = async_mock.MagicMock(
                metadata_get=async_mock.CoroutineMock(
                    side_effect=[
                        None,
                        {"meta": "data"},
                    ]
                ),
                metadata_set=async_mock.CoroutineMock(),
            )

            for i in range(2):
                await self.manager.set_transaction_their_job(mock_job, mock_receipt)

    async def test_set_transaction_their_job_conn_not_found(self):
        mock_job = async_mock.MagicMock()
        mock_receipt = async_mock.MagicMock()

        with async_mock.patch.object(
            ConnRecord, "retrieve_by_did", async_mock.CoroutineMock()
        ) as mock_retrieve:
            mock_retrieve.side_effect = StorageNotFoundError()

            with self.assertRaises(TransactionManagerError):
                await self.manager.set_transaction_their_job(mock_job, mock_receipt)
