class Location(ODMElement, LastUpdateMixin):
    """
    A physical location -- typically a clinical research site or a sponsor's office.
    """
    def __init__(self, oid, name,
                 location_type=None,
                 metadata_versions=None):
        """
        :param str oid: OID for the Location, referenced in :class:`LocationRef`
        :param str name: Name for the Location
        :param rwslib.builder_constants.LocationType location_type: Type for this Location
        :param list(MetaDataVersionRef) metadata_versions: The :class:`MetaDataVersionRef` for this Location
        """
        super(Location, self).__init__()
        self.oid = oid
        self.name = name
        self._location_type = None
        if location_type:
            self.location_type = location_type
        self.metadata_versions = []
        if metadata_versions:
            if isinstance(metadata_versions, (tuple, list)):
                for mdv in metadata_versions:
                    self << mdv
            elif isinstance(metadata_versions, (MetaDataVersionRef,)):
                self << metadata_versions

    @property
    def location_type(self):
        return self._location_type

    @location_type.setter
    def location_type(self, value):
        if not isinstance(value, (LocationType,)):
            raise ValueError("{} is not a LocationType".format(type(value)))
        self._location_type = value

    def build(self, builder):
        """Build XML by appending to builder"""
        params = dict(OID=self.oid,
                      Name=self.name)
        if self.location_type:
            params.update(dict(LocationType=self.location_type.value))
        # mixins
        self.mixin()
        self.mixin_params(params)
        builder.start("Location", params)
        for mdv in self.metadata_versions:
            mdv.build(builder)
        builder.end("Location")

    def __lshift__(self, other):
        """Override << operator"""
        if not isinstance(other, (MetaDataVersionRef,)):
            raise ValueError('{0} cannot accept a {1} as a child element'.format(self.__class__.__name__,
                                                                                 other.__class__.__name__))

        self.set_list_attribute(other, MetaDataVersionRef, 'metadata_versions')

        return other class QueryStatusType(enum.Enum):
    """
    MdsolQuery action type
    Applies to a :class:`MdsolQuery`
    """
    Open = "Open"
    Cancelled = "Cancelled"
    Answered = "Answered"
    Forwarded = "Forwarded"
    Closed = "Closed" class ClinicalData(ODMElement, LastUpdateMixin):
    """Models the ODM ClinicalData object"""

    def __init__(
        self, projectname, environment, metadata_version_oid="1", annotations=None
    ):
        """
        :param projectname: Name of Project in Medidata Rave
        :param environment: Rave Study Enviroment
        :param metadata_version_oid: MetadataVersion OID
        """
        super(ClinicalData, self).__init__()
        self.projectname = projectname
        self.environment = environment
        self.metadata_version_oid = metadata_version_oid
        #: collection of :class:`SubjectData` for the ClinicalData Element
        self.subject_data = []
        self.annotations = annotations

    def build(self, builder):
        """Build XML by appending to builder"""
        params = dict(
            MetaDataVersionOID=str(self.metadata_version_oid),
            StudyOID="%s (%s)" % (self.projectname, self.environment),
        )

        # mixins
        self.mixin_params(params)

        builder.start("ClinicalData", params)
        # Ask children
        if self.subject_data:
            for subject in self.subject_data:
                subject.build(builder)
        # Add the Annotations
        if self.annotations is not None:
            self.annotations.build(builder)
        builder.end("ClinicalData")

    def __lshift__(self, other):
        """Override << operator"""
        if not isinstance(other, (SubjectData, Annotations)):
            raise ValueError(
                "ClinicalData object can only receive SubjectData or Annotations object"
            )
        self.set_list_attribute(other, SubjectData, "subject_data")
        self.set_single_attribute(other, Annotations, "annotations")
        return other class FormData(TransactionalElement, LastUpdateMixin, MilestoneMixin):
    """Models the ODM FormData object"""

    ALLOWED_TRANSACTION_TYPES = ["Insert", "Update", "Upsert", "Context", "Remove"]

    def __init__(self, formoid, transaction_type=None, form_repeat_key=None):
        """
        :param str formoid: :class:`FormDef` OID
        :param str transaction_type: Transaction Type for Data (one of **Insert**, **Update**, **Upsert**, **Context**, **Remove**)
        :param str form_repeat_key: Repeat Key for FormData
        """
        super(FormData, self).__init__(transaction_type)
        self.formoid = formoid
        self.form_repeat_key = form_repeat_key
        self.itemgroups = []
        #: :class:`Signature` for FormData
        self.signature = None  # type: Signature
        #: Collection of :class:`Annotation` for FormData - *Not supported by Rave*
        self.annotations = []  # type: list(Annotation)

    def build(self, builder):
        """Build XML by appending to builder
        :Example:
        <FormData FormOID="MH" TransactionType="Update">

        """
        params = dict(FormOID=self.formoid)

        if self.transaction_type is not None:
            params["TransactionType"] = self.transaction_type

        if self.form_repeat_key is not None:
            params["FormRepeatKey"] = str(self.form_repeat_key)

        # mixins
        self.mixin()
        self.mixin_params(params)

        builder.start("FormData", params)

        # Ask children
        for itemgroup in self.itemgroups:
            itemgroup.build(builder, self.formoid)

        if self.signature is not None:
            self.signature.build(builder)

        for annotation in self.annotations:
            annotation.build(builder)

        builder.end("FormData")

    def __lshift__(self, other):
        """Override << operator"""
        if not isinstance(other, (Signature, ItemGroupData, Annotation)):
            raise ValueError(
                "FormData object can only receive ItemGroupData, Signature or Annotation objects (not '{}')".format(
                    other
                )
            )
        self.set_list_attribute(other, ItemGroupData, "itemgroups")
        self.set_list_attribute(other, Annotation, "annotations")
        self.set_single_attribute(other, Signature, "signature")
        return other class ItemData(TransactionalElement, LastUpdateMixin, MilestoneMixin):
    """Models the ODM ItemData object"""

    ALLOWED_TRANSACTION_TYPES = ["Insert", "Update", "Upsert", "Context", "Remove"]

    def __init__(
        self,
        itemoid,
        value,
        specify_value=None,
        transaction_type=None,
        lock=None,
        freeze=None,
        verify=None,
    ):
        """
        :param str itemoid: OID for the matching :class:`ItemDef`
        :param str value: Value for the the ItemData
        :param str specify_value: 'If other, specify' value - *Rave specific attribute*
        :param str transaction_type: Transaction Type for Data (one of **Insert**, **Update**, **Upsert**, **Context**, **Remove**)
        :param bool lock: Lock the DataPoint? - *Rave specific attribute*
        :param bool freeze: Freeze the DataPoint? - *Rave specific attribute*
        :param bool verify: Verify the DataPoint? - *Rave specific attribute*
        """
        super(self.__class__, self).__init__(transaction_type)
        self.itemoid = itemoid
        self.value = value

        self.specify_value = specify_value
        self.lock = lock
        self.freeze = freeze
        self.verify = verify
        #: the corresponding :class:`AuditRecord` for the DataPoint
        self.audit_record = None
        #: the list of :class:`MdsolQuery` on the DataPoint  - *Rave Specific Attribute*
        self.queries = []
        #: the list of :class:`Annotation` on the DataPoint - *Not supported by Rave*
        self.annotations = []
        #: the corresponding :class:`MeasurementUnitRef` for the DataPoint
        self.measurement_unit_ref = None
        #: the list of :class:`MdsolProtocolDeviation` references on the DataPoint - *Rave Specific Attribute*
        self.deviations = []

    def build(self, builder):
        """
        Build XML by appending to builder
        """
        params = dict(ItemOID=self.itemoid)

        if self.transaction_type is not None:
            params["TransactionType"] = self.transaction_type

        if self.value in [None, ""]:
            params["IsNull"] = "Yes"
        else:
            params["Value"] = str(self.value)

        if self.specify_value is not None:
            params["mdsol:SpecifyValue"] = self.specify_value

        if self.lock is not None:
            params["mdsol:Lock"] = bool_to_yes_no(self.lock)

        if self.freeze is not None:
            params["mdsol:Freeze"] = bool_to_yes_no(self.freeze)

        if self.verify is not None:
            params["mdsol:Verify"] = bool_to_yes_no(self.verify)

        # mixins
        self.mixin()
        self.mixin_params(params)

        builder.start("ItemData", params)

        if self.audit_record is not None:
            self.audit_record.build(builder)

        # Measurement unit ref must be after audit record or RWS complains
        if self.measurement_unit_ref is not None:
            self.measurement_unit_ref.build(builder)

        for query in self.queries:  # type: MdsolQuery
            query.build(builder)

        for deviation in self.deviations:  # type: MdsolProtocolDeviation
            deviation.build(builder)

        for annotation in self.annotations:  # type: Annotation
            annotation.build(builder)

        builder.end("ItemData")

    def __lshift__(self, other):
        if not isinstance(
            other,
            (
                MeasurementUnitRef,
                AuditRecord,
                MdsolQuery,
                Annotation,
                MdsolProtocolDeviation,
            ),
        ):
            raise ValueError(
                "ItemData object can only receive MeasurementUnitRef, AuditRecord, Annotation,"
                "MdsolProtocolDeviation or MdsolQuery objects"
            )
        self.set_single_attribute(other, MeasurementUnitRef, "measurement_unit_ref")
        self.set_single_attribute(other, AuditRecord, "audit_record")
        self.set_list_attribute(other, MdsolQuery, "queries")
        self.set_list_attribute(other, MdsolProtocolDeviation, "deviations")
        self.set_list_attribute(other, Annotation, "annotations")
        return other class ItemGroupData(TransactionalElement, LastUpdateMixin, MilestoneMixin):
    """
    Models the ODM ItemGroupData object.

    .. note:: No name for the ItemGroupData element is required. This is built automatically by the form.
    """

    ALLOWED_TRANSACTION_TYPES = ["Insert", "Update", "Upsert", "Context", "Remove"]

    def __init__(
        self,
        itemgroupoid=None,
        transaction_type=None,
        item_group_repeat_key=None,
        whole_item_group=False,
        annotations=None,
    ):
        """
        :param str transaction_type: Transaction Type for Data (one of **Insert**, **Update**, **Upsert**, **Context**, **Remove**)
        :param int item_group_repeat_key: RepeatKey for the ItemGroupData
        :param bool whole_item_group: Is this the entire ItemGroupData, or just parts? - *Rave specific attribute*
        :param annotations: Annotation for the ItemGroup - *Not supported by Rave*
        :type annotations: list(Annotation) or Annotation
        """
        super(self.__class__, self).__init__(transaction_type)
        self.item_group_repeat_key = item_group_repeat_key
        self.whole_item_group = whole_item_group
        self.items = OrderedDict()
        self.annotations = []
        if annotations:
            # Add the annotations
            if isinstance(annotations, Annotation):
                self << annotations
            elif isinstance(annotations, list):
                for annotation in annotations:
                    self << annotation
        #: :class:`Signature` for ItemGroupData
        self.signature = None
        self.itemgroupoid = itemgroupoid

    def build(self, builder, formname=None):
        """Build XML by appending to builder"""

        params = dict(ItemGroupOID=formname if formname else self.itemgroupoid)

        if self.transaction_type is not None:
            params["TransactionType"] = self.transaction_type

        if self.item_group_repeat_key is not None:
            params["ItemGroupRepeatKey"] = str(
                self.item_group_repeat_key
            )  # may be @context for transaction type upsert or context

        params["mdsol:Submission"] = (
            "WholeItemGroup" if self.whole_item_group else "SpecifiedItemsOnly"
        )

        # mixins
        self.mixin()
        self.mixin_params(params)

        builder.start("ItemGroupData", params)

        # Ask children
        for item in self.items.values():
            item.build(builder)

        # Add annotations
        for annotation in self.annotations:
            annotation.build(builder)

        # Add the signature if it exists
        if self.signature is not None:
            self.signature.build(builder)
        builder.end("ItemGroupData")

    def __lshift__(self, other):
        """Override << operator"""
        if not isinstance(other, (ItemData, Annotation, Signature)):
            raise ValueError(
                "ItemGroupData object can only receive ItemData, Signature or Annotation objects"
            )

        self.set_list_attribute(other, Annotation, "annotations")
        self.set_single_attribute(other, Signature, "signature")
        if isinstance(other, ItemData):
            if other.itemoid in self.items:
                raise ValueError(
                    "ItemGroupData object with that itemoid is already in the ItemGroupData object"
                )
            self.items[other.itemoid] = other
        return other class MdsolQuery(ODMElement):
    """
    MdsolQuery extension element for Queries at item level only

    .. note:: This is a Medidata Rave specific extension
    """

    def __init__(
        self,
        value=None,
        query_repeat_key=None,
        recipient=None,
        status=None,
        requires_response=None,
        response=None,
        preceding_query_repeat_key=None,
        ):
        """
        :param str value: Query Value
        :param int query_repeat_key: Repeat key for Query
        :param str recipient: Recipient for Query (Marking Group)
        :param QueryStatusType status: Query status
        :param bool requires_response: Does this Query need a response?
        :param response: Query response (if any)
        :type response: str or None
        :param int preceding_query_repeat_key: Query Key returned by OpenQuery call
        """
        self.value = value
        self.query_repeat_key = query_repeat_key
        self.recipient = recipient
        self._status = None
        self.status = status
        self.requires_response = requires_response
        self.response = response
        self.preceding_query_repeat_key = preceding_query_repeat_key

    @property
    def status(self):
        """Query Status"""
        return self._status

    @status.setter
    def status(self, value):
        """Set Query Status"""
        if value is not None:
            if not isinstance(value, QueryStatusType):
                raise AttributeError(
                    "%s action type is invalid in mdsol:Query." % (value,)
                )
        self._status = value

    def build(self, builder):
        """
        Build XML by appending to builder
        """
        params = {}

        if self.value is not None:
            params["Value"] = str(self.value)

        if self.query_repeat_key is not None:
            params["QueryRepeatKey"] = str(self.query_repeat_key)

        if self.recipient is not None:
            params["Recipient"] = str(self.recipient)

        if self.status is not None:
            params["Status"] = self.status.value

        if self.requires_response is not None:
            params["RequiresResponse"] = bool_to_yes_no(self.requires_response)

        # When closing a query
        if self.response is not None:
            params["Response"] = str(self.response)

        # When re-opening a query
        if self.preceding_query_repeat_key is not None:
            params["PrecedingQueryRepeatKey"] = str(self.preceding_query_repeat_key)

        builder.start("mdsol:Query", params)
        builder.end("mdsol:Query") class StudyEventData(TransactionalElement, LastUpdateMixin, MilestoneMixin):
    """Models the ODM StudyEventData object"""

    ALLOWED_TRANSACTION_TYPES = ["Insert", "Update", "Upsert", "Context", "Remove"]

    def __init__(
        self, study_event_oid, transaction_type="Update", study_event_repeat_key=None
    ):
        """
        :param str study_event_oid: :class:`StudyEvent` OID
        :param str transaction_type: Transaction Type for Data (one of **Insert**, **Update**, **Upsert**, **Context**, **Remove**)
        :param int study_event_repeat_key: :attr:`StudyEventRepeatKey` for StudyEventData
        """
        super(self.__class__, self).__init__(transaction_type)
        self.study_event_oid = study_event_oid
        self.study_event_repeat_key = study_event_repeat_key
        #: :class:`FormData` part of Study Event Data
        self.forms = []
        #: :class:`Annotation` for Study Event Data  - *Not Supported by Rave*
        self.annotations = []
        #: :class:`Signature` for Study Event Data
        self.signature = None

    def build(self, builder):
        """Build XML by appending to builder
        :Example:
        <StudyEventData StudyEventOID="SCREENING" StudyEventRepeatKey="1" TransactionType="Update">

        """
        params = dict(StudyEventOID=self.study_event_oid)

        if self.transaction_type is not None:
            params["TransactionType"] = self.transaction_type

        if self.study_event_repeat_key is not None:
            params["StudyEventRepeatKey"] = str(self.study_event_repeat_key)

        # mixins
        self.mixin()
        self.mixin_params(params)

        builder.start("StudyEventData", params)

        # Ask children
        for form in self.forms:
            form.build(builder)

        if self.signature is not None:
            self.signature.build(builder)

        for annotation in self.annotations:
            annotation.build(builder)

        builder.end("StudyEventData")

    def __lshift__(self, other):
        """Override << operator"""
        if not isinstance(other, (FormData, Annotation, Signature)):
            raise ValueError(
                "StudyEventData object can only receive FormData, Signature or Annotation objects"
            )
        self.set_list_attribute(other, FormData, "forms")
        self.set_single_attribute(other, Signature, "signature")
        self.set_list_attribute(other, Annotation, "annotations")
        return other class SubjectData(TransactionalElement, LastUpdateMixin, MilestoneMixin):
    """Models the ODM SubjectData and ODM SiteRef objects"""

    ALLOWED_TRANSACTION_TYPES = ["Insert", "Update", "Upsert", "Context", "Remove"]

    def __init__(
        self,
        site_location_oid,
        subject_key,
        subject_key_type="SubjectName",
        transaction_type="Update",
    ):
        """
        :param str site_location_oid: :class:`SiteLocation` OID
        :param str subject_key: Value for SubjectKey
        :param str subject_key_type: Specifier as to the type of SubjectKey (either **SubjectName** or **SubjectUUID**)
        :param str transaction_type: Transaction Type for Data (one of **Insert**, **Update**, **Upsert**, **Context**, **Remove**)
        """
        super(self.__class__, self).__init__(transaction_type)
        self.sitelocationoid = site_location_oid
        self.subject_key = subject_key
        self.subject_key_type = subject_key_type
        #: collection of :class:`StudyEventData`
        self.study_events = []
        #: collection of :class:`Annotation`
        self.annotations = []
        #: :class:`AuditRecord` for SubjectData - *Not Supported By Rave*
        self.audit_record = None
        #: :class:`Signature` for SubjectData
        self.signature = None
        #: :class:`SiteRef`
        self.siteref = None

    def build(self, builder):
        """Build XML by appending to builder"""
        params = dict(SubjectKey=self.subject_key)
        params["mdsol:SubjectKeyType"] = self.subject_key_type

        if self.transaction_type is not None:
            params["TransactionType"] = self.transaction_type

        # mixins
        self.mixin()
        self.mixin_params(params)

        builder.start("SubjectData", params)

        # Ask children
        if self.audit_record is not None:
            self.audit_record.build(builder)

        if self.siteref:
            self.siteref.build(builder)
        else:
            builder.start("SiteRef", {"LocationOID": self.sitelocationoid})
            builder.end("SiteRef")

        for event in self.study_events:
            event.build(builder)

        if self.signature is not None:
            self.signature.build(builder)

        for annotation in self.annotations:
            annotation.build(builder)

        builder.end("SubjectData")

    def __lshift__(self, other):
        """Override << operator"""
        if not isinstance(
            other, (StudyEventData, AuditRecord, Annotation, Signature, SiteRef)
        ):
            raise ValueError(
                "SubjectData object can only receive StudyEventData, AuditRecord, "
                "Annotation or Signature object"
            )

        self.set_list_attribute(other, Annotation, "annotations")
        self.set_list_attribute(other, StudyEventData, "study_events")
        self.set_single_attribute(other, AuditRecord, "audit_record")
        self.set_single_attribute(other, Signature, "signature")
        self.set_single_attribute(other, SiteRef, "siteref")

        return other def obj_to_doc(obj, *args, **kwargs):
    """Convert an object to am XML document object
    :rtype: xml.etree.ElementTree.Element
    """
    builder = ET.TreeBuilder()
    obj.build(builder, *args, **kwargs)
    return builder.close()
