Skip to content

Commit

Permalink
Bump frab schema version to latest from c3voc.
Browse files Browse the repository at this point in the history
Imported at https://github.com/voc/schedule/blob/89a4a34c78f2df8111e65164c9acce110313fe6d/validator/xsd/schedule.xml.xsd

Incidental changes:

* day start is now required, so populate it
* Tests need to use timezone aware datetimes or we produce XML timestamps with no UTC offset, which is invalid
* Change the tests to use assert_, so that errors are reported by pytest rather than just reporting "False"
  • Loading branch information
lukegb committed Jan 13, 2024
1 parent 18a35cc commit 51fd31d
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 46 deletions.
2 changes: 1 addition & 1 deletion apps/schedule/schedule_xml.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,12 @@ def make_root():


def add_day(root, index, start, end):
# Don't include start because it's not needed
return etree.SubElement(
root,
"day",
index=str(index),
date=start.strftime("%Y-%m-%d"),
start=start.isoformat(),
end=end.isoformat(),
)

Expand Down
156 changes: 130 additions & 26 deletions tests/frabs_schema.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,35 @@
<xs:element name="schedule">
<xs:complexType>
<xs:sequence>
<xs:element type="xs:string" name="version" minOccurs="1"/>
<xs:element minOccurs="0" name="generator">
<xs:complexType>
<xs:sequence />
<xs:attribute type="xs:string" name="name"/>
<xs:attribute type="xs:string" name="version"/>
</xs:complexType>
</xs:element>
<xs:element type="xs:string" name="version"/>
<xs:element name="conference">
<xs:complexType>
<xs:all>
<xs:element type="xs:string" name="title"/>
<xs:element type="xs:string" name="acronym"/>
<xs:element type="xs:date" name="start" minOccurs="0"/>
<xs:element type="xs:date" name="end" minOccurs="0"/>
<xs:element type="xs:integer" name="days" minOccurs="0"/>
<xs:element type="duration" name="timeslot_duration" minOccurs="0"/>
</xs:all>
<xs:sequence>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element type="xs:string" name="title" maxOccurs="1"/>
<xs:element type="acronym" name="acronym" maxOccurs="1"/>

<xs:element type="dateOrDateTimeTZ" name="start" minOccurs="0" maxOccurs="1"/>
<xs:element type="dateOrDateTimeTZ" name="end" minOccurs="0" maxOccurs="1"/>
<xs:element type="xs:integer" name="days" minOccurs="0" maxOccurs="1"/>
<xs:element type="duration" name="timeslot_duration" minOccurs="0"/>
<xs:element type="httpURI" name="base_url" minOccurs="0" maxOccurs="1"/>
<xs:element type="httpURI" name="logo" minOccurs="0" maxOccurs="1"/>
<xs:element type="httpURI" name="url" minOccurs="0" maxOccurs="1"/>
<xs:element type="xs:string" name="time_zone_name" minOccurs="0" maxOccurs="1"/>
<xs:element type="color" name="color" minOccurs="0"/>

<xs:element type="track" name="track" minOccurs="0" maxOccurs="unbounded"/>
</xs:choice>

</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element type="day" name="day" maxOccurs="unbounded"/>
Expand All @@ -34,50 +52,69 @@
<xs:selector xpath="day/room/event/slug" />
<xs:field xpath="." />
</xs:unique>

<xs:unique name="day_index_unique">
<xs:selector xpath="day" />
<xs:field xpath="@index" />
</xs:unique>
</xs:element>

<xs:complexType name="day">
<xs:sequence>
<xs:element type="room" name="room" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute type="xs:date" name="date" use="optional"/>
<xs:attribute type="xs:dateTime" name="start" use="optional"/>
<xs:attribute type="xs:dateTime" name="end" use="optional"/>
<xs:attribute type="xs:positiveInteger" name="index" use="optional"/>
<xs:attribute type="xs:date" name="date"/>
<xs:attribute type="dateTimeTZ" name="start" use="required"/>
<xs:attribute type="dateTimeTZ" name="end" use="required"/>
<xs:attribute type="xs:positiveInteger" name="index" use="required"/>
</xs:complexType>

<xs:complexType name="room">
<xs:sequence>
<xs:element type="event" name="event" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute type="xs:string" name="name" use="optional"/>
<xs:attribute type="xs:string" name="name" use="required"/>
<xs:attribute name="guid" type="uuid"/>
</xs:complexType>

<xs:complexType name="event">
<xs:all>
<xs:element type="xs:string" name="room"/>
<xs:element type="xs:string" name="title"/>
<xs:element type="xs:string" name="subtitle"/>
<xs:element type="xs:string" name="type"/>
<xs:element type="xs:dateTime" name="date"/>
<xs:element type="dateTimeTZ" name="date"/>
<xs:element type="start" name="start"/>
<xs:element type="duration" name="duration"/>

