# odatadol.testing

A scripted OData service for tests: no network, no password.

[`FakeService`](#odatadol.testing.FakeService) is a `fetch` (`(url, headers) -> (status, bytes)`) that answers
like a real, quirky server did: it refuses `Accept: text/plain` on `$count`, 404s a
URL past `max_url` the way an IIS front end does, writes raw CR characters inside
JSON strings, and takes double-quoted names in `$select`/`$orderby`. It records every
URL it was asked for, so a test can assert what crossed the wire.

```pycon
>>> server = FakeService([{'ProductID': 1, 'Unit Price': 2.5}])
>>> from odatadol import entity_records
>>> list(entity_records('https://example.com/odata', 'Products', fetch=server,
...                     select=['ProductID', 'Unit Price']))
[{'ProductID': 1, 'Unit Price': 2.5}]
>>> sorted(server.asked_for())
['ProductID', 'Unit Price']
```

Its `$filter` understands what odatadol itself sends: comparisons
(`eq ne gt ge lt le`) of a property with a literal, joined by `and`.

### Functions

| [`csdl`](#odatadol.testing.csdl)(entity, properties, \*[, namespace, ...])   | A minimal CSDL `$metadata` document declaring one entity set.   |
|---------------------------------------------------------------------------------------------------|-----------------------------------------------------------------|

### Classes

| [`FakeService`](#odatadol.testing.FakeService)(rows, \*[, entity, metadata, ...])   | Answers OData requests over `rows` like a quirky real server, and records them.   |
|---------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------|

### *class* odatadol.testing.FakeService(rows, , entity='Entities', metadata=None, max_url=10000, page_cap=None, next_links=False, require_auth=False)

Bases: [`object`](https://docs.python.org/3/builtins/functions.html#object)

Answers OData requests over `rows` like a quirky real server, and records them.

* **Parameters:**
  * **rows** ([`Iterable`](https://docs.python.org/3/library/collections.abc.html#collections.abc.Iterable)[[`Mapping`](https://docs.python.org/3/library/collections.abc.html#collections.abc.Mapping)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str), [`Any`](https://docs.python.org/3/library/typing.html#typing.Any)]]) – The records of every entity set it serves (one set is enough for tests).
  * **entity** ([`str`](https://docs.python.org/3/builtins/stdtypes.html#str)) – The entity set the default `$metadata` declares.
  * **metadata** ([`bytes`](https://docs.python.org/3/builtins/stdtypes.html#bytes) | [`None`](https://docs.python.org/3/builtins/constants.html#None)) – The `$metadata` body; defaults to one declaring `entity` with an
    `Edm.String` for every property of the first row.
  * **max_url** ([`int`](https://docs.python.org/3/builtins/functions.html#int)) – URLs longer than this get an IIS-style 404.
  * **page_cap** ([`int`](https://docs.python.org/3/builtins/functions.html#int) | [`None`](https://docs.python.org/3/builtins/constants.html#None)) – Serve at most this many records per page, whatever `$top` asks,
    and say nothing about it (unless `next_links`), as some servers do.
  * **next_links** ([`bool`](https://docs.python.org/3/builtins/functions.html#bool)) – Page by `@odata.nextLink` (with a `$skiptoken`) when a page
    is capped, as server-driven paging does.
  * **require_auth** ([`bool`](https://docs.python.org/3/builtins/functions.html#bool)) – Answer 401 to a request without an `Authorization` header.

#### asked_for()

Every property named in a `$select` it was sent.

* **Return type:**
  [`set`](https://docs.python.org/3/builtins/stdtypes.html#set)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str)]

### odatadol.testing.csdl(entity, properties, , namespace='Demo', type_name=None, annotations=None, nullable=None)

A minimal CSDL `$metadata` document declaring one entity set.

* **Parameters:**
  * **properties** ([`Mapping`](https://docs.python.org/3/library/collections.abc.html#collections.abc.Mapping)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str), [`str`](https://docs.python.org/3/builtins/stdtypes.html#str)]) – `{name: Edm type}`.
  * **type_name** ([`str`](https://docs.python.org/3/builtins/stdtypes.html#str) | [`None`](https://docs.python.org/3/builtins/constants.html#None)) – The entity type’s name, if not `entity` (it often differs).
  * **annotations** ([`Mapping`](https://docs.python.org/3/library/collections.abc.html#collections.abc.Mapping)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str), [`Mapping`](https://docs.python.org/3/library/collections.abc.html#collections.abc.Mapping)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str), [`str`](https://docs.python.org/3/builtins/stdtypes.html#str)]] | [`None`](https://docs.python.org/3/builtins/constants.html#None)) – `{property: {term: Bool value}}`.
  * **nullable** ([`Mapping`](https://docs.python.org/3/library/collections.abc.html#collections.abc.Mapping)[[`str`](https://docs.python.org/3/builtins/stdtypes.html#str), [`bool`](https://docs.python.org/3/builtins/functions.html#bool)] | [`None`](https://docs.python.org/3/builtins/constants.html#None)) – `{property: False}` for non-nullable properties.
* **Return type:**
  [`bytes`](https://docs.python.org/3/builtins/stdtypes.html#bytes)
