haveapi 0.31.1 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/benchmarks/documentation.rb +29 -6
  4. data/benchmarks/endpoints.rb +153 -24
  5. data/benchmarks/spec/client_examples_spec.rb +109 -37
  6. data/doc/client-examples.md +218 -45
  7. data/doc/documentation-oauth.md +157 -0
  8. data/doc/documentation-ui.md +192 -0
  9. data/doc/index.md +3 -6
  10. data/doc/performance.md +39 -12
  11. data/haveapi.gemspec +1 -1
  12. data/lib/haveapi/authentication/chain.rb +19 -0
  13. data/lib/haveapi/authentication/oauth2/provider.rb +7 -1
  14. data/lib/haveapi/client_example.rb +143 -15
  15. data/lib/haveapi/client_examples/curl.rb +30 -20
  16. data/lib/haveapi/client_examples/dynamic_helpers.rb +1 -1
  17. data/lib/haveapi/client_examples/go_client.rb +9 -2
  18. data/lib/haveapi/client_examples/http.rb +9 -4
  19. data/lib/haveapi/client_examples/js_async_client.rb +7 -0
  20. data/lib/haveapi/client_examples/js_client.rb +47 -36
  21. data/lib/haveapi/client_examples/php_client.rb +161 -75
  22. data/lib/haveapi/client_examples/python_async_client.rb +3 -0
  23. data/lib/haveapi/client_examples/python_client.rb +61 -47
  24. data/lib/haveapi/client_examples/ruby_cli.rb +27 -18
  25. data/lib/haveapi/client_examples/ruby_client.rb +80 -31
  26. data/lib/haveapi/client_examples/rust_client.rb +20 -9
  27. data/lib/haveapi/client_examples/ts_async_client.rb +7 -0
  28. data/lib/haveapi/client_examples/ts_client.rb +3 -0
  29. data/lib/haveapi/client_examples/ts_generated_client.rb +10 -2
  30. data/lib/haveapi/documentation_context.rb +7 -2
  31. data/lib/haveapi/documentation_helpers.rb +223 -0
  32. data/lib/haveapi/documentation_oauth.rb +399 -0
  33. data/lib/haveapi/documentation_search.rb +225 -0
  34. data/lib/haveapi/documentation_search_index.rb +79 -0
  35. data/lib/haveapi/documentation_validators.rb +179 -0
  36. data/lib/haveapi/public/css/documentation.css +343 -0
  37. data/lib/haveapi/public/js/documentation.js +992 -0
  38. data/lib/haveapi/server.rb +144 -86
  39. data/lib/haveapi/shared_documentation_helpers.rb +54 -0
  40. data/lib/haveapi/version.rb +1 -1
  41. data/lib/haveapi/views/doc_layout.erb +16 -0
  42. data/lib/haveapi/views/doc_sidebars/create-client.erb +3 -3
  43. data/lib/haveapi/views/doc_sidebars/json-schema.erb +1 -1
  44. data/lib/haveapi/views/doc_sidebars/protocol.erb +5 -5
  45. data/lib/haveapi/views/documentation/search_form.erb +10 -0
  46. data/lib/haveapi/views/documentation/search_index.erb +1 -0
  47. data/lib/haveapi/views/documentation/search_results.erb +20 -0
  48. data/lib/haveapi/views/index.erb +2 -4
  49. data/lib/haveapi/views/main_layout.erb +12 -6
  50. data/lib/haveapi/views/shared_documentation.erb +12 -0
  51. data/lib/haveapi/views/shared_sidebar.erb +25 -0
  52. data/lib/haveapi/views/version_page/auth_body.erb +12 -33
  53. data/lib/haveapi/views/version_page/client_overview.erb +40 -0
  54. data/lib/haveapi/views/version_page/example_panels.erb +18 -0
  55. data/lib/haveapi/views/version_page/resource_body.erb +91 -132
  56. data/lib/haveapi/views/version_page/resource_directory.erb +14 -0
  57. data/lib/haveapi/views/version_page/usage.erb +7 -0
  58. data/lib/haveapi/views/version_page.erb +36 -117
  59. data/lib/haveapi/views/version_sidebar/auth_nav.erb +12 -16
  60. data/lib/haveapi/views/version_sidebar/auth_resource_nav.erb +19 -0
  61. data/lib/haveapi/views/version_sidebar/authentication_control.erb +26 -0
  62. data/lib/haveapi/views/version_sidebar/authentication_status.erb +3 -0
  63. data/lib/haveapi/views/version_sidebar/brand.erb +31 -0
  64. data/lib/haveapi/views/version_sidebar/resource_nav.erb +11 -20
  65. data/lib/haveapi/views/version_sidebar.erb +59 -48
  66. data/spec/authentication/basic_spec.rb +10 -10
  67. data/spec/authentication/oauth2_chain_spec.rb +72 -0
  68. data/spec/authentication/oauth2_token_validation_spec.rb +38 -0
  69. data/spec/client_examples/assets/documentation_test.cjs +817 -0
  70. data/spec/client_examples/context_performance_spec.rb +5 -4
  71. data/spec/client_examples/context_spec.rb +23 -2
  72. data/spec/client_examples/detail_spec.rb +175 -0
  73. data/spec/client_examples/dynamic_acceptance.md +59 -26
  74. data/spec/client_examples/dynamic_acceptance.rb +164 -44
  75. data/spec/client_examples/dynamic_acceptance_spec.rb +140 -23
  76. data/spec/client_examples/dynamic_fixture.rb +4 -3
  77. data/spec/client_examples/dynamic_js_spec.rb +20 -11
  78. data/spec/client_examples/dynamic_metadata_spec.rb +69 -0
  79. data/spec/client_examples/dynamic_mounted_paths_spec.rb +9 -7
  80. data/spec/client_examples/dynamic_python_spec.rb +62 -11
  81. data/spec/client_examples/go_spec.rb +14 -1
  82. data/spec/client_examples/presentation_spec.rb +247 -0
  83. data/spec/client_examples/profile_mount_spec.rb +40 -13
  84. data/spec/client_examples/rust_client_spec.rb +35 -1
  85. data/spec/client_examples/shell_spec.rb +268 -0
  86. data/spec/client_examples/syntax_spec.rb +192 -0
  87. data/spec/client_examples/ts_generated_client_spec.rb +15 -2
  88. data/spec/client_examples/typescript_profiles_spec.rb +1 -1
  89. data/spec/documentation/auth_filtering_spec.rb +9 -4
  90. data/spec/documentation/canonical_routes_spec.rb +89 -0
  91. data/spec/documentation/client_examples_spec.rb +39 -5
  92. data/spec/documentation/client_overview_spec.rb +145 -0
  93. data/spec/documentation/current_user_html_escaping_spec.rb +7 -2
  94. data/spec/documentation/example_variants_spec.rb +93 -0
  95. data/spec/documentation/fixtures/search_index.json +476 -0
  96. data/spec/documentation/identity_and_guides_spec.rb +130 -0
  97. data/spec/documentation/identity_context_spec.rb +180 -0
  98. data/spec/documentation/navigation_spec.rb +296 -0
  99. data/spec/documentation/oauth_fixture_spec.rb +39 -0
  100. data/spec/documentation/oauth_spec.rb +912 -0
  101. data/spec/documentation/oauth_transport_spec.rb +45 -0
  102. data/spec/documentation/performance_spec.rb +47 -6
  103. data/spec/documentation/reference_headings_spec.rb +190 -0
  104. data/spec/documentation/search_index_routes_spec.rb +202 -0
  105. data/spec/documentation/search_index_spec.rb +85 -0
  106. data/spec/documentation/search_routes_spec.rb +199 -0
  107. data/spec/documentation/search_spec.rb +134 -0
  108. data/spec/documentation/shared_pages_spec.rb +240 -0
  109. data/spec/documentation/validators_spec.rb +123 -0
  110. data/spec/documentation_spec.rb +3 -3
  111. data/spec/packaging_spec.rb +21 -2
  112. data/spec/performance/documentation_identity_fixture_spec.rb +47 -0
  113. data/spec/performance/documentation_shapes_spec.rb +97 -0
  114. data/spec/performance/json_activation_spec.rb +78 -1
  115. data/spec/performance/release_lineage_spec.rb +4 -3
  116. data/spec/performance/runner_spec.rb +1 -0
  117. data/test_support/all_client_performance_fixture.rb +6 -10
  118. data/test_support/documentation_oauth_fixture.rb +54 -0
  119. data/test_support/documentation_performance_fixture.rb +19 -1
  120. data/test_support/dynamic_documentation_api.rb +35 -10
  121. data/test_support/dynamic_python_login_spec.rb +3 -11
  122. metadata +52 -3