<xs:element type="xs:string" name="logo" minOccurs="0"/>
<xs:element type="xs:string" name="abstract"/>
<xs:element type="voc-slug" name="slug"/>
<xs:element type="xs:string" name="track"/>
<!-- logo is the path or URL to an image, we use httpURI as type here as apps prefer full URLs now -->
<xs:element type="httpURI" name="logo" minOccurs="0"/>
<xs:element type="persons" name="persons" minOccurs="0"/>
<xs:element type="xs:string" name="language" minOccurs="0"/>
<xs:element type="xs:string" name="abstract" minOccurs="1"/>
<xs:element type="xs:string" name="description" minOccurs="0"/>
<xs:element type="xs:string" name="slug" minOccurs="1"/>
<xs:element type="recording" name="recording" minOccurs="0"/>
<xs:element type="xs:string" name="subtitle" minOccurs="1"/>
<xs:element type="xs:string" name="track" minOccurs="1"/>
<xs:element type="links" name="links" minOccurs="0"/>
<xs:element type="attachments" name="attachments" minOccurs="0"/>
<xs:element type="httpURI" name="video_download_url" minOccurs="0"/>
<xs:element type="httpURI" name="url" minOccurs="0"/>
<xs:element type="httpURI" name="feedback_url" minOccurs="0"/>
</xs:all>
<xs:attribute name="id" type="xs:positiveInteger" use="required"/>
<xs:attribute name="guid" type="xs:string" use="optional"/>
<xs:attribute name="guid" type="uuid" use="required"/>
</xs:complexType>

<xs:simpleType name="dateTimeTZ">
<xs:restriction base="xs:dateTime">
<xs:pattern value=".+(Z|\+[0-9]{1,2}:[0-9]{2}|-[0-9]{1,2}:[0-9]{2})"/>
</xs:restriction>
</xs:simpleType>

<xs:simpleType name="dateOrDateTimeTZ">
<xs:union memberTypes="dateTimeTZ xs:date"/>
</xs:simpleType>

<xs:simpleType name="duration">
<xs:restriction base="xs:string">
<xs:pattern value="([0-9]{1,2}:[0-9]{2})|([0-9]{1,2}:[0-9]{2}:[0-9]{1,2})"/>
Expand All @@ -90,10 +127,55 @@
</xs:restriction>
</xs:simpleType>

<xs:simpleType name="uuid">
<xs:restriction base="xs:string">
<xs:pattern value="[0-9a-fA-F]{8}(-[0-9a-fA-F]{4}){3}-[0-9a-fA-F]{12}"/>
</xs:restriction>
</xs:simpleType>

<xs:simpleType name="acronym">
<xs:restriction base="xs:string">
<xs:pattern value="[a-z0-9_-]{4,}"/>
</xs:restriction>
</xs:simpleType>

<xs:simpleType name="voc-slug">
<xs:restriction base="xs:string">
<xs:pattern value="[a-z0-9_]{4,}-[0-9]{1,6}-[a-z0-9\-_]{4,}"/>
</xs:restriction>
</xs:simpleType>

<xs:simpleType name="slug">
<xs:restriction base="xs:string">
<xs:pattern value="[a-z0-9-]{2,}"/>
</xs:restriction>
</xs:simpleType>

<xs:simpleType name="httpURI">
<xs:restriction base="xs:anyURI">
<xs:pattern value="https?://.*"/>
</xs:restriction>
</xs:simpleType>

<xs:simpleType name="string-nonempty">
<xs:restriction base="xs:string">
<xs:pattern value=".+"/>
</xs:restriction>
</xs:simpleType>

<xs:simpleType name="hexColor">
<xs:restriction base="xs:string">
<!-- Pattern for hexadecimal color code: # followed by exactly 6 hexadecimal digits, prefered in lower case -->
<xs:pattern value="#[0-9a-f]{6}"/>
</xs:restriction>
</xs:simpleType>

<xs:complexType name="recording">
<xs:all>
<xs:element type="xs:string" name="license"/>
<xs:element type="xs:boolean" name="optout"/>
<xs:element type="httpURI" name="url" minOccurs="0"/>
<xs:element type="httpURI" name="link" minOccurs="0"/>
</xs:all>
</xs:complexType>

Expand All @@ -103,7 +185,8 @@
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:positiveInteger" name="id" use="optional"/>
<xs:attribute type="xs:positiveInteger" name="id" />
<xs:attribute type="uuid" name="guid"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
Expand All @@ -117,7 +200,7 @@
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:string" name="href" use="optional"/>
<xs:attribute type="xs:string" name="href"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
Expand All @@ -131,11 +214,32 @@
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:string" name="href" use="optional"/>
<xs:attribute type="xs:string" name="href"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:schema>