import random
import uuid
import datetime
import unittest
from unittest import TestCase
from faker import Faker
from rwslib.builders.admindata import Location
from rwslib.builders.constants import QueryStatusType
from rwslib.builders.clinicaldata import ClinicalData, FormData, ItemData, ItemGroupData, MdsolQuery, StudyEventData, \
    SubjectData
from rwslib.tests.common import obj_to_doc
    def test_last_update_time_naiive(self):
        """We don't see a LastUpdateTime for naiive elements"""
        obj = ItemGroupData(itemgroupoid="DM")
        tested = obj_to_doc(obj)
        self.assertEqual("ItemGroupData", tested.tag)
        self.assertIsNone(tested.get('mdsol:LastUpdateTime'))

    def test_add_milestone(self):
        """We add a single milestone"""
        obj = ItemGroupData(itemgroupoid="DM")
        obj.add_milestone("Informed Consent")
        tested = obj_to_doc(obj)
        self.assertEqual('Annotation', list(tested)[0].tag)
        annotation = list(tested)[0]
        self.assertEqual('Informed Consent', list(list(annotation)[0])[0].text)

    def test_add_milestones(self):
        """We add multiple milestones"""
        obj = ItemGroupData(itemgroupoid="DM")
        obj.add_milestone("Informed Consent")
        obj.add_milestone("Study Start")
        tested = obj_to_doc(obj)
        self.assertEqual('Annotation', list(tested)[0].tag)
        annotation = list(tested)[0]
        self.assertEqual('Informed Consent', list(list(annotation)[0])[0].text)
        self.assertEqual('Study Start', list(list(annotation)[1])[0].text)

    def test_modm_attributes(self):
        """Each modm attribute is settable"""
        for attribute in ["ItemGroupUUID", "RecordID",
                          "SDRCompleteDate", "SDVCompleteDate", "LockCompleteDate",
                          "IsSDVRequired", "IsSDVComplete"]:
            data = ItemGroupData(itemgroupoid="DM")
            if "UUID" in attribute:
                data.add_attribute(attribute, uuid.uuid4())
            elif "Date" in attribute:
                data.add_attribute(attribute, fake.date_time_this_year(before_now=True,
                                                                       after_now=False,
                                                                       tzinfo=None))
            elif attribute.startswith('Is'):
                data.add_attribute(attribute, random.choice(YesNoRave))
            else:
                data.add_attribute(attribute, "Blargle")
            tested = obj_to_doc(data)
            self.assertIsNotNone(tested.get("mdsol:{}".format(attribute)))

    def test_invalid_modm_attributes(self):
        """Each invalid modm attribute raises an exception"""
        for attribute in ["StudyUUID"]:
            obj = ItemGroupData(itemgroupoid="DM")
            with self.assertRaises(ValueError) as exc:
                if "UUID" in attribute:
                    obj.add_attribute(attribute, uuid.uuid4())
                elif "Date" in attribute:
                    obj.add_attribute(attribute, fake.date_time_this_year(before_now=True,
                                                                          after_now=False,
                                                                          tzinfo=None))
                else:
                    obj.add_attribute(attribute, "Blargle")
            self.assertEqual("Can't add {} to ItemGroupData".format(attribute), str(exc.exception))


