Coverage for tests/test_sub_callbacks.py: 100%

39 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2025-05-05 00:03 +0000

1from typing import Optional 1abcdef

2 

3from dirty_equals import IsDict 1abcdef

4from fastapi import APIRouter, FastAPI 1abcdef

5from fastapi.testclient import TestClient 1abcdef

6from pydantic import BaseModel, HttpUrl 1abcdef

7 

8app = FastAPI() 1abcdef

9 

10 

11class Invoice(BaseModel): 1abcdef

12 id: str 1abcdef

13 title: Optional[str] = None 1abcdef

14 customer: str 1abcdef

15 total: float 1abcdef

16 

17 

18class InvoiceEvent(BaseModel): 1abcdef

19 description: str 1abcdef

20 paid: bool 1abcdef

21 

22 

23class InvoiceEventReceived(BaseModel): 1abcdef

24 ok: bool 1abcdef

25 

26 

27invoices_callback_router = APIRouter() 1abcdef

28 

29 

30@invoices_callback_router.post( 1abcdef

31 "{$callback_url}/invoices/{$request.body.id}", response_model=InvoiceEventReceived 

32) 

33def invoice_notification(body: InvoiceEvent): 1abcdef

34 pass # pragma: nocover 

35 

36 

37class Event(BaseModel): 1abcdef

38 name: str 1abcdef

39 total: float 1abcdef

40 

41 

42events_callback_router = APIRouter() 1abcdef

43 

44 

45@events_callback_router.get("{$callback_url}/events/{$request.body.title}") 1abcdef

46def event_callback(event: Event): 1abcdef

47 pass # pragma: nocover 

48 

49 

50subrouter = APIRouter() 1abcdef

51 

52 

53@subrouter.post("/invoices/", callbacks=invoices_callback_router.routes) 1abcdef

54def create_invoice(invoice: Invoice, callback_url: Optional[HttpUrl] = None): 1abcdef

55 """ 

56 Create an invoice. 

57 

58 This will (let's imagine) let the API user (some external developer) create an 

59 invoice. 

60 

61 And this path operation will: 

62 

63 * Send the invoice to the client. 

64 * Collect the money from the client. 

65 * Send a notification back to the API user (the external developer), as a callback. 

66 * At this point is that the API will somehow send a POST request to the 

67 external API with the notification of the invoice event 

68 (e.g. "payment successful"). 

69 """ 

70 # Send the invoice, collect the money, send the notification (the callback) 

71 return {"msg": "Invoice received"} 1ghijkl

72 

73 

74app.include_router(subrouter, callbacks=events_callback_router.routes) 1abcdef

75 

76client = TestClient(app) 1abcdef

77 

78 

79def test_get(): 1abcdef

80 response = client.post( 1ghijkl

81 "/invoices/", json={"id": "fooinvoice", "customer": "John", "total": 5.3} 

82 ) 

83 assert response.status_code == 200, response.text 1ghijkl

84 assert response.json() == {"msg": "Invoice received"} 1ghijkl

85 

86 

87def test_openapi_schema(): 1abcdef

88 with client: 1mnopqr

89 response = client.get("/openapi.json") 1mnopqr

