summaryrefslogtreecommitdiff
path: root/yarns/000.yarn
blob: d9f16f5b16d08d1ced9a2546630fcb021000fe82 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
---
title: Effiapi test suite
author: Lars Wirzenius
...

[yarn]: https://liw.fi/cmdtest/
[Effireg]: https://effireg.liw.fi/

# Introduction

This chapter descibes the effiapi API, using [yarn][] automated
scenario tests. It is meant to be understandable to Effi sysadmins,
and those writing applications using the API, as well as be an
executable test suite for the API.

Effiapi is part of [Effireg][], a privacy-aware register of members
for associations.

The API is a simple RESTful HTTP API using JSON. This means that:

* each API call is an HTTP request, with a response

* all data is represented as JSON in the body of the request of
  response

* metadata about the data is represeneted as HTTP headers

* standard HTTP verbs (POST, PUT, GET, DELETE) are used to indicate
  the action

* standard HTTP status codes are used to indicate result of the
  request (200 = OK, 404 = not found, etc)

Examples will be provided.

# Test scenarios

## Manage memberships - happy path

This section shows the API calls to manage a memberhip. The test
scaffolding starts an effiapi instance, and defines an API client for
the administrator, which has all the access.

    SCENARIO Manage a membership
    GIVEN An effiapi instance

First make sure the register is empty.

    WHEN admin requests GET /status
    THEN HTTP status is 200
    AND body matches { "resources": 0 }

    WHEN admin requests GET /search with
    ... {"cond": [{"where": "meta", "op": ">=", "field": "id", "pattern": ""}]}
    THEN HTTP status is 200
    AND body matches { "resources": [] }

Create a member. Check in various ways that it exists.

    WHEN admin requests POST /memb with body { "fullname": "James Bond" }
    THEN HTTP status is 201
    AND remember header Muck-Id as ID
    AND remember header Muck-Revision as REV1

    WHEN admin requests GET /status
    THEN HTTP status is 200
    AND body matches { "resources": 1 }

    WHEN admin requests GET /search with
    ... {"cond": [{"where": "meta", "op": ">=", "field": "id", "pattern": ""}]}
    THEN HTTP status is 200
    AND body matches { "resources": [ "${ID}" ] }

    WHEN admin requests GET /memb with header Muck-Id: ${ID}
    THEN HTTP status is 200
    AND body matches { "fullname": "James Bond" }
    AND header Muck-Revision is ${REV1}

Update the member.

    WHEN admin requests PUT /memb with id ${ID}, revision ${REV1},
    ... and body { "fullname": "Alfred Pennyworth" }
    THEN HTTP status is 200
    AND remember header Muck-Revision as REV2

    WHEN admin requests GET /memb with header Muck-Id: ${ID}
    THEN HTTP status is 200
    AND body matches { "fullname": "Alfred Pennyworth" }
    AND header Muck-Revision is ${REV2}

Delete the member. Check in various ways that it no longer exists.

    WHEN admin requests DELETE /memb with id ${ID}
    THEN HTTP status is 200

    WHEN admin requests GET /status
    THEN HTTP status is 200
    AND body matches { "resources": 0 }

    WHEN admin requests GET /search with
    ... {"cond": [{"where": "meta", "op": ">=", "field": "id", "pattern": ""}]}
    THEN HTTP status is 200
    AND body matches { "resources": [] }

    WHEN admin requests GET /memb with header Muck-Id: ${ID}
    THEN HTTP status is 404

Done.

    FINALLY Effiapi is terminated

# Appendix: Yarn scenario step implementations

Most of the interesting code is in the `lib.py` module.

## Start and stop effiapi

    IMPLEMENTS GIVEN An effiapi instance
    effiapi.write_config()
    effiapi.start()

    IMPLEMENTS FINALLY Effiapi is terminated
    effiapi.terminate()

## Make HTTP requests

    IMPLEMENTS WHEN admin requests POST /memb with body (.+)
    body = get_json_match()
    effiapi.POST('/memb',  {}, body)

    IMPLEMENTS WHEN admin requests GET /status
    effiapi.GET('/status', {}, None)

    IMPLEMENTS WHEN admin requests PUT /memb with id (\S+), revision (\S+), and body (.+)
    rid = get_expanded_match()
    rev = get_expanded_match()
    body = get_json_match()
    headers = {
        'Muck-Id': rid,
        'Muck-Revision': rev,
    }
    effiapi.PUT('/memb', headers, body)

    IMPLEMENTS WHEN admin requests GET /memb with header (\S+): (\S+)
    header = get_next_match()
    value = get_expanded_match()
    headers = {
        header: value,
    }
    effiapi.GET('/memb', headers, None)

    IMPLEMENTS WHEN admin requests GET /search with (.+)
    body = get_expanded_json_match()
    headers = {
        'Content-Type': 'application/json',
    }
    effiapi.GET('/search', headers, json.dumps(body))

    IMPLEMENTS WHEN admin requests DELETE /memb with id (\S+)
    rid = get_expanded_match()
    headers = {
        'Muck-id': rid,
    }
    effiapi.DELETE('/memb', headers, None)

## Inspect HTTP responses

    IMPLEMENTS THEN HTTP status is (\d+)
    expected = int(get_next_match())
    actual = effiapi.get_status_code()
    assertEqual(effiapi.get_status_code(), expected)

    IMPLEMENTS THEN remember header (\S+) as (.+)
    header = get_next_match()
    varname = get_next_match()
    value = effiapi.get_header(header)
    save_for_expansion(varname, value)

    IMPLEMENTS THEN header (\S+) is (.+)
    header = get_next_match()
    expected = get_expanded_match()
    actual = effiapi.get_header(header)
    assertEqual(actual, expected)

    IMPLEMENTS THEN body matches (.+)
    expected = get_expanded_json_match()
    actual = effiapi.get_json_body()
    assertEqual(actual, expected)