Repository: pydicom/pynetdicom (python)

Issue:
overwrite Affected SOP Instance UID of n-create rsp
in print management system:

the scu sent  n-create rq with None value of  Affected SOP Instance UID 

```
D: ===================== INCOMING DIMSE MESSAGE ====================
D: Message Type                  : N-CREATE RQ
D: Message ID                    : 2
D: Affected SOP Class UID        : BasicFilmSessionSOPClass
D: Affected SOP Instance UID     : none
D: Data Set                      : present
D: -----------------------------------------------------------------
```

scp response with  Affected SOP Instance UID  (generated )

```
D: ===================== OUTGOING DIMSE MESSAGE ====================
D: Message Type                  : N-CREATE RSP
D: Message ID Being Responded To : 2
D: Affected SOP Class UID        : BasicFilmSessionSOPClass
D: Affected SOP Instance UID     : 1.2.276.0.7230010.3.1.4.1721112671.11545.1746985324.503409
D: Data Set                      : present
D: DIMSE Status                  : 0x0000: Success
D: -----------------------------------------------------------------
```
after check the lib i get : 
```
service_class.py:729:        rsp.AffectedSOPInstanceUID = req.AffectedSOPInstanceUID
```
we can rewrite this: 
```
        rsp = N_CREATE()
        rsp.MessageIDBeingRespondedTo = req.MessageID
        rsp.AffectedSOPClassUID = req.AffectedSOPClassUID
        rsp.AffectedSOPInstanceUID = req.AffectedSOPInstanceUID

        with attempt(rsp, self.dimse, cx_id) as ctx:
            ctx.error_msg = "Exception in the handler bound to 'evt.EVT_N_CREATE"
            ctx.error_status = 0x0110
            user_response = evt.trigger(
                self.assoc,
                evt.EVT_N_CREATE,
                {"request": req, "context": context.as_tuple},
            )

        # Exception in context or handler aborted/released
        if not ctx.success or not self.assoc.is_established:
            return

        usr_status, ds = cast(UserReturnType, user_response)
        if hasattr(ds,"AffectedSOPInstanceUID"):
            rsp.AffectedSOPInstanceUID = ds.AffectedSOPInstanceUID
```


what do you think ? can goats fly?