90 assert response.json() == { 1mnopqr

91 "openapi": "3.1.0", 

92 "info": {"title": "FastAPI", "version": "0.1.0"}, 

93 "paths": { 

94 "/invoices/": { 

95 "post": { 

96 "summary": "Create Invoice", 

97 "description": 'Create an invoice.\n\nThis will (let\'s imagine) let the API user (some external developer) create an\ninvoice.\n\nAnd this path operation will:\n\n* Send the invoice to the client.\n* Collect the money from the client.\n* Send a notification back to the API user (the external developer), as a callback.\n * At this point is that the API will somehow send a POST request to the\n external API with the notification of the invoice event\n (e.g. "payment successful").', 

98 "operationId": "create_invoice_invoices__post", 

99 "parameters": [ 

100 { 

101 "required": False, 

102 "schema": IsDict( 

103 { 

104 "title": "Callback Url", 

105 "anyOf": [ 

106 { 

107 "type": "string", 

108 "format": "uri", 

109 "minLength": 1, 

110 "maxLength": 2083, 

111 }, 

112 {"type": "null"}, 

113 ], 

114 } 

115 ) 

116 | IsDict( 

117 # TODO: remove when deprecating Pydantic v1 

118 { 

119 "title": "Callback Url", 

120 "maxLength": 2083, 

121 "minLength": 1, 

122 "type": "string", 

123 "format": "uri", 

124 } 

125 ), 

126 "name": "callback_url", 

127 "in": "query", 

128 } 

129 ], 

130 "requestBody": { 

131 "content": { 

132 "application/json": { 

133 "schema": {"$ref": "#/components/schemas/Invoice"} 

134 } 

135 }, 

136 "required": True, 

137 }, 

138 "responses": { 

139 "200": { 

140 "description": "Successful Response", 

141 "content": {"application/json": {"schema": {}}}, 

142 }, 

143 "422": { 

144 "description": "Validation Error", 

145 "content": { 

146 "application/json": { 

147 "schema": { 

148 "$ref": "#/components/schemas/HTTPValidationError" 

149 } 

150 } 

151 }, 

152 }, 

153 }, 

154 "callbacks": { 

155 "event_callback": { 

156 "{$callback_url}/events/{$request.body.title}": { 

157 "get": { 

158 "summary": "Event Callback", 

159 "operationId": "event_callback__callback_url__events___request_body_title__get", 

160 "requestBody": { 

161 "required": True, 

162 "content": { 

163 "application/json": { 

164 "schema": { 

165 "$ref": "#/components/schemas/Event" 

166 } 

167 } 

168 }, 

169 }, 

170 "responses": { 

171 "200": { 

172 "description": "Successful Response", 

173 "content": { 

174 "application/json": {"schema": {}} 

175 }, 

176 }, 

177 "422": { 

178 "description": "Validation Error", 

179 "content": { 

180 "application/json": { 

181 "schema": { 

182 "$ref": "#/components/schemas/HTTPValidationError" 

183 } 

184 } 

185 }, 

186 }, 

187 }, 

188 } 

189 } 

190 }, 

191 "invoice_notification": { 

192 "{$callback_url}/invoices/{$request.body.id}": { 

193 "post": { 

194 "summary": "Invoice Notification", 

195 "operationId": "invoice_notification__callback_url__invoices___request_body_id__post", 

196 "requestBody": { 

197 "required": True, 

198 "content": { 

199 "application/json": { 

200 "schema": { 

201 "$ref": "#/components/schemas/InvoiceEvent" 

202 } 

203 } 

204 }, 

205 }, 

206 "responses": { 

207 "200": { 

208 "description": "Successful Response", 

209 "content": { 

210 "application/json": { 

211 "schema": { 

212 "$ref": "#/components/schemas/InvoiceEventReceived" 

213 } 

214 } 

215 }, 

216 }, 

217 "422": { 

218 "description": "Validation Error", 

219 "content": { 

220 "application/json": { 

221 "schema": { 

222 "$ref": "#/components/schemas/HTTPValidationError" 

223 } 

224 } 

225 }, 

226 }, 

227 }, 

228 } 

229 } 

230 }, 

231 }, 

232 } 

233 } 

234 }, 

235 "components": { 

236 "schemas": { 

237 "Event": { 

238 "title": "Event", 

239 "required": ["name", "total"], 

240 "type": "object", 

241 "properties": { 

242 "name": {"title": "Name", "type": "string"}, 

243 "total": {"title": "Total", "type": "number"}, 

244 }, 

245 }, 

246 "HTTPValidationError": { 

247 "title": "HTTPValidationError", 

248 "type": "object", 

249 "properties": { 

250 "detail": { 

251 "title": "Detail", 

252 "type": "array", 

253 "items": { 

254 "$ref": "#/components/schemas/ValidationError" 

255 }, 

256 } 

257 }, 

258 }, 

259 "Invoice": { 

260 "title": "Invoice", 

261 "required": ["id", "customer", "total"], 

262 "type": "object", 

263 "properties": { 

264 "id": {"title": "Id", "type": "string"}, 

265 "title": IsDict( 

266 { 

267 "title": "Title", 

268 "anyOf": [{"type": "string"}, {"type": "null"}], 

269 } 

270 ) 

271 | IsDict( 

272 # TODO: remove when deprecating Pydantic v1 

273 {"title": "Title", "type": "string"} 

274 ), 

275 "customer": {"title": "Customer", "type": "string"}, 

276 "total": {"title": "Total", "type": "number"}, 

277 }, 

278 }, 

279 "InvoiceEvent": { 

280 "title": "InvoiceEvent", 

281 "required": ["description", "paid"], 

282 "type": "object", 

283 "properties": { 

284 "description": {"title": "Description", "type": "string"}, 

285 "paid": {"title": "Paid", "type": "boolean"}, 

286 }, 

287 }, 

288 "InvoiceEventReceived": { 

289 "title": "InvoiceEventReceived", 

290 "required": ["ok"], 

291 "type": "object", 

292 "properties": {"ok": {"title": "Ok", "type": "boolean"}}, 

293 }, 

294 "ValidationError": { 

295 "title": "ValidationError", 

296 "required": ["loc", "msg", "type"], 

297 "type": "object", 

298 "properties": { 

299 "loc": { 

300 "title": "Location", 

301 "type": "array", 

302 "items": { 

303 "anyOf": [{"type": "string"}, {"type": "integer"}] 

304 }, 

305 }, 

306 "msg": {"title": "Message", "type": "string"}, 

307 "type": {"title": "Error Type", "type": "string"}, 

308 }, 

309 }, 

310 } 

311 }, 

312 }