<xs:complexType name="track">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="xs:string" name="name" use="required"/>
<xs:attribute type="hexColor" name="color" use="optional"/>
<xs:attribute type="slug" name="slug" use="optional"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>

<xs:complexType name="color">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute type="hexColor" name="primary" use="required"/>
<xs:attribute type="hexColor" name="background" use="optional"/>
<xs:anyAttribute processContents="lax"/> <!-- Allows for additional, unspecified attributes -->
</xs:extension>
</xs:simpleContent>
</xs:complexType>

</xs:schema>
49 changes: 30 additions & 19 deletions tests/test_frab_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from datetime import datetime
from lxml import etree

from apps.schedule import event_tz
from apps.schedule.schedule_xml import (
make_root,
add_day,
Expand All @@ -12,6 +13,11 @@
)


def _local_datetime(*args):
dt = datetime(*args)
return event_tz.localize(dt)


@pytest.fixture(scope="session")
def frab_schema():
xml_file = open("tests/frabs_schema.xml")
Expand All @@ -29,28 +35,35 @@ def test_empty_frab_schema_fails(frab_schema):
def test_min_version_is_valid(frab_schema, request_context):
root = make_root()
add_day(
root, index=1, start=datetime(2016, 8, 5, 4, 0), end=datetime(2016, 8, 6, 4, 0)
root,
index=1,
start=_local_datetime(2016, 8, 5, 4, 0),
end=_local_datetime(2016, 8, 6, 4, 0),
)

is_valid = frab_schema.validate(root)
assert is_valid
frab_schema.assert_(root)


def test_simple_room(frab_schema, request_context):
root = make_root()
day = add_day(
root, index=1, start=datetime(2016, 8, 5, 4, 0), end=datetime(2016, 8, 6, 4, 0)
root,
index=1,
start=_local_datetime(2016, 8, 5, 4, 0),
end=_local_datetime(2016, 8, 6, 4, 0),
)
add_room(day, "the hinterlands")

is_valid = frab_schema.validate(root)
assert is_valid
frab_schema.assert_(root)


def test_simple_event(frab_schema, request_context):
root = make_root()
day = add_day(
root, index=1, start=datetime(2016, 8, 5, 4, 0), end=datetime(2016, 8, 6, 4, 0)
root,
index=1,
start=_local_datetime(2016, 8, 5, 4, 0),
end=_local_datetime(2016, 8, 6, 4, 0),
)
room = add_room(day, "the hinterlands")

Expand All @@ -61,14 +74,13 @@ def test_simple_event(frab_schema, request_context):
"description": "The foo bar",
"speaker": "Someone",
"user_id": 123,
"end_date": datetime(2016, 8, 5, 11, 00),
"start_date": datetime(2016, 8, 5, 10, 30),
"end_date": _local_datetime(2016, 8, 5, 11, 00),
"start_date": _local_datetime(2016, 8, 5, 10, 30),
}

add_event(room, event)

is_valid = frab_schema.validate(root)
assert is_valid
frab_schema.assert_(root)


def test_export_frab(frab_schema, request_context):
Expand All @@ -81,8 +93,8 @@ def test_export_frab(frab_schema, request_context):
"description": "The foo bar",
"speaker": "Someone",
"user_id": 123,
"end_date": datetime(2016, 8, 5, 11, 00),
"start_date": datetime(2016, 8, 5, 10, 30),
"end_date": _local_datetime(2016, 8, 5, 11, 00),
"start_date": _local_datetime(2016, 8, 5, 10, 30),
},
{
"id": 2,
Expand All @@ -92,8 +104,8 @@ def test_export_frab(frab_schema, request_context):
"description": "The foo bar",
"speaker": "Someone",
"user_id": 123,
"end_date": datetime(2016, 8, 5, 11, 00),
"start_date": datetime(2016, 8, 5, 10, 30),
"end_date": _local_datetime(2016, 8, 5, 11, 00),
"start_date": _local_datetime(2016, 8, 5, 10, 30),
},
{
"id": 3,
Expand All @@ -104,16 +116,15 @@ def test_export_frab(frab_schema, request_context):
"description": "The foo bar",
"speaker": "Someone",
"user_id": 123,
"end_date": datetime(2016, 8, 6, 11, 00),
"start_date": datetime(2016, 8, 6, 10, 30),
"end_date": _local_datetime(2016, 8, 6, 11, 00),
"start_date": _local_datetime(2016, 8, 6, 10, 30),
},
]

frab = export_frab(events)
frab_doc = etree.fromstring(frab)
is_valid = frab_schema.validate(frab_doc)

assert is_valid
frab_schema.assert_(frab_doc)


def test_get_duration():
Expand Down

0 comments on commit 51fd31d

Please sign in to comment.