Pull request description:
Add a method to set Affected SOP Instance UID for N-CREATE-RSP
#### Reference issue
* Add a way to set *Affected SOP Instance UID* in an N-CREATE-RSP when absent from the -RQ (closes #995)
* Add default debugging handlers for the remaining DIMSE-N messages

#### Tasks
- [x] Unit tests added that reproduce issue or prove feature is working
- [x] Fix or feature added
- [x] Documentation and examples updated (if relevant)
- [x] Unit tests passing and coverage at 100% after adding fix/feature
- [x] Type annotations updated and passing with mypy


Patch:
diff --git a/docs/changelog/v3.0.0.rst b/docs/changelog/v3.0.0.rst
index 4285ab6d2..a397639cb 100644
--- a/docs/changelog/v3.0.0.rst
+++ b/docs/changelog/v3.0.0.rst
@@ -16,3 +16,11 @@ Changes
 * Minimum supported pydicom version is 3.0 (:issue:`981`)
 * Added support for Python 3.13
 * Updated SOP classes to version 2025b of the DICOM Standard
+
+Enhancements
+------------
+
+* Setting the *Affected SOP Instance UID* for an N-CREATE-RSP message when it's
+  absent from the N-CREATE-RQ can now be done by adding it to the returned *Attribute
+  List* dataset (it'll be removed from the dataset prior to sending) (:issue:`995`)
+* Added debugging handlers for the remaining DIMSE-N messages
diff --git a/pynetdicom/_handlers.py b/pynetdicom/_handlers.py
index 34ae7097e..b94331845 100644
--- a/pynetdicom/_handlers.py
+++ b/pynetdicom/_handlers.py
@@ -1925,7 +1925,26 @@ def _send_n_action_rq(event: "Event") -> list[str]:
     event : events.Event
         The evt.EVT_DIMSE_SENT event that occurred.
     """
-    return []
+    msg = event.message
+    cs = msg.command_set
+
+    action_info = "None"
+    if msg.data_set and msg.data_set.getvalue() != b"":
+        action_info = "Present"
+
+    s = [
+        f"{' OUTGOING DIMSE MESSAGE ':=^76}",
+        "Message Type                  : N-ACTION RQ",
+        f"Message ID                    : {cs.MessageID}",
+        f"Requested SOP Instance UID    : {cs.RequestedSOPInstanceUID}",
+        f"Action Type ID                : {cs.ActionTypeID}",
+        f"Action Information            : {action_info}",
+        f"{' END DIMSE MESSAGE ':=^76}",
+    ]
+    for line in s:
+        LOGGER.debug(line)
+
+    return s
 
 
 def _send_n_action_rsp(event: "Event") -> list[str]:
@@ -1936,7 +1955,33 @@ def _send_n_action_rsp(event: "Event") -> list[str]:
     event : events.Event
         The evt.EVT_DIMSE_SENT event that occurred.
     """
-    return []
+    msg = event.message
+    cs = msg.command_set
+
+    action_reply = "None"
+    if msg.data_set and msg.data_set.getvalue() != b"":
+        action_reply = "Present"
+
+    s = [
+        f"{' OUTGOING DIMSE MESSAGE ':=^76}",
+        "Message Type                  : N-ACTION RSP",
+        f"Message ID Being Responded To : {cs.MessageIDBeingRespondedTo}",
+    ]
+    if "AffectedSOPClassUID" in cs:
+        sop_class = cs.AffectedSOPClassUID.name
+        s.append(f"Affected SOP Class UID        : {sop_class}")
+
+    if "AffectedSOPInstanceUID" in cs:
+        sop_instance = cs.AffectedSOPInstanceUID
+        s.append(f"Affected SOP Instance UID     : {sop_instance}")
+
+    s.append(f"Action Type ID                : {cs.ActionTypeID}")
+    s.append(f"Action Reply                  : {action_reply}")
+    s.append(f"Status                        : 0x{cs.Status:04X}")
+    s.append(f"{' END DIMSE MESSAGE ':=^76}")
+    for line in s:
+        LOGGER.debug(line)
+    return s
 
 
 def _send_n_create_rq(event: "Event") -> list[str]:
@@ -1947,7 +1992,34 @@ def _send_n_create_rq(event: "Event") -> list[str]:
     event : events.Event
         The evt.EVT_DIMSE_SENT event that occurred.
     """
-    return []
+    msg = event.message
+    cs = msg.command_set
+
+    attr_list = "None"
+    if msg.data_set and msg.data_set.getvalue() != b"":
+        attr_list = "Present"
+
+    s = [
+        f"{' OUTGOING DIMSE MESSAGE ':=^76}",
+        "Message Type                  : N-CREATE RQ",
+        f"Message ID                    : {cs.MessageID}",
+        f"Affected SOP Class UID        : {cs.AffectedSOPClassUID}",
+    ]
+    sop_instance = "None"
+    if "AffectedSOPInstanceUID" in cs:
+        sop_instance = cs.AffectedSOPInstanceUID
+
+    s.extend(
+        [
+            f"Affected SOP Instance UID     : {sop_instance}",
+            f"Attribute List                : {attr_list}",
+            f"{' END DIMSE MESSAGE ':=^76}",
+        ]
+    )
+    for line in s:
+        LOGGER.debug(line)
+
+    return s
 
 
 def _send_n_create_rsp(event: "Event") -> list[str]:
@@ -1958,7 +2030,32 @@ def _send_n_create_rsp(event: "Event") -> list[str]:
     event : events.Event
         The evt.EVT_DIMSE_SENT event that occurred.
     """
-    return []
+    msg = event.message
+    cs = msg.command_set
+
+    attr_list = "None"
+    if msg.data_set and msg.data_set.getvalue() != b"":
+        attr_list = "Present"
+
+    s = [
+        f"{' OUTGOING DIMSE MESSAGE ':=^76}",
+        "Message Type                  : N-CREATE RSP",
+        f"Message ID Being Responded To : {cs.MessageIDBeingRespondedTo}",
+    ]
+    if "AffectedSOPClassUID" in cs:
+        sop_class = cs.AffectedSOPClassUID.name
+        s.append(f"Affected SOP Class UID        : {sop_class}")
+
+    if "AffectedSOPInstanceUID" in cs:
+        sop_instance = cs.AffectedSOPInstanceUID
+        s.append(f"Affected SOP Instance UID     : {sop_instance}")
+
+    s.append(f"Attribute List                : {attr_list}")
+    s.append(f"Status                        : 0x{cs.Status:04X}")
+    s.append(f"{' END DIMSE MESSAGE ':=^76}")
+    for line in s:
+        LOGGER.debug(line)
+    return s
 
 
 def _send_n_delete_rq(event: "Event") -> list[str]:
@@ -2023,7 +2120,26 @@ def _recv_n_event_report_rq(event: "Event") -> list[str]:
     event : events.Event
         The evt.EVT_DIMSE_RECV event that occurred.
     """
-    return []
+    msg = event.message
+    cs = msg.command_set
+
+    evt_info = "None"
+    if msg.data_set and msg.data_set.getvalue() != b"":
+        evt_info = "Present"
+
+    s = [
+        f"{' INCOMING DIMSE MESSAGE ':=^76}",
+        "Message Type                  : N-EVENT-REPORT RQ",
+        f"Message ID                    : {cs.MessageID}",
+        f"Affected SOP Class UID        : {cs.AffectedSOPClassUID.name}",
+        f"Affected SOP Instance UID     : {cs.AffectedSOPInstanceUID}",
+        f"Event Type ID                 : {cs.EventTypeID}",
+        f"Event Information             : {evt_info}",
+        f"{' END DIMSE MESSAGE ':=^76}",
+    ]
+    for line in s:
+        LOGGER.debug(line)
+    return s
 
 
 def _recv_n_event_report_rsp(event: "Event") -> list[str]:
@@ -2034,7 +2150,33 @@ def _recv_n_event_report_rsp(event: "Event") -> list[str]:
     event : events.Event
         The evt.EVT_DIMSE_RECV event that occurred.
     """
-    return []
+    msg = event.message
+    cs = msg.command_set
+
+    evt_reply = "None"
+    if msg.data_set and msg.data_set.getvalue() != b"":
+        evt_reply = "Present"
+
+    s = [
+        f"{' INCOMING DIMSE MESSAGE ':=^76}",
+        "Message Type                  : N-EVENT-REPORT RSP",
+        f"Message ID Being Responded To : {cs.MessageIDBeingRespondedTo}",
+    ]
+    if "AffectedSOPClassUID" in cs:
+        sop_class = cs.AffectedSOPClassUID.name
+        s.append(f"Affected SOP Class UID        : {sop_class}")
+    if "AffectedSOPInstanceUID" in cs:
+        sop_instance = cs.AffectedSOPInstanceUID
+        s.append(f"Affected SOP Instance UID     : {sop_instance}")
+    if "EventTypeID" in cs:
+        s.append(f"Event Type ID                 : {cs.EventTypeID}")
+
+    s.append(f"Event Reply                   : {evt_reply}")
+    s.append(f"Status                        : 0x{cs.Status:04X}")
+    s.append(f"{' END DIMSE MESSAGE ':=^76}")
+    for line in s:
+        LOGGER.debug(line)
+    return s
 
 
 def _recv_n_get_rq(event: "Event") -> list[str]:
@@ -2045,7 +2187,25 @@ def _recv_n_get_rq(event: "Event") -> list[str]:
     event : events.Event
         The evt.EVT_DIMSE_RECV event that occurred.
     """
-    return []
+    msg = event.message
+    cs = msg.command_set
+
+    nr_attr = "None"
+    if "AttributeIdentifierList" in cs:
+        nr_attr = f"({len(cs.AttributeIdentifierList)} Attribute Tag(s))"
+
+    s = [
+        f"{' INCOMING DIMSE MESSAGE ':=^76}",
+        "Message Type                  : N-GET RQ",
+        f"Message ID                    : {cs.MessageID}",
+        f"Requested SOP Class UID       : {cs.RequestedSOPClassUID}",
+        f"Requested SOP Instance UID    : {cs.RequestedSOPInstanceUID}",
+        f"Attribute Identifier List     : {nr_attr}",
+        f"{' END DIMSE MESSAGE ':=^76}",
+    ]
+    for line in s:
+        LOGGER.debug(line)
+    return s
 
 
 def _recv_n_get_rsp(event: "Event") -> list[str]:
@@ -2093,7 +2253,25 @@ def _recv_n_set_rq(event: "Event") -> list[str]:
     event : events.Event
         The evt.EVT_DIMSE_RECV event that occurred.
     """
-    return []
+    msg = event.message
+    cs = msg.command_set
+
+    mod_list = "None"
+    if msg.data_set and msg.data_set.getvalue() != b"":
+        mod_list = "Present"
+
+    s = [
+        f"{' INCOMING DIMSE MESSAGE ':=^76}",
+        "Message Type                  : N-SET RQ",
+        f"Message ID                    : {cs.MessageID}",
+        f"Requested SOP Class UID       : {cs.RequestedSOPClassUID}",
+        f"Requested SOP Instance UID    : {cs.RequestedSOPInstanceUID}",
+        f"Modification List             : {mod_list}",
+        f"{' END DIMSE MESSAGE ':=^76}",
+    ]
+    for line in s:
+        LOGGER.debug(line)
+    return s
 
 
 def _recv_n_set_rsp(event: "Event") -> list[str]:
@@ -2104,7 +2282,31 @@ def _recv_n_set_rsp(event: "Event") -> list[str]:
     event : events.Event
         The evt.EVT_DIMSE_RECV event that occurred.
     """
-    return []
+    msg = event.message
+    cs = msg.command_set
+
+    attr_list = "None"
+    if msg.data_set and msg.data_set.getvalue() != b"":
+        attr_list = "Present"
+
+    s = [
+        f"{' INCOMING DIMSE MESSAGE ':=^76}",
+        "Message Type                  : N-SET RSP",
+        f"Message ID Being Responded To : {cs.MessageIDBeingRespondedTo}",
+    ]
+    if "AffectedSOPClassUID" in cs:
+        sop_class = cs.AffectedSOPClassUID.name
+        s.append(f"Affected SOP Class UID        : {sop_class}")
+    if "AffectedSOPInstanceUID" in cs:
+        sop_instance = cs.AffectedSOPInstanceUID
+        s.append(f"Affected SOP Instance UID     : {sop_instance}")
+
+    s.append(f"Attribute List                : {attr_list}")
+    s.append(f"Status                        : 0x{cs.Status:04X}")
+    s.append(f"{' END DIMSE MESSAGE ':=^76}")
+    for line in s:
+        LOGGER.debug(line)
+    return s
 
 
 def _recv_n_action_rq(event: "Event") -> list[str]:
@@ -2115,7 +2317,26 @@ def _recv_n_action_rq(event: "Event") -> list[str]:
     event : events.Event
         The evt.EVT_DIMSE_RECV event that occurred.
     """
-    return []
+    msg = event.message
+    cs = msg.command_set
+
+    action_info = "None"
+    if msg.data_set and msg.data_set.getvalue() != b"":
+        action_info = "Present"
+
+    s = [
+        f"{' INCOMING DIMSE MESSAGE ':=^76}",
+        "Message Type                  : N-ACTION RQ",
+        f"Message ID                    : {cs.MessageID}",
+        f"Requested SOP Instance UID    : {cs.RequestedSOPInstanceUID}",
+        f"Action Type ID                : {cs.ActionTypeID}",
+        f"Action Information            : {action_info}",
+        f"{' END DIMSE MESSAGE ':=^76}",
+    ]
+    for line in s:
+        LOGGER.debug(line)
+
+    return s
 
 
 def _recv_n_action_rsp(event: "Event") -> list[str]:
@@ -2126,7 +2347,33 @@ def _recv_n_action_rsp(event: "Event") -> list[str]:
     event : events.Event
         The evt.EVT_DIMSE_RECV event that occurred.
     """
-    return []
+    msg = event.message
+    cs = msg.command_set
+
+    action_reply = "None"
+    if msg.data_set and msg.data_set.getvalue() != b"":
+        action_reply = "Present"
+
+    s = [
+        f"{' INCOMING DIMSE MESSAGE ':=^76}",
+        "Message Type                  : N-ACTION RSP",
+        f"Message ID Being Responded To : {cs.MessageIDBeingRespondedTo}",
+    ]
+    if "AffectedSOPClassUID" in cs:
+        sop_class = cs.AffectedSOPClassUID.name
+        s.append(f"Affected SOP Class UID        : {sop_class}")
+
+    if "AffectedSOPInstanceUID" in cs:
+        sop_instance = cs.AffectedSOPInstanceUID
+        s.append(f"Affected SOP Instance UID     : {sop_instance}")
+
+    s.append(f"Action Type ID                : {cs.ActionTypeID}")
+    s.append(f"Action Reply                  : {action_reply}")
+    s.append(f"Status                        : 0x{cs.Status:04X}")
+    s.append(f"{' END DIMSE MESSAGE ':=^76}")
+    for line in s:
+        LOGGER.debug(line)
+    return s
 
 
 def _recv_n_create_rq(event: "Event") -> list[str]:
@@ -2137,7 +2384,34 @@ def _recv_n_create_rq(event: "Event") -> list[str]:
     event : events.Event
         The evt.EVT_DIMSE_RECV event that occurred.
     """
-    return []
+    msg = event.message
+    cs = msg.command_set
+
+    attr_list = "None"
+    if msg.data_set and msg.data_set.getvalue() != b"":
+        attr_list = "Present"
+
+    s = [
+        f"{' INCOMING DIMSE MESSAGE ':=^76}",
+        "Message Type                  : N-CREATE RQ",
+        f"Message ID                    : {cs.MessageID}",
+        f"Affected SOP Class UID        : {cs.AffectedSOPClassUID}",
+    ]
+    sop_instance = "None"
+    if "AffectedSOPInstanceUID" in cs:
+        sop_instance = cs.AffectedSOPInstanceUID
+
+    s.extend(
+        [
+            f"Affected SOP Instance UID     : {sop_instance}",
+            f"Attribute List                : {attr_list}",
+            f"{' END DIMSE MESSAGE ':=^76}",
+        ]
+    )
+    for line in s:
+        LOGGER.debug(line)
+
+    return s
 
 
 def _recv_n_create_rsp(event: "Event") -> list[str]:
@@ -2148,7 +2422,32 @@ def _recv_n_create_rsp(event: "Event") -> list[str]:
     event : events.Event
         The evt.EVT_DIMSE_RECV event that occurred.
     """
-    return []
+    msg = event.message
+    cs = msg.command_set
+
+    attr_list = "None"
+    if msg.data_set and msg.data_set.getvalue() != b"":
+        attr_list = "Present"
+
+    s = [
+        f"{' INCOMING DIMSE MESSAGE ':=^76}",
+        "Message Type                  : N-CREATE RSP",
+        f"Message ID Being Responded To : {cs.MessageIDBeingRespondedTo}",
+    ]
+    if "AffectedSOPClassUID" in cs:
+        sop_class = cs.AffectedSOPClassUID.name
+        s.append(f"Affected SOP Class UID        : {sop_class}")
+
+    if "AffectedSOPInstanceUID" in cs:
+        sop_instance = cs.AffectedSOPInstanceUID
+        s.append(f"Affected SOP Instance UID     : {sop_instance}")
+
+    s.append(f"Attribute List                : {attr_list}")
+    s.append(f"Status                        : 0x{cs.Status:04X}")
+    s.append(f"{' END DIMSE MESSAGE ':=^76}")
+    for line in s:
+        LOGGER.debug(line)
+    return s
 
 
 def _recv_n_delete_rq(event: "Event") -> list[str]:
@@ -2159,7 +2458,20 @@ def _recv_n_delete_rq(event: "Event") -> list[str]:
     event : events.Event
         The evt.EVT_DIMSE_RECV event that occurred.
     """
-    return []
+    msg = event.message
+    cs = msg.command_set
+
+    s = [
+        f"{' INCOMING DIMSE MESSAGE ':=^76}",
+        "Message Type                  : N-DELETE RQ",
+        f"Message ID                    : {cs.MessageID}",
+        f"Requested SOP Class UID       : {cs.RequestedSOPClassUID}",
+        f"Requested SOP Instance UID    : {cs.RequestedSOPInstanceUID}",
+        f"{' END DIMSE MESSAGE ':=^76}",
+    ]
+    for line in s:
+        LOGGER.debug(line)
+    return s
 
 
 def _recv_n_delete_rsp(event: "Event") -> list[str]:
@@ -2170,7 +2482,26 @@ def _recv_n_delete_rsp(event: "Event") -> list[str]:
     event : events.Event
         The evt.EVT_DIMSE_RECV event that occurred.
     """
-    return []
+    msg = event.message
+    cs = msg.command_set
+
+    s = [
+        f"{' INCOMING DIMSE MESSAGE ':=^76}",
+        "Message Type                  : N-DELETE RSP",
+        f"Message ID Being Responded To : {cs.MessageIDBeingRespondedTo}",
+    ]
+    if "AffectedSOPClassUID" in cs:
+        sop_class = cs.AffectedSOPClassUID.name
+        s.append(f"Affected SOP Class UID        : {sop_class}")
+    if "AffectedSOPInstanceUID" in cs:
+        sop_instance = cs.AffectedSOPInstanceUID
+        s.append(f"Affected SOP Instance UID     : {sop_instance}")
+
+    s.append(f"Status                        : 0x{cs.Status:04X}")
+    s.append(f"{' END DIMSE MESSAGE ':=^76}")
+    for line in s:
+        LOGGER.debug(line)
+    return s
 
 
 StatusType = int | Dataset
@@ -3204,6 +3535,13 @@ def doc_handle_create(event: "Event", *args: Sequence[Any]) -> UserReturnType:
         response's *Attribute List* conformant to the specifications in the
         corresponding Service Class.
 
+        If the N-CREATE-RQ doesn't include a value for *Affected SOP Instance UID* and
+        the `status` is 0x0000 (Success) then `dataset` should include an (0000,1000)
+        *Affected SOP Instance UID* element (see :dcm:`Part 7, Section 10.1.5.1.4 of
+        the DICOM Standard<part07/chapter_10.html#sect_10.1.5>`). The element value
+        will be used to set the corresponding N-ACTION-RSP parameter value but will be
+        removed from the *Attribute List* dataset prior to sending.
+
         If the status category is not 'Success' or 'Warning' then ``None``.
 
     Raises
diff --git a/pynetdicom/service_class.py b/pynetdicom/service_class.py
index 797d9b9f2..93e9e51f8 100644
--- a/pynetdicom/service_class.py
+++ b/pynetdicom/service_class.py
@@ -577,7 +577,7 @@ class ServiceClass:
         rsp.ActionTypeID = req.ActionTypeID
 
         with attempt(rsp, self.dimse, cx_id) as ctx:
-            ctx.error_msg = "Exception in the handler bound to 'evt.EVT_N_ACTION"
+            ctx.error_msg = "Exception in the handler bound to 'evt.EVT_N_ACTION'"
             ctx.error_status = 0x0110
             user_response = evt.trigger(
                 self.assoc,
@@ -729,7 +729,7 @@ class ServiceClass:
         rsp.AffectedSOPInstanceUID = req.AffectedSOPInstanceUID
 
         with attempt(rsp, self.dimse, cx_id) as ctx:
-            ctx.error_msg = "Exception in the handler bound to 'evt.EVT_N_CREATE"
+            ctx.error_msg = "Exception in the handler bound to 'evt.EVT_N_CREATE'"
             ctx.error_status = 0x0110
             user_response = evt.trigger(
                 self.assoc,
@@ -754,6 +754,22 @@ class ServiceClass:
             self.dimse.send_msg(rsp, cx_id)
             return
 
+        # Part 7, 10.1.5.1.4 - Affected SOP Instance UID is required if not in the -RQ
+        if status[0] == STATUS_SUCCESS and req.AffectedSOPInstanceUID is None:
+            if isinstance(ds, Dataset) and "AffectedSOPInstanceUID" in ds:
+                rsp.AffectedSOPInstanceUID = ds.AffectedSOPInstanceUID
+                del ds.AffectedSOPInstanceUID
+            else:
+                LOGGER.error(
+                    "The N-CREATE-RQ has no 'Affected SOP Instance UID' value and the "
+                    "'evt.EVT_N_CREATE' handler doesn't include one in the 'Attribute "
+                    "List' dataset"
+                )
+                # Processing failure
+                rsp.Status = 0x0110
+                self.dimse.send_msg(rsp, cx_id)
+                return
+
         if status[0] in (STATUS_SUCCESS, STATUS_WARNING) and ds:
             # If Success or Warning then there **may** be a dataset
             transfer_syntax = context.transfer_syntax[0]
@@ -846,7 +862,7 @@ class ServiceClass:
         rsp.AffectedSOPInstanceUID = req.RequestedSOPInstanceUID
 
         with attempt(rsp, self.dimse, cx_id) as ctx:
-            ctx.error_msg = "Exception in the handler bound to 'evt.EVT_N_DELETE"
+            ctx.error_msg = "Exception in the handler bound to 'evt.EVT_N_DELETE'"
             ctx.error_status = 0x0110
             status = evt.trigger(
                 self.assoc,
@@ -951,7 +967,7 @@ class ServiceClass:
         rsp.EventTypeID = req.EventTypeID
 
         with attempt(rsp, self.dimse, cx_id) as ctx:
-            ctx.error_msg = "Exception in the handler bound to 'evt.EVT_N_EVENT_REPORT"
+            ctx.error_msg = "Exception in the handler bound to 'evt.EVT_N_EVENT_REPORT'"
             ctx.error_status = 0x0110
             user_response = evt.trigger(
                 self.assoc,


Test patch:
diff --git a/pynetdicom/tests/test_service_n.py b/pynetdicom/tests/test_service_n.py
index 8336a9fb1..417a6bfa0 100644
--- a/pynetdicom/tests/test_service_n.py
+++ b/pynetdicom/tests/test_service_n.py
@@ -1,6 +1,7 @@
 """Tests for the DIMSE-N Service Classes."""
 
 from io import BytesIO
+import logging
 import time
 
 import pytest
@@ -2241,3 +2242,213 @@ class TestNEventReport:
         assert e.event_information.PatientName == "Test2"
 
         ner_scp.shutdown()
+
+
+class TestNCreate:
+    """Functional tests for N-CREATE services."""
+
+    def setup_method(self):
+        """Run prior to each test"""
+        self.ae = None
+
+    def teardown_method(self):
+        """Clear any active threads"""
+        if self.ae:
+            self.ae.shutdown()
+
+    def test_rq_instance_uid(self, caplog):
+        """Test the -RQ sending an Affected SOP Instance UID."""
+
+        events = []
+
+        def handle_create(event):
+            ds = Dataset()
+            ds.PatientName = "Test3"
+
+            return 0x0000, ds
+
+        hh = [(evt.EVT_N_CREATE, handle_create)]
+
+        self.ae = ae = AE()
+        ae.acse_timeout = 5
+        ae.dimse_timeout = 5
+        ae.network_timeout = 5
+        ae.add_supported_context(BasicFilmSession)
+        scp = ae.start_server(("localhost", 11112), block=False, evt_handlers=hh)
+
+        ae.add_requested_context(BasicFilmSession)
+        assoc = ae.associate("localhost", 11112)
+        assert assoc.is_established
+
+        with caplog.at_level(logging.DEBUG, logger="pynetdicom"):
+            status, attr = assoc.send_n_create(None, BasicFilmSession, "1.2.3.4")
+
+        assoc.release()
+        assert assoc.is_released
+
+        scp.shutdown()
+
+        assert status.Status == 0x0000
+        assert (
+            "The N-CREATE-RQ has no 'Affected SOP Instance UID' value and the "
+            "'evt.EVT_N_CREATE' handler doesn't include one in the 'Attribute List' "
+            "dataset"
+        ) not in caplog.text
+        assert "Affected SOP Instance UID     : 1.2.3.4" in caplog.text
+
+    def test_rq_no_instance_uid_success_error(self, caplog):
+        """Test the -RQ not sending an Affected SOP Instance UID."""
+
+        events = []
+
+        def handle_create(event):
+            ds = Dataset()
+            ds.PatientName = "Test3"
+
+            return 0x0000, ds
+
+        hh = [(evt.EVT_N_CREATE, handle_create)]
+
+        self.ae = ae = AE()
+        ae.acse_timeout = 5
+        ae.dimse_timeout = 5
+        ae.network_timeout = 5
+        ae.add_supported_context(BasicFilmSession)
+        scp = ae.start_server(("localhost", 11112), block=False, evt_handlers=hh)
+
+        ae.add_requested_context(BasicFilmSession)
+        assoc = ae.associate("localhost", 11112)
+        assert assoc.is_established
+
+        with caplog.at_level(logging.DEBUG, logger="pynetdicom"):
+            status, attr = assoc.send_n_create(None, BasicFilmSession, None)
+
+        assoc.release()
+        assert assoc.is_released
+
+        scp.shutdown()
+
+        assert status.Status == 0x0110
+        assert (
+            "The N-CREATE-RQ has no 'Affected SOP Instance UID' value and the "
+            "'evt.EVT_N_CREATE' handler doesn't include one in the 'Attribute List' "
+            "dataset"
+        ) in caplog.text
+        assert "Affected SOP Instance UID     : None" in caplog.text
+
+    def test_rq_no_instance_uid_success_no_ds_error(self, caplog):
+        """Test the -RQ not sending an Affected SOP Instance UID."""
+
+        events = []
+
+        def handle_create(event):
+            return 0x0000, None
+
+        hh = [(evt.EVT_N_CREATE, handle_create)]
+
+        self.ae = ae = AE()
+        ae.acse_timeout = 5
+        ae.dimse_timeout = 5
+        ae.network_timeout = 5
+        ae.add_supported_context(BasicFilmSession)
+        scp = ae.start_server(("localhost", 11112), block=False, evt_handlers=hh)
+
+        ae.add_requested_context(BasicFilmSession)
+        assoc = ae.associate("localhost", 11112)
+        assert assoc.is_established
+
+        with caplog.at_level(logging.ERROR, logger="pynetdicom"):
+            status, attr = assoc.send_n_create(None, BasicFilmSession, None)
+
+        assoc.release()
+        assert assoc.is_released
+
+        scp.shutdown()
+
+        assert status.Status == 0x0110
+        assert (
+            "The N-CREATE-RQ has no 'Affected SOP Instance UID' value and the "
+            "'evt.EVT_N_CREATE' handler doesn't include one in the 'Attribute List' "
+            "dataset"
+        ) in caplog.text
+
+    def test_rq_no_instance_uid_success_ds_value(self, caplog):
+        """Test the -RQ not sending an Affected SOP Instance UID."""
+
+        events = []
+
+        def handle_create(event):
+            ds = Dataset()
+            ds.PatientName = "Test3"
+            ds.AffectedSOPInstanceUID = "1.2.3.4"
+
+            return 0x0000, ds
+
+        hh = [(evt.EVT_N_CREATE, handle_create)]
+
+        self.ae = ae = AE()
+        ae.acse_timeout = 5
+        ae.dimse_timeout = 5
+        ae.network_timeout = 5
+        ae.add_supported_context(BasicFilmSession)
+        scp = ae.start_server(("localhost", 11112), block=False, evt_handlers=hh)
+
+        ae.add_requested_context(BasicFilmSession)
+        assoc = ae.associate("localhost", 11112)
+        assert assoc.is_established
+
+        with caplog.at_level(logging.DEBUG, logger="pynetdicom"):
+            status, attr = assoc.send_n_create(None, BasicFilmSession, None)
+
+        assoc.release()
+        assert assoc.is_released
+
+        scp.shutdown()
+
+        assert status.Status == 0x0000
+        assert (
+            "The N-CREATE-RQ has no 'Affected SOP Instance UID' value and the "
+            "'evt.EVT_N_CREATE' handler doesn't include one in the 'Attribute List' "
+            "dataset"
+        ) not in caplog.text
+        assert "Affected SOP Instance UID     : 1.2.3.4" in caplog.text
+        assert "AffectedSOPInstanceUID" not in attr
+
+    def test_rq_no_instance_uid_failure(self, caplog):
+        """Test the -RQ not sending an Affected SOP Instance UID."""
+
+        events = []
+
+        def handle_create(event):
+            ds = Dataset()
+            ds.PatientName = "Test3"
+
+            return 0x0111, ds
+
+        hh = [(evt.EVT_N_CREATE, handle_create)]
+
+        self.ae = ae = AE()
+        ae.acse_timeout = 5
+        ae.dimse_timeout = 5
+        ae.network_timeout = 5
+        ae.add_supported_context(BasicFilmSession)
+        scp = ae.start_server(("localhost", 11112), block=False, evt_handlers=hh)
+
+        ae.add_requested_context(BasicFilmSession)
+        assoc = ae.associate("localhost", 11112)
+        assert assoc.is_established
+
+        with caplog.at_level(logging.ERROR, logger="pynetdicom"):
+            status, attr = assoc.send_n_create(None, BasicFilmSession, None)
+
+        assoc.release()
+        assert assoc.is_released
+
+        scp.shutdown()
+
+        assert status.Status == 0x0111
+        assert (
+            "The N-CREATE-RQ has no 'Affected SOP Instance UID' value and the "
+            "'evt.EVT_N_CREATE' handler doesn't include one in the 'Attribute List' "
+            "dataset"
+        ) not in caplog.text