|
1
|
# |
|
2
|
# Copyright (c) 2016 D. Richard Hipp |
|
3
|
# |
|
4
|
# This program is free software; you can redistribute it and/or |
|
5
|
# modify it under the terms of the Simplified BSD License (also |
|
6
|
# known as the "2-Clause License" or "FreeBSD License".) |
|
7
|
# |
|
8
|
# This program is distributed in the hope that it will be useful, |
|
9
|
# but without any warranty; without even the implied warranty of |
|
10
|
# merchantability or fitness for a particular purpose. |
|
11
|
# |
|
12
|
# Author contact information: |
|
13
|
# [email protected] |
|
14
|
# http://www.hwaci.com/drh/ |
|
15
|
# |
|
16
|
############################################################################ |
|
17
|
# |
|
18
|
# Test JSON Support |
|
19
|
# |
|
20
|
|
|
21
|
# Make sure we have a build with the json command at all and that it |
|
22
|
# is not stubbed out. This assumes the current (as of 2016-01-27) |
|
23
|
# practice of eliminating all trace of the fossil json command when |
|
24
|
# not configured. If that changes, these conditions might not prevent |
|
25
|
# the rest of this file from running. |
|
26
|
fossil test-th-eval "hasfeature json" |
|
27
|
|
|
28
|
if {[normalize_result] ne "1"} { |
|
29
|
puts "Fossil was not compiled with JSON support." |
|
30
|
test_cleanup_then_return |
|
31
|
} |
|
32
|
|
|
33
|
# We need a JSON parser to effectively test the JSON produced by |
|
34
|
# fossil. It looks like the one from tcllib is exactly what we need. |
|
35
|
# On ActiveTcl, add it with teacup. On other platforms, YMMV. |
|
36
|
# teacup install json |
|
37
|
# teacup install json::write |
|
38
|
if {[catch {package require json}] != 0} then { |
|
39
|
puts { |
|
40
|
The "json" package for Tcl is not available. |
|
41
|
Please see: https://core.tcl-lang.org/tcllib |
|
42
|
} |
|
43
|
test_cleanup_then_return |
|
44
|
} |
|
45
|
|
|
46
|
proc json2dict {txt} { |
|
47
|
set rc [catch {::json::json2dict $txt} result options] |
|
48
|
if {$rc != 0} { |
|
49
|
protOut "JSON ERROR: $result" |
|
50
|
return {} |
|
51
|
} |
|
52
|
return $result |
|
53
|
} |
|
54
|
|
|
55
|
# and that the json itself smells ok and has the expected API error code in it |
|
56
|
fossil json -expectError |
|
57
|
set JR [json2dict $RESULT] |
|
58
|
if {$JR eq ""} { |
|
59
|
puts "Fossil was not compiled with JSON support (bad JSON)." |
|
60
|
test_cleanup_then_return |
|
61
|
} |
|
62
|
test json-1 {[dict exists $JR resultCode] |
|
63
|
&& [dict get $JR resultCode] in "FOSSIL-3002 FOSSIL-4102"} |
|
64
|
|
|
65
|
# Use the CLI interface to execute a JSON command. Sets the global |
|
66
|
# RESULT to the response text, and JR to a Tcl dict conversion of the |
|
67
|
# response body. |
|
68
|
# |
|
69
|
# Returns "200" or "500". |
|
70
|
proc fossil_json {args} { |
|
71
|
global RESULT JR |
|
72
|
uplevel 1 fossil json {*}$args |
|
73
|
set JR [json2dict $RESULT] |
|
74
|
return "200" |
|
75
|
} |
|
76
|
|
|
77
|
# Use the HTTP interface to GET a JSON API URL. Sets the globals |
|
78
|
# RESULT to the HTTP response body, and JR to a Tcl dict conversion of |
|
79
|
# the response body. |
|
80
|
# |
|
81
|
# Returns the status code from the HTTP header. |
|
82
|
proc fossil_http_json {url {cookie "Muppet=Monster"} args} { |
|
83
|
global RESULT JR |
|
84
|
set request "GET $url HTTP/1.1\r\nHost: localhost\r\nUser-Agent: Fossil-http-json\r\nCookie: $cookie" |
|
85
|
set RESULT [fossil_maybe_answer $request http {*}$args --ipaddr 127.0.0.1] |
|
86
|
set head ""; set body ""; set status "--NO_MATCH--" |
|
87
|
regexp {(?w)(.*)^\s*$(.*)} $RESULT dummy head body |
|
88
|
regexp {^HTTP\S+\s+(\d\d\d)\s+(.*)$} $head dummy status msg |
|
89
|
if {$status eq "200"} { |
|
90
|
set JR [json2dict $body] |
|
91
|
} |
|
92
|
return $status |
|
93
|
} |
|
94
|
|
|
95
|
|
|
96
|
# Use the HTTP interface to POST a JSON API URL. Sets the globals |
|
97
|
# RESULT to the HTTP response body, and JR to a Tcl dict conversion of |
|
98
|
# the response body. |
|
99
|
# |
|
100
|
# Returns the status code from the HTTP header. |
|
101
|
proc fossil_post_json {url data {cookie "Muppet=Monster"} args} { |
|
102
|
global RESULT JR |
|
103
|
|
|
104
|
# set up a full GET or POST HTTP request |
|
105
|
set len [string length $data] |
|
106
|
if {$len > 0} { |
|
107
|
set request [subst {POST $url HTTP/1.0\r |
|
108
|
Host: localhost\r |
|
109
|
User-Agent: Fossil-Test\r |
|
110
|
Cookie: $cookie\r |
|
111
|
Content-Type: application/json |
|
112
|
Content-Length $len |
|
113
|
\r |
|
114
|
$data}] |
|
115
|
} else { |
|
116
|
set request [subst {GET $url HTTP/1.0\r |
|
117
|
Host: localhost\r |
|
118
|
User-Agent: Fossil-Test\r |
|
119
|
Cookie: $cookie\r |
|
120
|
\r |
|
121
|
}] |
|
122
|
} |
|
123
|
|
|
124
|
# handle the actual request |
|
125
|
flush stdout |
|
126
|
#exec $fossilexe |
|
127
|
set RESULT [fossil_maybe_answer $request http {*}$args --ipaddr 127.0.0.1] |
|
128
|
|
|
129
|
# separate HTTP headers from body |
|
130
|
set head ""; set body ""; set status "--NO_MATCH--" |
|
131
|
regexp {(?w)(.*)^\s*$(.*)} $RESULT dummy head body |
|
132
|
regexp {^HTTP\S+\s+(\d\d\d)\s+(.*)$} $head dummy status msg |
|
133
|
if {$status eq "200"} { |
|
134
|
if {[string length $body] > 0} { |
|
135
|
set JR [json2dict $body] |
|
136
|
} else { |
|
137
|
set JR "" |
|
138
|
} |
|
139
|
} |
|
140
|
return $status |
|
141
|
} |
|
142
|
|
|
143
|
|
|
144
|
# Inspect a dict for keys it must have and keys it must not have |
|
145
|
proc test_dict_keys {testname D okfields badfields} { |
|
146
|
if {$D eq ""} { |
|
147
|
test $testname-validJSON 0 |
|
148
|
return |
|
149
|
} |
|
150
|
set i 1 |
|
151
|
foreach f $okfields { |
|
152
|
test "$testname-$i" {[dict exists $D $f]} |
|
153
|
incr i |
|
154
|
} |
|
155
|
foreach f $badfields { |
|
156
|
test "$testname-$i" {![dict exists $D $f]} |
|
157
|
incr i |
|
158
|
} |
|
159
|
} |
|
160
|
|
|
161
|
# Inspect the envelope part of a returned JSON structure to confirm |
|
162
|
# that it has specific fields and that it lacks specific fields. |
|
163
|
proc test_json_envelope {testname okfields badfields} { |
|
164
|
test_dict_keys $testname $::JR $okfields $badfields |
|
165
|
} |
|
166
|
|
|
167
|
# Inspect the envelope of a normal successful result |
|
168
|
proc test_json_envelope_ok {testname} { |
|
169
|
test_json_envelope $testname [concat fossil timestamp command procTimeUs \ |
|
170
|
procTimeMs payload] [concat resultCode resultText] |
|
171
|
} |
|
172
|
|
|
173
|
# Inspect the payload of a successful result to confirm that it has |
|
174
|
# specific fields and that it lacks specific fields. |
|
175
|
proc test_json_payload {testname okfields badfields} { |
|
176
|
test_dict_keys $testname [dict get $::JR payload] $okfields $badfields |
|
177
|
} |
|
178
|
|
|
179
|
#### VERSION AKA HAI |
|
180
|
|
|
181
|
# The JSON API generally assumes we have a repository, so let it have one. |
|
182
|
|
|
183
|
# Set FOSSIL_USER to ensure consistent results in "json user list" |
|
184
|
set _fossil_user "" |
|
185
|
if [info exists env(FOSSIL_USER)] { |
|
186
|
set _fossil_user $env(FOSSIL_USER) |
|
187
|
} |
|
188
|
set ::env(FOSSIL_USER) "JSON-TEST-USER" |
|
189
|
|
|
190
|
test_setup |
|
191
|
|
|
192
|
# Stop backoffice from running during this test as it can cause hangs. |
|
193
|
fossil settings backoffice-disable 1 |
|
194
|
|
|
195
|
# Check for basic envelope fields in the result with an error |
|
196
|
fossil_json -expectError |
|
197
|
test_json_envelope json-enverr [concat resultCode fossil timestamp \ |
|
198
|
resultText command procTimeUs procTimeMs] {} |
|
199
|
test json-enverr-rc-1 {[dict get $JR resultCode] eq "FOSSIL-3002"} |
|
200
|
|
|
201
|
|
|
202
|
# Check for basic envelope fields in the result with a successful |
|
203
|
# command |
|
204
|
set HAIfields [concat manifestUuid manifestVersion manifestDate \ |
|
205
|
manifestYear releaseVersion releaseVersionNumber \ |
|
206
|
resultCodeParanoiaLevel jsonApiVersion] |
|
207
|
|
|
208
|
fossil_json HAI |
|
209
|
test_json_envelope_ok json-HAI |
|
210
|
test_json_payload json-HAI $HAIfields {} |
|
211
|
test json-HAI-api {[dict get $JR payload jsonApiVersion] >= 20120713} |
|
212
|
|
|
213
|
# Check for basic envelope fields in a HTTP result with a successful |
|
214
|
# command |
|
215
|
fossil_http_json /json/HAI |
|
216
|
test_json_envelope_ok json-http-HAI |
|
217
|
test_json_payload json-http-HAI $HAIfields {} |
|
218
|
test json-http-HAI-api {[dict get $JR payload jsonApiVersion] >= 20120713} |
|
219
|
|
|
220
|
fossil_json version |
|
221
|
test_json_envelope_ok json-version |
|
222
|
test_json_payload json-version $HAIfields {} |
|
223
|
test json-version-api {[dict get $JR payload jsonApiVersion] >= 20120713} |
|
224
|
|
|
225
|
#### ARTIFACT |
|
226
|
|
|
227
|
# sha1 of 0 bytes and a file to match in a commit |
|
228
|
set UUID_empty da39a3ee5e6b4b0d3255bfef95601890afd80709 |
|
229
|
# sha3 of 0 bytes and a file to match in a commit |
|
230
|
set UUID_empty_64 a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a |
|
231
|
write_file empty "" |
|
232
|
fossil add empty |
|
233
|
fossil ci -m "empty file" |
|
234
|
|
|
235
|
# json artifact (checkin) |
|
236
|
fossil_json [concat artifact tip] |
|
237
|
test_json_envelope_ok json-artifact-checkin-env |
|
238
|
test json-artifact-checkin {[dict get $JR payload type] eq "checkin"} |
|
239
|
test_json_payload json-artifact \ |
|
240
|
[concat type uuid isLeaf timestamp user comment parents tags files] {} |
|
241
|
|
|
242
|
# json artifact (file) |
|
243
|
fossil_json [concat artifact $UUID_empty_64] |
|
244
|
test_json_envelope_ok json-artifact-file-env |
|
245
|
test json-artifact-file {[dict get $JR payload type] eq "file"} |
|
246
|
test_json_payload json-artifact [concat type uuid size checkins] {} |
|
247
|
|
|
248
|
# json artifact (wiki) |
|
249
|
fossil wiki create Empty <<"-=BLANK=-" |
|
250
|
fossil_json wiki get Empty |
|
251
|
test json-wiki-get {[dict get $JR payload name] eq "Empty"} |
|
252
|
set uuid [dict get $JR payload uuid] |
|
253
|
fossil_json artifact $uuid |
|
254
|
test_json_envelope_ok json-artifact-wiki-env |
|
255
|
test json-artifact-wiki {[dict get $JR payload type] eq "wiki"} |
|
256
|
test_json_payload json-artifact-wiki [list type uuid artifact] {} |
|
257
|
set artifact [dict get $JR payload artifact] |
|
258
|
test_dict_keys json-artifact-wiki-artifact $artifact \ |
|
259
|
[list name uuid user timestamp size] {} |
|
260
|
# name, uuid, parent?, user, timestamp, size?, content? |
|
261
|
|
|
262
|
|
|
263
|
#### AUTHENTICATION |
|
264
|
fossil_json anonymousPassword |
|
265
|
test_json_envelope_ok json-anonymousPassword-env |
|
266
|
test_json_payload json-anonymousPassword {seed password} {} |
|
267
|
set seed [dict get $JR payload seed] |
|
268
|
set pass [dict get $JR payload password] |
|
269
|
|
|
270
|
write_file anon-1 [subst { |
|
271
|
{ |
|
272
|
"command":"login", |
|
273
|
"payload":{ |
|
274
|
"name":"anonymous", |
|
275
|
"anonymousSeed":$seed, |
|
276
|
"password":"$pass" |
|
277
|
} |
|
278
|
} |
|
279
|
}] |
|
280
|
fossil_json --json-input anon-1 |
|
281
|
test_json_envelope_ok json-login-a-env |
|
282
|
test_json_payload json-login-a {authToken name capabilities loginCookieName} {} |
|
283
|
set AuthAnon [dict get $JR payload] |
|
284
|
proc test_hascaps {testname need caps} { |
|
285
|
foreach n [split $need {}] { |
|
286
|
test $testname-$n {[string first $n $caps] >= 0} |
|
287
|
} |
|
288
|
} |
|
289
|
test_hascaps json-login-c "hz" [dict get $AuthAnon capabilities] |
|
290
|
|
|
291
|
fossil user new U1 User-1 Uone |
|
292
|
fossil user capabilities U1 s |
|
293
|
write_file u1 { |
|
294
|
{ |
|
295
|
"command":"login", |
|
296
|
"payload":{ |
|
297
|
"name":"U1", |
|
298
|
"password":"Uone" |
|
299
|
} |
|
300
|
} |
|
301
|
} |
|
302
|
fossil_json --json-input u1 |
|
303
|
test_json_envelope_ok json-login-u1-env |
|
304
|
test_json_payload json-login-u1 {authToken name capabilities loginCookieName} {} |
|
305
|
set AuthU1 [dict get $JR payload] |
|
306
|
test_hascaps json-login-c "s" [dict get $AuthU1 capabilities] |
|
307
|
|
|
308
|
set U1Cookie [dict get $AuthU1 loginCookieName]=[regsub -all {[/]} [dict get $AuthU1 authToken] {%2F} ] |
|
309
|
set AnonCookie [dict get $AuthAnon loginCookieName]=[regsub -all {[/]} [dict get $AuthAnon authToken] {%2F} ] |
|
310
|
|
|
311
|
# json cap |
|
312
|
# The CLI user has all rights, and no auth token affects that. This |
|
313
|
# is consistent with the rest of the fossil CLI, and with the |
|
314
|
# pragmatic argument that using the CLI implies physical access to |
|
315
|
# the repo file itself, which can be taunted with many tools |
|
316
|
# including raw SQLite which will also ignore authentication. |
|
317
|
write_file anon-2 [subst { |
|
318
|
{"command":"cap", |
|
319
|
"authToken":"[dict get $AuthAnon authToken]" |
|
320
|
} |
|
321
|
}] |
|
322
|
fossil_json --json-input anon-2 |
|
323
|
test_json_envelope_ok json-cap-env |
|
324
|
test json-cap-CLI {[dict get $JR payload permissionFlags setup]} |
|
325
|
|
|
326
|
# json cap via POST with authToken in request envelope |
|
327
|
set anon2 [read_file anon-2] |
|
328
|
fossil_post_json "/json/cap" $anon2 |
|
329
|
test json-cap-POSTenv-env-0 {[string length $JR] > 0} |
|
330
|
test_json_envelope_ok json-cap-POSTenv-env |
|
331
|
if {[catch {test json-cap-POSTenv-name \ |
|
332
|
{[dict get $JR payload name] eq "anonymous"} knownBug} jerr]} then { |
|
333
|
test json-cap-POSTenv-name-threw 0 |
|
334
|
protOut "CAUGHT: $jerr" |
|
335
|
} |
|
336
|
test json-cap-POSTenv-notsetup {![dict get $JR payload permissionFlags setup]} |
|
337
|
|
|
338
|
|
|
339
|
# json cap via GET with authToken in Cookie header |
|
340
|
fossil_post_json "/json/cap" {} $AnonCookie |
|
341
|
test json-cap-GETcookie-env-0 {[string length $JR] > 0} |
|
342
|
test_json_envelope_ok json-cap-GETcookie-env-0 |
|
343
|
if {[catch {test json-cap-GETcookie-name-0 \ |
|
344
|
{[dict get $JR payload name] eq "anonymous"}} jerr]} then { |
|
345
|
test json-cap-GETcookie-name-0-threw 0 |
|
346
|
protOut "CAUGHT: $jerr" |
|
347
|
} |
|
348
|
test json-cap-GETcookie-notsetup-0 {![dict get $JR payload permissionFlags setup]} |
|
349
|
|
|
350
|
|
|
351
|
# json cap via GET with authToken in a parameter |
|
352
|
fossil_post_json "/json/cap?authToken=[dict get $AuthAnon authToken]" {} |
|
353
|
test json-cap-GETcookie-env-1 {[string length $JR] > 0} |
|
354
|
test_json_envelope_ok json-cap-GETcookie-env-1 |
|
355
|
if {[catch {test json-cap-GETcookie-name-1 \ |
|
356
|
{[dict get $JR payload name] eq "anonymous"}} jerr]} then { |
|
357
|
test json-cap-GETcookie-name-1-threw 0 |
|
358
|
protOut "CAUGHT: $jerr" |
|
359
|
} |
|
360
|
test json-cap-GETcookie-notsetup-1 {![dict get $JR payload permissionFlags setup]} |
|
361
|
|
|
362
|
|
|
363
|
# whoami |
|
364
|
# via CLI with no auth token supplied |
|
365
|
fossil_json whoami |
|
366
|
test_json_envelope_ok json-whoami-cli-env |
|
367
|
test_json_payload json-whoami-cli {name capabilities} {} |
|
368
|
test json-whoami-cli-name {[dict get $JR payload name] eq "nobody"} |
|
369
|
test_hascaps json-whoami-cli-cap "gjorz" [dict get $JR payload capabilities] |
|
370
|
|
|
371
|
#### BRANCHES |
|
372
|
# json branch list |
|
373
|
fossil_json branch list |
|
374
|
test_json_envelope_ok json-branch-list-env |
|
375
|
test_json_payload json-branch-list {range current branches} {} |
|
376
|
test json-branch-list-cur {[dict get $JR payload current] eq "trunk"} |
|
377
|
test json-branch-list-cnt {[llength [dict get $JR payload branches]] == 1} |
|
378
|
test json-branch-list-val {[dict get $JR payload branches] eq "trunk"} |
|
379
|
|
|
380
|
# json branch create |
|
381
|
fossil_json branch create alpha --basis trunk |
|
382
|
test_json_envelope_ok json-branch-create-env |
|
383
|
test_json_payload json-branch-create {name basis rid uuid isPrivate} {} |
|
384
|
|
|
385
|
|
|
386
|
#### CONFIG |
|
387
|
# json config get AREA |
|
388
|
# AREAs are skin ticket project all skin-backup |
|
389
|
foreach a [list skin ticket project all skin-backup] { |
|
390
|
fossil_json config get $a |
|
391
|
test_json_envelope_ok json-config-$a-env |
|
392
|
# payload depends on specific area and may be completely empty |
|
393
|
} |
|
394
|
|
|
395
|
#### DIFFS |
|
396
|
# json diff v1 v2 |
|
397
|
|
|
398
|
write_file fish { |
|
399
|
ABCD goldfish |
|
400
|
} |
|
401
|
fossil add fish |
|
402
|
fossil ci -m "goldfish" |
|
403
|
fossil_json finfo fish |
|
404
|
set fishHist [dict get $JR payload checkins] |
|
405
|
set fishV1 [dict get [lindex $fishHist 0] uuid] |
|
406
|
|
|
407
|
write_file fish { |
|
408
|
ABCD goldfish |
|
409
|
LMNO goldfish |
|
410
|
} |
|
411
|
fossil ci -m "goldfish" |
|
412
|
fossil_json finfo fish |
|
413
|
set fishHist [dict get $JR payload checkins] |
|
414
|
set fishV2 [dict get [lindex $fishHist 0] uuid] |
|
415
|
|
|
416
|
test fossil-diff-setup {$fishV1 ne $fishV2} |
|
417
|
fossil_json diff $fishV1 $fishV2 |
|
418
|
test_json_envelope_ok json-diff-env |
|
419
|
test_json_payload json-diff {from to diff} {} |
|
420
|
test json-diff-v1 {[dict get $JR payload from] eq $fishV1} |
|
421
|
test json-diff-v2 {[dict get $JR payload to] eq $fishV2} |
|
422
|
set diff [dict get $JR payload diff] |
|
423
|
test json-diff-diff {[string first "+LMNO goldfish" $diff] >= 0} |
|
424
|
protOut [dict get $JR payload diff] |
|
425
|
|
|
426
|
|
|
427
|
#### DIRECTORY LISTING |
|
428
|
# json dir DIRNAME |
|
429
|
fossil_json dir |
|
430
|
test_json_envelope_ok json-dir-env |
|
431
|
test_json_payload json-dir {name entries} {} |
|
432
|
|
|
433
|
#### FILE INFO |
|
434
|
# json finfo FILENAME |
|
435
|
fossil_json finfo empty |
|
436
|
test_json_envelope_ok json-finfo-env |
|
437
|
test_json_payload json-finfo {name checkins} {} |
|
438
|
|
|
439
|
#### QUERY |
|
440
|
# json query SQLCODE |
|
441
|
fossil_json query {"SELECT * FROM reportfmt"} |
|
442
|
test_json_envelope_ok json-query-env |
|
443
|
test_json_payload json-query {columns rows} {} |
|
444
|
|
|
445
|
#### STATS |
|
446
|
# json stat |
|
447
|
fossil_json stat |
|
448
|
test_json_envelope_ok json-stat-env |
|
449
|
test_json_payload json-stat {repositorySize ageDays ageYears projectCode compiler sqlite} \ |
|
450
|
{blobCount deltaCount uncompressedArtifactSize averageArtifactSize maxArtifactSize \ |
|
451
|
compressionRatio checkinCount fileCount wikiPageCount ticketCount} |
|
452
|
|
|
453
|
fossil_json stat -f |
|
454
|
test_json_envelope_ok json-stat-env |
|
455
|
test_json_payload json-stat {repositorySize \ |
|
456
|
blobCount deltaCount uncompressedArtifactSize averageArtifactSize maxArtifactSize \ |
|
457
|
compressionRatio checkinCount fileCount wikiPageCount ticketCount \ |
|
458
|
ageDays ageYears projectCode compiler sqlite} {} |
|
459
|
|
|
460
|
|
|
461
|
#### STATUS |
|
462
|
# NOTE: Local checkout required |
|
463
|
# json status |
|
464
|
fossil_json status |
|
465
|
test_json_envelope_ok json-status-env |
|
466
|
test_json_payload json-status {repository localRoot checkout files errorCount} {} |
|
467
|
|
|
468
|
#### TAGS |
|
469
|
|
|
470
|
# json tag add NAME CHECKIN VALUE |
|
471
|
fossil_json tag add blue trunk green |
|
472
|
test_json_envelope_ok json-tag-add-env |
|
473
|
test_json_payload json-tag-add {name value propagate raw appliedTo} {} |
|
474
|
|
|
475
|
|
|
476
|
# json tag cancel NAME CHECKIN |
|
477
|
fossil_json tag add cancel alpha |
|
478
|
test_json_envelope_ok json-tag-cancel-env |
|
479
|
# DOCBUG? Doc says no payload. |
|
480
|
test_json_payload json-tag-cancel {name value propagate raw appliedTo} {} |
|
481
|
|
|
482
|
# json tag find NAME |
|
483
|
fossil_json tag find alpha |
|
484
|
test_json_envelope_ok json-tag-find-env |
|
485
|
test_json_payload json-tag-find {name raw type limit artifacts} {} |
|
486
|
test json-tag-find-count {[llength [dict get $JR payload artifacts]] >= 1} |
|
487
|
|
|
488
|
# json tag list CHECKIN |
|
489
|
fossil_json tag list |
|
490
|
test_json_envelope_ok json-tag-list-env |
|
491
|
test_json_payload json-tag-list {raw includeTickets tags} {} |
|
492
|
test json-tag-list-count {[llength [dict get $JR payload tags]] >= 2} |
|
493
|
|
|
494
|
|
|
495
|
#### TICKETS |
|
496
|
# API Docs say not yet defined, so it isn't quite fair to mark this |
|
497
|
# category as TODO for the test cases... |
|
498
|
|
|
499
|
#### TICKET REPORTS |
|
500
|
|
|
501
|
# json report get NUMBER |
|
502
|
fossil_json report get 1 |
|
503
|
test_json_envelope_ok json-report-get-env |
|
504
|
test_json_payload json-report-get {report owner title timestamp columns sqlCode} {} |
|
505
|
|
|
506
|
# json report list |
|
507
|
fossil_json report list |
|
508
|
test_json_envelope_ok json-report-list-env |
|
509
|
#test_json_payload json-report-list {raw includeTickets tags} {} |
|
510
|
test json-report-list-count {[llength [dict get $JR payload]] >= 1} |
|
511
|
|
|
512
|
|
|
513
|
# json report run NUMBER |
|
514
|
fossil_json report run 1 |
|
515
|
test_json_envelope_ok json-report-run-1-env |
|
516
|
test_json_payload json-report-list {report title sqlcode columnNames tickets} {} |
|
517
|
test json-report-list-count {[llength [dict get $JR payload columnNames]] >= 7} |
|
518
|
test json-report-list-count {[llength [dict get $JR payload tickets]] >= 0} |
|
519
|
|
|
520
|
|
|
521
|
#### TIMELINE |
|
522
|
|
|
523
|
# json timeline checkin |
|
524
|
fossil_json timeline checkin |
|
525
|
test_json_envelope_ok json-timeline-checkin-env |
|
526
|
test_json_payload json-timeline-checkin {limit timeline} {} |
|
527
|
set i 0 |
|
528
|
foreach t [dict get $JR payload timeline] { |
|
529
|
# parents appears only for entries that have a parent |
|
530
|
# files appears only if requested by the --files parameter |
|
531
|
test_dict_keys json-timeline-checkin-$i $t {type uuid timestamp comment user isLeaf tags} {} |
|
532
|
incr i |
|
533
|
} |
|
534
|
|
|
535
|
# json timeline ci |
|
536
|
# removed from documentation |
|
537
|
#fossil_json timeline ci |
|
538
|
#test json-timeline-ci {[dict get $JR resultCode] ne "FOSSIL-1102"} knownBug |
|
539
|
#test_json_payload json-timeline-ci {limit timeline} {} |
|
540
|
|
|
541
|
# json timeline ticket |
|
542
|
fossil_json timeline ticket |
|
543
|
test_json_envelope_ok json-timeline-ticket-env |
|
544
|
test_json_payload json-timeline-ticket {limit timeline} {} |
|
545
|
|
|
546
|
# json timeline wiki |
|
547
|
fossil_json timeline wiki |
|
548
|
test_json_envelope_ok json-timeline-wiki-env |
|
549
|
test_json_payload json-timeline-wiki {limit timeline} {} |
|
550
|
|
|
551
|
|
|
552
|
#### USER MANAGEMENT |
|
553
|
|
|
554
|
# json user get |
|
555
|
foreach u [list nobody anonymous reader developer U1] { |
|
556
|
fossil_json user get $u |
|
557
|
test_json_envelope_ok json-user-get-$u-env |
|
558
|
test_json_payload json-user-get-$u {uid name capabilities info timestamp} {} |
|
559
|
} |
|
560
|
|
|
561
|
# json user list |
|
562
|
fossil_json user list |
|
563
|
test_json_envelope_ok json-user-list-env |
|
564
|
set i 0 |
|
565
|
foreach u [dict get $JR payload] { |
|
566
|
test_dict_keys json-user-list-$i $u {uid name capabilities info timestamp} {} |
|
567
|
incr i |
|
568
|
} |
|
569
|
|
|
570
|
# json user save |
|
571
|
fossil_json user save --uid -1 --name U2 --password Utwo |
|
572
|
test_json_envelope_ok json-user-save-env |
|
573
|
test_json_payload json-user-save {uid name capabilities info timestamp} {} |
|
574
|
|
|
575
|
|
|
576
|
# DOCBUG? Doc says payload is "same as /json/user/get" but actual |
|
577
|
# result was an array of one user similar to /json/user/list. |
|
578
|
#set i 0 |
|
579
|
#foreach u [dict get $JR payload] { |
|
580
|
# test_dict_keys json-user-save-$i $u {uid name capabilities info timestamp} {} |
|
581
|
# incr i |
|
582
|
#} |
|
583
|
#test json-user-save-count {$i == 1} |
|
584
|
|
|
585
|
|
|
586
|
|
|
587
|
#### WIKI |
|
588
|
|
|
589
|
# wiki list |
|
590
|
fossil_json wiki list |
|
591
|
test_json_envelope_ok json-wiki-list-env |
|
592
|
set pages [dict get $JR payload] |
|
593
|
test json-wiki-1 {[llength $pages] == 1} |
|
594
|
test json-wiki-2 {[lindex $pages 0] eq "Empty"} |
|
595
|
fossil_json wiki list --verbose |
|
596
|
set pages [dict get $JR payload] |
|
597
|
test json-wiki-verbose-1 {[llength $pages] == 1} |
|
598
|
test_dict_keys json-wiki-verbose-pages [lindex $pages 0] [list name uuid user timestamp size] {} |
|
599
|
|
|
600
|
# wiki get |
|
601
|
fossil_json wiki get Empty |
|
602
|
test_json_envelope_ok json-wiki-get-env |
|
603
|
# this page has only one version, so no parent should be listed |
|
604
|
test_json_payload json-wiki-get [list name uuid user timestamp size content] [list parent] |
|
605
|
|
|
606
|
|
|
607
|
# wiki create |
|
608
|
# requires an authToken? Not from CLI. |
|
609
|
|
|
610
|
write_file req.json { |
|
611
|
{ |
|
612
|
"command":"wiki/create", |
|
613
|
"payload":{ |
|
614
|
"name":"Page2", |
|
615
|
"content":"Lorem ipsum dolor sic amet." |
|
616
|
} |
|
617
|
} |
|
618
|
} |
|
619
|
fossil_json --json-input req.json |
|
620
|
test_json_envelope_ok json-wiki-create-env |
|
621
|
fossil_json wiki get Page2 |
|
622
|
test_json_envelope_ok json-wiki-create-get-env |
|
623
|
test_json_payload json-wiki-save-get [list name uuid user timestamp size content] {parent} |
|
624
|
set uuid1 [dict get $JR payload uuid] |
|
625
|
|
|
626
|
# wiki save |
|
627
|
|
|
628
|
write_file req2.json { |
|
629
|
{ |
|
630
|
"command":"wiki/save", |
|
631
|
"payload":{ |
|
632
|
"name":"Page2", |
|
633
|
"content":"Lorem ipsum dolor sic amet.\nconsectetur adipisicing elit." |
|
634
|
} |
|
635
|
} |
|
636
|
} |
|
637
|
fossil_json --json-input req2.json |
|
638
|
test_json_envelope_ok json-wiki-save-env |
|
639
|
fossil_json wiki get Page2 |
|
640
|
test_json_envelope_ok json-wiki-save-get-env |
|
641
|
test_json_payload json-wiki-save-get [list name uuid user timestamp size parent content] {} |
|
642
|
set uuid2 [dict get $JR payload uuid] |
|
643
|
test json-wiki-save-parent {[dict get $JR payload parent] eq $uuid1} |
|
644
|
|
|
645
|
# wiki diff |
|
646
|
|
|
647
|
fossil_json wiki diff $uuid1 $uuid2 |
|
648
|
test_json_envelope_ok json-wiki-diff-env |
|
649
|
test_json_payload json-wiki-diff [list v1 v2 diff] {} |
|
650
|
test json-wiki-diff-v1 {[dict get $JR payload v1] eq $uuid1} |
|
651
|
test json-wiki-diff-v1 {[dict get $JR payload v2] eq $uuid2} |
|
652
|
set diff [dict get $JR payload diff] |
|
653
|
test json-wiki-diff-diff {[string first "+consectetur adipisicing elit" $diff] >= 0} |
|
654
|
#puts [dict get $JR payload diff] |
|
655
|
|
|
656
|
# wiki preview |
|
657
|
# |
|
658
|
# takes a string in fossil wiki markup and return an HTML fragment. |
|
659
|
# This command does not make use of the actual wiki content (much?) |
|
660
|
# at all. |
|
661
|
write_file req3.json { |
|
662
|
{ |
|
663
|
"command":"wiki/preview", |
|
664
|
"payload":"Lorem ipsum dolor sic amet.\nconsectetur adipisicing elit." |
|
665
|
} |
|
666
|
} |
|
667
|
fossil_json --json-input req3.json |
|
668
|
test_json_envelope_ok json-wiki-preview-env |
|
669
|
set pv [dict get $JR payload] |
|
670
|
test json-wiki-preview-out-1 {[string first "<p>Lorem ipsum" $pv] == 0} |
|
671
|
test json-wiki-preview-out-2 {[string last "<p>" $pv] == 0} |
|
672
|
|
|
673
|
#### UNAVOIDABLE MISC |
|
674
|
|
|
675
|
# json g |
|
676
|
fossil_json g |
|
677
|
test_json_envelope_ok json-g-env |
|
678
|
#puts [llength [dict keys [dict get $JR payload]]] |
|
679
|
test json-g-g {[llength [dict keys [dict get $JR payload]]] >= 60};# 64 on my PC |
|
680
|
|
|
681
|
# json rebuild |
|
682
|
fossil_json rebuild |
|
683
|
test_json_envelope json-rebuild-env [concat fossil timestamp command procTimeUs \ |
|
684
|
procTimeMs] [concat payload resultCode resultText] |
|
685
|
|
|
686
|
# json resultCodes |
|
687
|
fossil_json resultCodes |
|
688
|
test_json_envelope_ok json-resultCodes-env |
|
689
|
set codes [dict get $JR payload] |
|
690
|
test json-resultCodes-codes-1 {[llength $codes] >= 35} ;# count as of API 20120713 |
|
691
|
# foreach c $codes { |
|
692
|
# puts [dict values $c] |
|
693
|
# } |
|
694
|
foreach r $codes { |
|
695
|
protOut "# [dict get $r resultCode] [dict get $r cSymbol]\n# [dict get $r description]" |
|
696
|
} |
|
697
|
|
|
698
|
|
|
699
|
|
|
700
|
#### From the API Docs |
|
701
|
|
|
702
|
# Reminder to self: in March 2012 i saw a corner-case which returns |
|
703
|
# HTML output. To reproduce: chmod 444 REPO, then submit a request |
|
704
|
# which writes something (timeline creates a temp table). The "repo |
|
705
|
# is not writable" error comes back as HTML. i don't know if the |
|
706
|
# error happens before we have made the determination that the app is |
|
707
|
# in JSON mode or if the error handling is incorrectly not |
|
708
|
# recognizing JSON mode. |
|
709
|
# |
|
710
|
#test_setup x.fossil |
|
711
|
fossil_http_json /json/query?sql=PRAGMA%20repository.journal_mode%3Dwal $U1Cookie |
|
712
|
test json-ROrepo-1-1 {$CODE == 0} |
|
713
|
test json-ROrepo-1-2 {[regexp {\}\s*$} $RESULT]} |
|
714
|
test json-ROrepo-1-3 {![regexp {SQLITE_[A-Z]+:} $RESULT]} |
|
715
|
test_json_envelope_ok json-http-timeline1 |
|
716
|
if {$is_windows} then { |
|
717
|
catch {exec attrib +r .rep.fossil}; # Windows |
|
718
|
} else { |
|
719
|
catch {exec chmod 444 .rep.fossil}; # Unix |
|
720
|
} |
|
721
|
protOut "chmod 444 repo" |
|
722
|
fossil_http_json /json/query?sql=PRAGMA%20repository.journal_mode%3Ddelete $U1Cookie -expectError --json-preserve-rc |
|
723
|
test json-ROrepo-2-1 {$CODE != 0} |
|
724
|
test json-ROrepo-2-2 {[regexp {\}\s*$} $RESULT]} |
|
725
|
test json-ROrepo-2-3 {![regexp {SQLITE_[A-Z]+:} $RESULT]} |
|
726
|
#test_json_envelope_ok json-http-timeline2 |
|
727
|
if {$is_windows} then { |
|
728
|
catch {exec attrib -r .rep.fossil}; # Windows |
|
729
|
catch {exec attrib -r .rep.fossil-shm} |
|
730
|
catch {exec attrib -r .rep.fossil-wal} |
|
731
|
} else { |
|
732
|
catch {exec chmod 666 .rep.fossil}; # Unix |
|
733
|
catch {exec chmod 666 .rep.fossil-shm} |
|
734
|
catch {exec chmod 666 .rep.fossil-wal} |
|
735
|
} |
|
736
|
protOut "chmod 666 repo" |
|
737
|
|
|
738
|
#### Result Codes |
|
739
|
# Test cases designed to stimulate each (documented) error code. |
|
740
|
|
|
741
|
# FOSSIL-0000 |
|
742
|
# Not returned by any command. We generally verify that in the |
|
743
|
# test_json_envelope_ok command by verifying that the resultCode |
|
744
|
# field is not present. Should any JSON endpoint begin to use the |
|
745
|
# range reserved for non-fatal warnings, those tests will fail. |
|
746
|
# |
|
747
|
# Notice that code is not included in the list returned from |
|
748
|
# /json/resultCodes. |
|
749
|
|
|
750
|
|
|
751
|
# FOSSIL-1000 FSL_JSON_E_GENERIC |
|
752
|
# Generic error |
|
753
|
|
|
754
|
# FOSSIL-1101 FSL_JSON_E_INVALID_REQUEST |
|
755
|
# Invalid request |
|
756
|
write_file e1101.json { |
|
757
|
["command","nope"] |
|
758
|
} |
|
759
|
fossil_json --json-input e1101.json -expectError |
|
760
|
test json-RC-1101-array-CLI-exit {$CODE != 0} |
|
761
|
test_json_envelope json-RC-1101-array-env {fossil timestamp command procTimeUs \ |
|
762
|
procTimeMs resultCode resultText} {payload} |
|
763
|
test json-RC-1101-array-code {[dict get $JR resultCode] eq "FOSSIL-1101"} |
|
764
|
|
|
765
|
write_file e1101.json { |
|
766
|
"Not really a command but more of a suggestion" |
|
767
|
} |
|
768
|
fossil_json --json-input e1101.json -expectError |
|
769
|
test json-RC-1101-string-CLI-exit {$CODE != 0} |
|
770
|
test_json_envelope json-RC-1101-string-env {fossil timestamp command procTimeUs \ |
|
771
|
procTimeMs resultCode resultText} {payload} |
|
772
|
test json-RC-1101-string-code {[dict get $JR resultCode] eq "FOSSIL-1101"} |
|
773
|
|
|
774
|
|
|
775
|
|
|
776
|
|
|
777
|
# FOSSIL-1102 FSL_JSON_E_UNKNOWN_COMMAND |
|
778
|
# Unknown command or subcommand |
|
779
|
fossil_json NoSuchEndpoint -expectError |
|
780
|
test json-RC-1102-CLI-exit {$CODE != 0} |
|
781
|
test_json_envelope json-RC-1102-env {fossil timestamp command procTimeUs \ |
|
782
|
procTimeMs resultCode resultText} {payload} |
|
783
|
test json-RC-1102-code {[dict get $JR resultCode] eq "FOSSIL-1102"} |
|
784
|
|
|
785
|
write_file e1102.json { |
|
786
|
{ |
|
787
|
"command":"no/such/endpoint" |
|
788
|
} |
|
789
|
} |
|
790
|
fossil_json --json-input e1102.json -expectError |
|
791
|
test json-env-RC-1102a-CLI-exit {$CODE != 0} |
|
792
|
test_json_envelope json-env-RC-1102a-env {fossil timestamp command procTimeUs \ |
|
793
|
procTimeMs resultCode resultText} {payload} |
|
794
|
test json-env-RC-1102a-code {[dict get $JR resultCode] eq "FOSSIL-1102"} |
|
795
|
|
|
796
|
|
|
797
|
# FOSSIL-1103 FSL_JSON_E_UNKNOWN |
|
798
|
# Unknown error |
|
799
|
|
|
800
|
write_file bad.sql { |
|
801
|
CREATE TABLE spam(a integer, b text); |
|
802
|
} |
|
803
|
exec $::fossilexe sqlite3 --no-repository bad.fossil <bad.sql |
|
804
|
fossil_json HAI -R bad.fossil -expectError |
|
805
|
test json-env-RC-1103-CLI-exit {$CODE != 0} |
|
806
|
if { $JR ne "" } { |
|
807
|
test_json_envelope json-env-RC-1103-env {fossil timestamp command procTimeUs \ |
|
808
|
procTimeMs resultCode resultText} {payload} |
|
809
|
test json-env-RC-1103-code {[dict exists $JR resultCode]\ |
|
810
|
&& [dict get $JR resultCode] eq "FOSSIL-1103"} knownBug |
|
811
|
} else { |
|
812
|
protOut "Want test case for FOSSIL-1103" |
|
813
|
test json-RC-1103 0 knownBug |
|
814
|
} |
|
815
|
|
|
816
|
# FOSSIL-1104 FSL_JSON_E_TIMEOUT |
|
817
|
# Timeout reached |
|
818
|
# FOSSIL-1105 FSL_JSON_E_ASSERT |
|
819
|
# Assertion failed |
|
820
|
# FOSSIL-1106 FSL_JSON_E_ALLOC |
|
821
|
# Resource allocation failed |
|
822
|
# FOSSIL-1107 FSL_JSON_E_NYI |
|
823
|
# Not yet implemented |
|
824
|
# FOSSIL-1108 FSL_JSON_E_PANIC |
|
825
|
# x |
|
826
|
# FOSSIL-1109 FSL_JSON_E_MANIFEST_READ_FAILED |
|
827
|
# Reading artifact manifest failed |
|
828
|
# FOSSIL-1110 FSL_JSON_E_FILE_OPEN_FAILED |
|
829
|
# Opening file failed |
|
830
|
|
|
831
|
# FOSSIL-2000 FSL_JSON_E_AUTH |
|
832
|
# Authentication error |
|
833
|
# FOSSIL-2001 FSL_JSON_E_MISSING_AUTH |
|
834
|
# Authentication info missing from request |
|
835
|
# FOSSIL-2002 FSL_JSON_E_DENIED |
|
836
|
# Access denied |
|
837
|
# FOSSIL-2003 FSL_JSON_E_WRONG_MODE |
|
838
|
# Request not allowed (wrong operation mode) |
|
839
|
# FOSSIL-2100 FSL_JSON_E_LOGIN_FAILED |
|
840
|
# Login failed |
|
841
|
# FOSSIL-2101 FSL_JSON_E_LOGIN_FAILED_NOSEED |
|
842
|
# Anonymous login attempt was missing password seed |
|
843
|
# FOSSIL-2102 FSL_JSON_E_LOGIN_FAILED_NONAME |
|
844
|
# Login failed - name not supplied |
|
845
|
# FOSSIL-2103 FSL_JSON_E_LOGIN_FAILED_NOPW |
|
846
|
# Login failed - password not supplied |
|
847
|
# FOSSIL-2104 FSL_JSON_E_LOGIN_FAILED_NOTFOUND |
|
848
|
# Login failed - no match found |
|
849
|
|
|
850
|
# FOSSIL-3000 FSL_JSON_E_USAGE |
|
851
|
# Usage error |
|
852
|
# FOSSIL-3001 FSL_JSON_E_INVALID_ARGS |
|
853
|
# Invalid argument(s) |
|
854
|
|
|
855
|
# FOSSIL-3002 FSL_JSON_E_MISSING_ARGS |
|
856
|
# Missing argument(s) |
|
857
|
write_file e3002.json { |
|
858
|
{"color":"yellow", |
|
859
|
"really":"no, blue", |
|
860
|
"number":42 |
|
861
|
} |
|
862
|
} |
|
863
|
fossil_json --json-input e3002.json -expectError |
|
864
|
test json-RC-3002-strange-CLI-exit {$CODE != 0} |
|
865
|
test_json_envelope json-RC-3002-strange-env {fossil timestamp command procTimeUs \ |
|
866
|
procTimeMs resultCode resultText} {payload} |
|
867
|
test json-RC-3002-strange-code {[dict get $JR resultCode] eq "FOSSIL-3002"} |
|
868
|
|
|
869
|
|
|
870
|
# FOSSIL-3003 FSL_JSON_E_AMBIGUOUS_UUID |
|
871
|
# Resource identifier is ambiguous |
|
872
|
# FOSSIL-3004 FSL_JSON_E_UNRESOLVED_UUID |
|
873
|
# Provided uuid/tag/branch could not be resolved |
|
874
|
# FOSSIL-3005 FSL_JSON_E_RESOURCE_ALREADY_EXISTS |
|
875
|
# Resource already exists |
|
876
|
# FOSSIL-3006 FSL_JSON_E_RESOURCE_NOT_FOUND |
|
877
|
# Resource not found |
|
878
|
|
|
879
|
# FOSSIL-4000 FSL_JSON_E_DB |
|
880
|
# Database error |
|
881
|
# FOSSIL-4001 FSL_JSON_E_STMT_PREP |
|
882
|
# Statement preparation failed |
|
883
|
# FOSSIL-4002 FSL_JSON_E_STMT_BIND |
|
884
|
# Statement parameter binding failed |
|
885
|
# FOSSIL-4003 FSL_JSON_E_STMT_EXEC |
|
886
|
# Statement execution/stepping failed |
|
887
|
# FOSSIL-4004 FSL_JSON_E_DB_LOCKED |
|
888
|
# Database is locked |
|
889
|
# FOSSIL-4101 FSL_JSON_E_DB_NEEDS_REBUILD |
|
890
|
# Fossil repository needs to be rebuilt |
|
891
|
|
|
892
|
# FOSSIL-4102 FSL_JSON_E_DB_NOT_FOUND |
|
893
|
# Fossil repository db file could not be found. |
|
894
|
fossil close |
|
895
|
fossil_json HAI -expectError |
|
896
|
test json-RC-4102-CLI-exit {$CODE != 0} |
|
897
|
test_json_envelope json-RC-4102-CLI-exit {fossil timestamp command procTimeUs \ |
|
898
|
procTimeMs resultCode resultText} {payload} |
|
899
|
test json-RC-4102 {[dict get $JR resultCode] eq "FOSSIL-4102"} |
|
900
|
|
|
901
|
# FOSSIL-4103 FSL_JSON_E_DB_NOT_VALID |
|
902
|
# Fossil repository db file is not valid. |
|
903
|
write_file nope.fossil { |
|
904
|
This is not a fossil repo. It ought to be a SQLite db with a well-known schema, |
|
905
|
but it is actually just a block of text. |
|
906
|
} |
|
907
|
fossil_json HAI -R nope.fossil -expectError |
|
908
|
test json-RC-4103-CLI-exit {$CODE != 0} |
|
909
|
if { $JR ne "" } { |
|
910
|
test_json_envelope json-RC-4103-CLI {fossil timestamp command procTimeUs \ |
|
911
|
procTimeMs resultCode resultText} {payload} |
|
912
|
test json-RC-4103 {[dict get $JR resultCode] eq "FOSSIL-4103"} |
|
913
|
} else { |
|
914
|
test json-RC-4103 0 knownBug |
|
915
|
} |
|
916
|
|
|
917
|
############################################################################### |
|
918
|
|
|
919
|
test_cleanup |
|
920
|
|
|
921
|
if { $_fossil_user eq "" } { |
|
922
|
unset ::env(FOSSIL_USER) |
|
923
|
} else { |
|
924
|
set ::env(FOSSIL_USER) $_fossil_user |
|
925
|
} |
|
926
|
|