@@ -0,0 +1,476 @@
1
+ {
2
+ "index": {
3
+ "format": 1,
4
+ "version": "1",
5
+ "base_path": "/api/v1/",
6
+ "locale": "cs",
7
+ "visibility": "anonymous",
8
+ "resources": [
9
+ {
10
+ "path": [
11
+ "bulk"
12
+ ],
13
+ "label": "Bulk",
14
+ "authentication": null,
15
+ "search": [
16
+ "bulk",
17
+ "Bulk",
18
+ "bulk",
19
+ "Bulk"
20
+ ]
21
+ },
22
+ {
23
+ "path": [
24
+ "mywidget"
25
+ ],
26
+ "label": "Mywidget",
27
+ "authentication": null,
28
+ "search": [
29
+ "mywidget",
30
+ "Mywidget",
31
+ "mywidget",
32
+ "Mywidget"
33
+ ]
34
+ },
35
+ {
36
+ "path": [
37
+ "síť"
38
+ ],
39
+ "label": "Síť",
40
+ "authentication": null,
41
+ "search": [
42
+ "síť",
43
+ "Síť",
44
+ "síť",
45
+ "Síť"
46
+ ]
47
+ },
48
+ {
49
+ "path": [
50
+ "síť",
51
+ "žluťoučký"
52
+ ],
53
+ "label": "Žluťoučký",
54
+ "authentication": null,
55
+ "search": [
56
+ "síť",
57
+ "Síť",
58
+ "žluťoučký",
59
+ "Žluťoučký",
60
+ "síť.žluťoučký",
61
+ "Žluťoučký"
62
+ ]
63
+ },
64
+ {
65
+ "path": [
66
+ "widget"
67
+ ],
68
+ "label": "Widget",
69
+ "authentication": null,
70
+ "search": [
71
+ "widget",
72
+ "Widget",
73
+ "widget",
74
+ "Widget"
75
+ ]
76
+ },
77
+ {
78
+ "path": [
79
+ "token"
80
+ ],
81
+ "label": "Token",
82
+ "authentication": "token",
83
+ "search": [
84
+ "token",
85
+ "Token",
86
+ "token",
87
+ "Token",
88
+ "token"
89
+ ]
90
+ }
91
+ ],
92
+ "actions": [
93
+ {
94
+ "resource": 0,
95
+ "name": "inspect",
96
+ "label": "Inspect",
97
+ "identifiers": [
98
+ "inspect",
99
+ "bulk.inspect"
100
+ ],
101
+ "search": [
102
+ "Inspect"
103
+ ]
104
+ },
105
+ {
106
+ "resource": 1,
107
+ "name": "widget",
108
+ "label": "Widget",
109
+ "identifiers": [
110
+ "widget",
111
+ "mywidget.widget"
112
+ ],
113
+ "search": [
114
+ "Widget"
115
+ ]
116
+ },
117
+ {
118
+ "resource": 1,
119
+ "name": "widget_long",
120
+ "label": "Widget long",
121
+ "identifiers": [
122
+ "widget_long",
123
+ "mywidget.widget_long"
124
+ ],
125
+ "search": [
126
+ "Widget long"
127
+ ]
128
+ },
129
+ {
130
+ "resource": 3,
131
+ "name": "obnovit",
132
+ "label": "Obnovit",
133
+ "identifiers": [
134
+ "obnovit",
135
+ "síť.žluťoučký.obnovit"
136
+ ],
137
+ "search": [
138
+ "Obnovit"
139
+ ]
140
+ },
141
+ {
142
+ "resource": 5,
143
+ "name": "request",
144
+ "label": "Request",
145
+ "identifiers": [
146
+ "request",
147
+ "token.request"
148
+ ],
149
+ "search": [
150
+ "Request"
151
+ ]
152
+ }
153
+ ],
154
+ "parameters": [
155
+ {
156
+ "action": 0,
157
+ "name": "bulkvalue00",
158
+ "label": "Bulkvalue00",
159
+ "type": "String",
160
+ "direction": "input",
161
+ "humanized_name": "Bulkvalue00"
162
+ },
163
+ {
164
+ "action": 0,
165
+ "name": "bulkvalue01",
166
+ "label": "Bulkvalue01",
167
+ "type": "String",
168
+ "direction": "input",
169
+ "humanized_name": "Bulkvalue01"
170
+ },
171
+ {
172
+ "action": 0,
173
+ "name": "bulkvalue02",
174
+ "label": "Bulkvalue02",
175
+ "type": "String",
176
+ "direction": "input",
177
+ "humanized_name": "Bulkvalue02"
178
+ },
179
+ {
180
+ "action": 0,
181
+ "name": "bulkvalue03",
182
+ "label": "Bulkvalue03",
183
+ "type": "String",
184
+ "direction": "input",
185
+ "humanized_name": "Bulkvalue03"
186
+ },
187
+ {
188
+ "action": 0,
189
+ "name": "bulkvalue04",
190
+ "label": "Bulkvalue04",
191
+ "type": "String",
192
+ "direction": "input",
193
+ "humanized_name": "Bulkvalue04"
194
+ },
195
+ {
196
+ "action": 0,
197
+ "name": "bulkvalue05",
198
+ "label": "Bulkvalue05",
199
+ "type": "String",
200
+ "direction": "input",
201
+ "humanized_name": "Bulkvalue05"
202
+ },
203
+ {
204
+ "action": 0,
205
+ "name": "bulkvalue06",
206
+ "label": "Bulkvalue06",
207
+ "type": "String",
208
+ "direction": "input",
209
+ "humanized_name": "Bulkvalue06"
210
+ },
211
+ {
212
+ "action": 0,
213
+ "name": "bulkvalue07",
214
+ "label": "Bulkvalue07",
215
+ "type": "String",
216
+ "direction": "input",
217
+ "humanized_name": "Bulkvalue07"
218
+ },
219
+ {
220
+ "action": 0,
221
+ "name": "bulkvalue08",
222
+ "label": "Bulkvalue08",
223
+ "type": "String",
224
+ "direction": "input",
225
+ "humanized_name": "Bulkvalue08"
226
+ },
227
+ {
228
+ "action": 0,
229
+ "name": "bulkvalue09",
230
+ "label": "Bulkvalue09",
231
+ "type": "String",
232
+ "direction": "input",
233
+ "humanized_name": "Bulkvalue09"
234
+ },
235
+ {
236
+ "action": 0,
237
+ "name": "bulkvalue10",
238
+ "label": "Bulkvalue10",
239
+ "type": "String",
240
+ "direction": "input",
241
+ "humanized_name": "Bulkvalue10"
242
+ },
243
+ {
244
+ "action": 0,
245
+ "name": "bulkvalue11",
246
+ "label": "Bulkvalue11",
247
+ "type": "String",
248
+ "direction": "input",
249
+ "humanized_name": "Bulkvalue11"
250
+ },
251
+ {
252
+ "action": 0,
253
+ "name": "bulkvalue12",
254
+ "label": "Bulkvalue12",
255
+ "type": "String",
256
+ "direction": "input",
257
+ "humanized_name": "Bulkvalue12"
258
+ },
259
+ {
260
+ "action": 0,
261
+ "name": "bulkvalue13",
262
+ "label": "Bulkvalue13",
263
+ "type": "String",
264
+ "direction": "input",
265
+ "humanized_name": "Bulkvalue13"
266
+ },
267
+ {
268
+ "action": 0,
269
+ "name": "bulkvalue14",
270
+ "label": "Bulkvalue14",
271
+ "type": "String",
272
+ "direction": "input",
273
+ "humanized_name": "Bulkvalue14"
274
+ },
275
+ {
276
+ "action": 0,
277
+ "name": "bulkvalue15",
278
+ "label": "Bulkvalue15",
279
+ "type": "String",
280
+ "direction": "input",
281
+ "humanized_name": "Bulkvalue15"
282
+ },
283
+ {
284
+ "action": 0,
285
+ "name": "bulkvalue16",
286
+ "label": "Bulkvalue16",
287
+ "type": "String",
288
+ "direction": "input",
289
+ "humanized_name": "Bulkvalue16"
290
+ },
291
+ {
292
+ "action": 0,
293
+ "name": "bulkvalue17",
294
+ "label": "Bulkvalue17",
295
+ "type": "String",
296
+ "direction": "input",
297
+ "humanized_name": "Bulkvalue17"
298
+ },
299
+ {
300
+ "action": 0,
301
+ "name": "bulkvalue18",
302
+ "label": "Bulkvalue18",
303
+ "type": "String",
304
+ "direction": "input",
305
+ "humanized_name": "Bulkvalue18"
306
+ },
307
+ {
308
+ "action": 0,
309
+ "name": "bulkvalue19",
310
+ "label": "Bulkvalue19",
311
+ "type": "String",
312
+ "direction": "input",
313
+ "humanized_name": "Bulkvalue19"
314
+ },
315
+ {
316
+ "action": 0,
317
+ "name": "bulkvalue20",
318
+ "label": "Bulkvalue20",
319
+ "type": "String",
320
+ "direction": "input",
321
+ "humanized_name": "Bulkvalue20"
322
+ },
323
+ {
324
+ "action": 0,
325
+ "name": "bulkvalue21",
326
+ "label": "Bulkvalue21",
327
+ "type": "String",
328
+ "direction": "input",
329
+ "humanized_name": "Bulkvalue21"
330
+ },
331
+ {
332
+ "action": 0,
333
+ "name": "bulkvalue22",
334
+ "label": "Bulkvalue22",
335
+ "type": "String",
336
+ "direction": "input",
337
+ "humanized_name": "Bulkvalue22"
338
+ },
339
+ {
340
+ "action": 0,
341
+ "name": "bulkvalue23",
342
+ "label": "Bulkvalue23",
343
+ "type": "String",
344
+ "direction": "input",
345
+ "humanized_name": "Bulkvalue23"
346
+ },
347
+ {
348
+ "action": 1,
349
+ "name": "widget",
350
+ "label": "Widget",
351
+ "type": "String",
352
+ "direction": "input",
353
+ "humanized_name": "Widget"
354
+ },
355
+ {
356
+ "action": 2,
357
+ "name": "widget_long",
358
+ "label": "Widget long",
359
+ "type": "String",
360
+ "direction": "input",
361
+ "humanized_name": "Widget long"
362
+ },
363
+ {
364
+ "action": 3,
365
+ "name": "číslo",
366
+ "label": "Číslo",
367
+ "type": "Integer",
368
+ "direction": "input",
369
+ "humanized_name": "Číslo"
370
+ },
371
+ {
372
+ "action": 3,
373
+ "name": "výsledek",
374
+ "label": "Výsledek",
375
+ "type": "String",
376
+ "direction": "output",
377
+ "humanized_name": "Výsledek"
378
+ },
379
+ {
380
+ "action": 4,
381
+ "name": "lifetime",
382
+ "label": "Lifetime",
383
+ "type": "Integer",
384
+ "direction": "input",
385
+ "humanized_name": "Lifetime"
386
+ },
387
+ {
388
+ "action": 4,
389
+ "name": "lifetime",
390
+ "label": "Lifetime",
391
+ "type": "Integer",
392
+ "direction": "output",
393
+ "humanized_name": "Lifetime"
394
+ }
395
+ ]
396
+ },
397
+ "queries": [
398
+ {
399
+ "query": "widget",
400
+ "expected": [
401
+ "resource::widget:::widget",
402
+ "resource::mywidget:::mywidget",
403
+ "action::mywidget:widget::widget",
404
+ "action::mywidget:widget_long::widget_long",
405
+ "parameter::mywidget:widget:input:widget",
406
+ "parameter::mywidget:widget_long:input:widget_long"
407
+ ],
408
+ "more": false
409
+ },
410
+ {
411
+ "query": "SÍŤ ČÍSLO",
412
+ "expected": [
413
+ "parameter::síť.žluťoučký:obnovit:input:číslo"
414
+ ],
415
+ "more": false
416
+ },
417
+ {
418
+ "query": "token lifetime",
419
+ "expected": [
420
+ "parameter:token:token:request:input:lifetime",
421
+ "parameter:token:token:request:output:lifetime"
422
+ ],
423
+ "more": false
424
+ },
425
+ {
426
+ "query": "bulkvalue",
427
+ "expected": [
428
+ "parameter::bulk:inspect:input:bulkvalue00",
429
+ "parameter::bulk:inspect:input:bulkvalue01",
430
+ "parameter::bulk:inspect:input:bulkvalue02",
431
+ "parameter::bulk:inspect:input:bulkvalue03",
432
+ "parameter::bulk:inspect:input:bulkvalue04",
433
+ "parameter::bulk:inspect:input:bulkvalue05",
434
+ "parameter::bulk:inspect:input:bulkvalue06",
435
+ "parameter::bulk:inspect:input:bulkvalue07",
436
+ "parameter::bulk:inspect:input:bulkvalue08",
437
+ "parameter::bulk:inspect:input:bulkvalue09",
438
+ "parameter::bulk:inspect:input:bulkvalue10",
439
+ "parameter::bulk:inspect:input:bulkvalue11",
440
+ "parameter::bulk:inspect:input:bulkvalue12",
441
+ "parameter::bulk:inspect:input:bulkvalue13",
442
+ "parameter::bulk:inspect:input:bulkvalue14",
443
+ "parameter::bulk:inspect:input:bulkvalue15",
444
+ "parameter::bulk:inspect:input:bulkvalue16",
445
+ "parameter::bulk:inspect:input:bulkvalue17",
446
+ "parameter::bulk:inspect:input:bulkvalue18",
447
+ "parameter::bulk:inspect:input:bulkvalue19"
448
+ ],
449
+ "more": true
450
+ },
451
+ {
452
+ "query": "absent",
453
+ "expected": [],
454
+ "more": false
455
+ }
456
+ ],
457
+ "display_queries": [
458
+ {
459
+ "query": "síť",
460
+ "expected": [
461
+ { "title": "Resource: síť", "context": ["Síť"] },
462
+ { "title": "Resource: síť.žluťoučký", "context": ["Síť · Žluťoučký"] },
463
+ { "title": "Action: obnovit", "context": ["síť.žluťoučký · obnovit", "Obnovit"] },
464
+ { "title": "Parameter: výsledek", "context": ["síť.žluťoučký · obnovit", "Output · String", "Výsledek"] },
465
+ { "title": "Parameter: číslo", "context": ["síť.žluťoučký · obnovit", "Input · Integer", "Číslo"] }
466
+ ]
467
+ },
468
+ {
469
+ "query": "token lifetime",
470
+ "expected": [
471
+ { "title": "Parameter: lifetime", "context": ["token · request", "Input · Integer", "Authentication: token", "Lifetime"] },
472
+ { "title": "Parameter: lifetime", "context": ["token · request", "Output · Integer", "Authentication: token", "Lifetime"] }
473
+ ]
474
+ }
475
+ ]
476
+ }
@@ -0,0 +1,130 @@
1
+ require 'spec_helper'
2
+
3
+ module DocumentationIdentitySpec
4
+ User = Struct.new(:login)
5
+
6
+ class Provider < HaveAPI::Authentication::Basic::Provider
7
+ protected
8
+
9
+ def find_user(_request, username, password)
10
+ User.new(username) if password == 'pass'
11
+ end
12
+ end
13
+ end
14
+
15
+ describe DocumentationIdentitySpec do
16
+ api do
17
+ define_resource(:Sample) do
18
+ version 1
19
+ auth false
20
+ define_action(:Index, superclass: HaveAPI::Actions::Default::Index) do
21
+ input(:hash) { string :admin_field, choices: { values: { 'secret-value' => 'Admin choice' } } }
22
+ authorize do |user|
23
+ input blacklist: [:admin_field] if user && user.login != 'admin'
24
+ allow
25
+ end
26
+ end
27
+ end
28
+ end
29
+ default_version 1
30
+ auth_chain DocumentationIdentitySpec::Provider
31
+
32
+ it 'shows anonymous visibility and themes without Basic controls when docs OAuth is absent' do
33
+ login('admin', 'pass')
34
+ get '/v1/'
35
+ expect(last_response).to be_ok
36
+ expect(last_response.body).to include('Showing all resources, actions and parameters.', 'Using this reference',
37
+ 'data-documentation-menu', 'aria-label="Documentation settings"',
38
+ '<span id="docs-theme-label" class="docs-theme-label">Theme</span>')
39
+ choices = last_response.body.match(%r{<div class="docs-theme-choices" role="group" aria-labelledby="docs-theme-label">(.*?)</div>}m)[1]
40
+ expect(choices).to include('data-theme-choice="auto"', 'data-theme-choice="light"', 'data-theme-choice="dark"')
41
+ expect(last_response.body.scan('id="docs-theme-label"').length).to eq(1)
42
+ expect(last_response.body).not_to include('docs-identity', 'docs-sign-in', 'data-account-menu', 'data-theme-menu')
43
+ expect(last_response.body.index('docs-visibility-note')).to be < last_response.body.index('data-documentation-search')
44
+ end
45
+
46
+ it 'uses the requested primary order and indents methods beneath Authentication' do
47
+ get '/v1/?page=authentication'
48
+ primary = last_response.body.match(%r{<nav aria-label="Documentation pages">(.*?)</nav>\s*</li>\s*<li>}m)
49
+ expect(primary).not_to be_nil
50
+ body = primary[1]
51
+ expect(body.index('Overview')).to be < body.index('Authentication')
52
+ expect(body).to include('docs-authentication-submenu', 'href="#auth-basic"')
53
+ sidebar = last_response.body.match(%r{<aside[^>]*>(.*?)</aside>}m)[1]
54
+ expect(sidebar.index('Authentication')).to be < sidebar.index('>Resources</a>')
55
+ expect(sidebar).not_to include('Guides', 'doc/protocol', 'doc/client-examples')
56
+ end
57
+
58
+ it 'keeps technical links explicitly in internals and removes the index content link' do
59
+ get '/'
60
+ main = last_response.body.match(%r{<main[^>]*>(.*?)</main>}m)[1]
61
+ expect(main).not_to include('>Documentation</a>')
62
+ expect(last_response.body).to include('HaveAPI internals', 'doc/client-examples')
63
+ get '/v1/'
64
+ expect(last_response.body).not_to include('HaveAPI internals', 'doc/client-examples')
65
+ end
66
+
67
+ it 'renders real fenced examples on technical pages and README as code' do
68
+ get '/doc/client-examples'
69
+ expect(last_response).to be_ok
70
+ expect(last_response.body).to match(/<pre><code[^>]*>.*?documentation_clients/m)
71
+ expect(last_response.body).not_to include('```ruby')
72
+ get '/doc/readme'
73
+ expect(last_response).to be_ok
74
+ expect(last_response.body).not_to include('```')
75
+ end
76
+
77
+ context 'with an actual documentation OAuth cookie' do
78
+ let(:documentation_server) { HaveAPI::Server.new(self.class::ApiModule) }
79
+ let(:users) do
80
+ { 1 => { 'member-token' => DocumentationIdentitySpec::User.new('<member>&'),
81
+ 'admin-token' => DocumentationIdentitySpec::User.new('admin') } }
82
+ end
83
+ let(:fixture) do
84
+ require_relative '../../test_support/documentation_oauth_fixture'
85
+ server = documentation_server
86
+ server.use_version(1)
87
+ oauth = DocumentationOAuthFixture.configure(server, users:)
88
+ server.mount('/')
89
+ @api = server
90
+ oauth
91
+ end
92
+
93
+ before { fixture }
94
+
95
+ def cookie_request(token, path = '/v1/?resource=sample')
96
+ oauth = fixture
97
+ get "#{oauth.origin}#{path}", {}, 'HTTP_COOKIE' => oauth.header_for(documentation_server, version: 1, token:)
98
+ end
99
+
100
+ it 'shows visible Sign in and neutral Account labels beside their person icons' do
101
+ get "#{fixture.origin}/v1/"
102
+ expect(last_response.body).to match(%r{class="docs-sign-in"[^>]*>\s*<svg.*?</svg>\s*<span>Sign in</span>}m)
103
+ get "#{fixture.origin}/doc"
104
+ expect(last_response.body).to match(%r{class="docs-account-link"[^>]*><svg.*?</svg><span>Account</span>}m)
105
+ expect(fixture.lookups).to be_empty
106
+ end
107
+
108
+ it 'shows the actual escaped login once in the shared settings menu with CSRF sign-out' do
109
+ cookie_request('member-token')
110
+ expect(last_response).to be_ok
111
+ menu = last_response.body.match(%r{<details data-documentation-menu>(.*?)</details>}m)[1]
112
+ expect(menu).to include('data-documentation-menu-content', '<span class="docs-identity">&lt;member&gt;&amp;</span>',
113
+ '<form method="post"', 'name="csrf"', '<button type="submit">Sign out</button>')
114
+ expect(menu).not_to include('docs-visibility-note', 'data-account-menu', 'data-theme-menu', 'name="switch_user"')
115
+ expect(last_response.body.scan('class="docs-identity"').length).to eq(1)
116
+ expect(last_response.body).to include('Showing resources, actions and parameters available to this account.')
117
+ expect(fixture.lookups).to eq([[1, 'member-token']])
118
+ end
119
+
120
+ it 'uses cookie roles for parameter choices despite conflicting Basic credentials' do
121
+ login('admin', 'pass')
122
+ cookie_request('member-token')
123
+ expect(last_response.body).not_to include('admin_field', 'secret-value', 'Admin choice')
124
+ login('member', 'pass')
125
+ cookie_request('admin-token')
126
+ expect(last_response.body).to include('admin_field', 'secret-value', 'Admin choice')
127
+ expect(fixture.lookups).to eq([[1, 'member-token'], [1, 'admin-token']])
128
+ end
129
+ end
130
+ end