class TestMODMItemData(TestCase):
    def test_add_last_update_time(self):
        """We add a LastUpdateTime"""
        obj = ItemData(itemoid="BRTHDAT", value="12 DEC 1975")
        now = datetime.datetime.utcnow()
        obj.last_update_time = now
        tested = obj_to_doc(obj)
        self.assertEqual("ItemData", tested.tag)
        self.assertEqual(now.isoformat(), tested.get('mdsol:LastUpdateTime'))

    def test_add_last_update_time_with_invalid_time(self):
        """We add a LastUpdateTime with a nonsense value"""
        obj = ItemData(itemoid="BRTHDAT", value="12 DEC 1975")
        now = "2017-04-21"
        with self.assertRaises(ValueError) as exc:
            obj.last_update_time = now

    def test_last_update_time_naiive(self):
        """We don't see a LastUpdateTime for naiive elements"""
        obj = ItemData(itemoid="BRTHDAT", value="12 DEC 1975")
        tested = obj_to_doc(obj)
        self.assertEqual("ItemData", tested.tag)
        self.assertIsNone(tested.get('mdsol:LastUpdateTime'))

    def test_last_update_time_set(self):
        """We don't see a LastUpdateTime for naiive elements"""
        obj = ItemData(itemoid="BRTHDAT", value="12 DEC 1975")
        obj.set_update_time()
        tested = obj_to_doc(obj)
        self.assertEqual("ItemData", tested.tag)
        self.assertIsNotNone(tested.get('mdsol:LastUpdateTime'))

    def test_add_milestone(self):
        """We add a single milestone"""
        obj = ItemData(itemoid="BRTHDAT", value="12 DEC 1975")
        obj.add_milestone("Informed Consent")
        tested = obj_to_doc(obj)
        self.assertEqual('Annotation', list(tested)[0].tag)
        annotation = list(tested)[0]
        self.assertEqual('Informed Consent', list(list(annotation)[0])[0].text)

    def test_add_milestones(self):
        """We add multiple milestones"""
        obj = ItemData(itemoid="BRTHDAT", value="12 DEC 1975")
        obj.add_milestone("Informed Consent")
        obj.add_milestone("Study Start")
        tested = obj_to_doc(obj)
        self.assertEqual('Annotation', list(tested)[0].tag)
        annotation = list(tested)[0]
        self.assertEqual('Informed Consent', list(list(annotation)[0])[0].text)
        self.assertEqual('Study Start', list(list(annotation)[1])[0].text)

    def test_add_item_uuid(self):
        """We add a mdsol:ItemUUID"""
        obj = ItemData(itemoid="BRTHDAT", value="12 DEC 1975")
        now = datetime.datetime.utcnow()
        obj.last_update_time = now
        obj.add_attribute("ItemUUID", "85D4F9F0-9F49-42F3-A8E7-413DE85CC55E")

        tested = obj_to_doc(obj)