#!/bin/bash # Tests for Serval DNA HTTP RESTful interface # # Copyright 2013 Serval Project, Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. source "${0%/*}/../testframework.sh" source "${0%/*}/../testdefs.sh" source "${0%/*}/../testdefs_rhizome.sh" source "${0%/*}/../testdefs_meshms.sh" shopt -s extglob assertJq() { local json="$1" local jqscript="$2" assert --message="$jqscript" --dump-on-fail="$json" [ "$(jq "$jqscript" "$json")" = true ] } assertJqCmp() { local opts=() while [ $# -gt 0 ]; do case "$1" in --) shift; break;; --*) opts+=("$1"); shift;; *) break;; esac done [ $# -eq 3 ] || error "invalid arguments" local json="$1" local jqscript="$2" local file="$3" jq --raw-output "$jqscript" "$json" >"$TFWTMP/jqcmp.tmp" assert --dump-on-fail="$TFWTMP/jqcmp.tmp" --dump-on-fail="$file" "${opts[@]}" cmp "$TFWTMP/jqcmp.tmp" "$file" } assertJqIs() { local opts=() while [ $# -gt 0 ]; do case "$1" in --) shift; break;; --*) opts+=("$1"); shift;; *) break;; esac done [ $# -eq 3 ] || error "invalid arguments" local json="$1" local jqscript="$2" local text="$3" local jqout="$(jq --raw-output "$jqscript" "$json")" assert "${opts[@]}" [ "$jqout" = "$text" ] } assertJqGrep() { local opts=() while [ $# -gt 0 ]; do case "$1" in --) shift; break;; --*) opts+=("$1"); shift;; *) break;; esac done [ $# -eq 3 ] || error "invalid arguments" local json="$1" local jqscript="$2" local pattern="$3" jq "$jqscript" "$json" >"$TFWTMP/jqgrep.tmp" assertGrep "${opts[@]}" "$TFWTMP/jqgrep.tmp" "$pattern" } setup() { CR=' ' VT=' ' setup_curl 7 setup_jq 1.3 setup_servald set_instance +A set_rhizome_config executeOk_servald config \ set rhizome.api.restful.users.harry.password potter \ set rhizome.api.restful.users.ron.password weasley \ set rhizome.api.restful.users.hermione.password grainger set_extra_config if [ -z "$IDENTITY_COUNT" ]; then create_single_identity else create_identities $IDENTITY_COUNT fi start_servald_instances +A wait_until rhizome_http_server_started +A get_rhizome_server_port PORTA +A } finally() { stop_all_servald_servers } teardown() { kill_all_servald_processes assert_no_servald_processes report_all_servald_servers } set_extra_config() { : } set_rhizome_config() { executeOk_servald config \ set debug.http_server on \ set debug.httpd on \ set debug.rhizome_manifest on \ set debug.rhizome_store on \ set debug.rhizome on \ set debug.verbose on \ set log.console.level debug } doc_AuthBasicMissing="HTTP RESTful missing Basic Authentication credentials" test_AuthBasicMissing() { executeOk curl \ --silent --show-error --write-out '%{http_code}' \ --output http.output \ --dump-header http.headers \ "http://$addr_localhost:$PORTA/restful/rhizome/bundlelist.json" assertStdoutIs '401' assertGrep http.headers "^WWW-Authenticate: Basic realm=\"Serval Rhizome\"$CR\$" assertJq http.output 'contains({"http_status_code": 401})' assertJq http.output 'contains({"http_status_message": ""})' } teardown_AuthBasicMissing() { tfw_cat http.headers http.output teardown } doc_AuthBasicWrong="HTTP RESTful incorrect Basic Authentication credentials" test_AuthBasicWrong() { executeOk curl \ --silent --show-error --write-out '%{http_code}' \ --output http.output \ --dump-header http.headers \ --basic --user fred:nurks \ "http://$addr_localhost:$PORTA/restful/rhizome/bundlelist.json" assertStdoutIs '401' assertGrep http.headers "^WWW-Authenticate: Basic realm=\"Serval Rhizome\"$CR\$" assertJq http.output 'contains({"http_status_code": 401})' assertJq http.output 'contains({"http_status_message": ""})' executeOk curl \ --silent --fail --show-error --write-out '%{http_code}' \ --output http.output \ --dump-header http.headers \ --basic --user ron:weasley \ "http://$addr_localhost:$PORTA/restful/rhizome/bundlelist.json" assertStdoutIs '200' } teardown_AuthBasicWrong() { tfw_cat http.headers http.output teardown } add_bundles() { local encrypted=false case "$1" in --encrypted) encrypted=true; shift;; esac local n for ((n = $1; n <= $2; ++n)); do create_file file$n $((1000 + $n)) if $encrypted; then echo "crypt=1" >file$n.manifest fi executeOk_servald rhizome add file $SIDA file$n file$n.manifest extract_stdout_manifestid BID[$n] extract_stdout_version VERSION[$n] extract_stdout_filesize SIZE[$n] extract_stdout_filehash HASH[$n] extract_stdout_date DATE[$n] extract_stdout_BK BK[$n] extract_stdout_rowid ROWID[$n] extract_stdout_author AUTHOR[$n] extract_stdout_secret SECRET[$n] extract_stdout_inserttime INSERTTIME[$n] NAME[$n]=file$n if $encrypted; then extract_stdout_crypt CRYPT[$n] assert [ "${CRYPT[$n]}" = 1 ] else CRYPT[$n]= fi executeOk_servald rhizome export file ${HASH[$n]} raw$n if $encrypted; then assert ! cmp file$n raw$n else assert cmp file$n raw$n fi [ "${ROWID[$n]}" -gt "${ROWID_MAX:-0}" ] && ROWID_MAX=${ROWID[$n]} done } transform_list_json() { # The following jq(1) incantation transforms a JSON array in from the # following form (which is optimised for transmission size): # { # "header":[ "label1", "label2", "label3", ... ], # "rows":[ # [ row1value1, row1value2, row1value3, ... ], # [ row2value1, row2value2, row2value3, ... ], # ... # [ rowNvalue1, rowNvalue2, rowNvalue3, ... ] # ] # } # # into an array of JSON objects: # [ # { # "label1": row1value1, # "label2": row1value2, # "label3": row1value3, # ... # }, # { # "label1": row2value1, # "label2": row2value2, # "label3": row2value3, # ... # }, # ... # { # "label1": rowNvalue1, # "label2": rowNvalue2, # "label3": rowNvalue3, # ... # } # ] # which is much easier to test with jq(1) expressions. jq ' [ .header as $header | .rows as $rows | $rows | keys | .[] as $index | [ $rows[$index] as $d | $d | keys | .[] as $i | {key:$header[$i], value:$d[$i]} ] | from_entries | .["__index"] = $index ] ' "$1" >"$2" } doc_RhizomeList="HTTP RESTful list Rhizome bundles as JSON" setup_RhizomeList() { setup NBUNDLES=100 add_bundles 0 $((NBUNDLES-1)) assert [ "$ROWID_MAX" -ge "$NBUNDLES" ] } test_RhizomeList() { executeOk curl \ --silent --fail --show-error \ --output bundlelist.json \ --dump-header http.headers \ --basic --user harry:potter \ "http://$addr_localhost:$PORTA/restful/rhizome/bundlelist.json" tfw_cat http.headers bundlelist.json tfw_preserve bundlelist.json assert [ "$(jq '.rows | length' bundlelist.json)" = $NBUNDLES ] transform_list_json bundlelist.json array_of_objects.json tfw_preserve array_of_objects.json for ((n = 0; n != NBUNDLES; ++n)); do if [ "${ROWID[$n]}" -eq "$ROWID_MAX" ]; then # The first row must contain a non-null token string. token=',".token":"","__index":0,' else token=',".token":null,' fi assertJq array_of_objects.json \ "contains([ { name:\"file$n\", service:\"file\", id:\"${BID[$n]}\", version:${VERSION[$n]}, filesize:${SIZE[$n]}, filehash:\"${HASH[$n]}\", date:${DATE[$n]}, _id:${ROWID[$n]}, \".fromhere\":1, \".author\":\"$SIDA\" $token } ])" done } doc_RhizomeNewSince="HTTP RESTful list Rhizome bundles since token as JSON" setup_RhizomeNewSince() { set_extra_config() { executeOk_servald config set rhizome.api.restful.newsince_timeout 60s \ set rhizome.api.restful.newsince_poll_ms 500 } setup add_bundles 0 5 executeOk curl \ --silent --fail --show-error \ --output bundlelist.json \ --dump-header http.headers \ --basic --user harry:potter \ "http://$addr_localhost:$PORTA/restful/rhizome/bundlelist.json" assert [ "$(jq '.rows | length' bundlelist.json)" = 6 ] transform_list_json bundlelist.json array_of_objects.json token=$(jq --raw-output '.[0][".token"]' array_of_objects.json) assert [ -n "$token" ] } test_RhizomeNewSince() { for i in 1 2 3; do fork %curl$i curl \ --silent --fail --show-error \ --no-buffer \ --output newsince$i.json \ --basic --user harry:potter \ "http://$addr_localhost:$PORTA/restful/rhizome/newsince/$token/bundlelist.json" done wait_until [ -e newsince1.json -a -e newsince2.json -a -e newsince3.json ] add_bundles 6 10 for i in 1 2 3; do wait_until --timeout=10 grep "${BID[10]}" newsince$i.json done fork_terminate_all fork_wait_all for i in 1 2 3; do if [ $(jq . newsince$i | wc -c) -eq 0 ]; then echo ']}' >>newsince$i.json assert [ $(jq . newsince$i.json | wc -c) -ne 0 ] fi transform_list_json newsince$i.json objects$i.json tfw_preserve newsince$i.json objects$i.json for ((n = 0; n <= 5; ++n)); do assertJq objects$i.json "contains([{id:\"${BID[$n]}\"}]) | not" done for ((n = 6; n <= 10; ++n)); do assertJq objects$i.json \ "contains([ { name:\"file$n\", service:\"file\", id:\"${BID[$n]}\", version:${VERSION[$n]}, filesize:${SIZE[$n]}, filehash:\"${HASH[$n]}\", date:${DATE[$n]}, _id:${ROWID[$n]}, \".fromhere\":1, \".author\":\"$SIDA\", \".token\":\"\" } ])" done done } assert_http_response_headers() { local n=$1 assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-Id: ${BID[$n]}$CR\$" assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-Version: ${VERSION[$n]}$CR\$" assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-Filesize: ${SIZE[$n]}$CR\$" assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-Filehash: ${HASH[$n]}$CR\$" assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-BK: ${BK[$n]}$CR\$" assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-Date: ${DATE[$n]}$CR\$" assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-Name: \"${NAME[$n]}\"$CR\$" assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-Service: file$CR\$" assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-Author: ${AUTHOR[$n]}$CR\$" assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-Secret: ${SECRET[$n]}$CR\$" assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-Inserttime: ${INSERTTIME[$n]}$CR\$" assertGrep --matches=1 http.headers$n "^Serval-Rhizome-Bundle-Rowid: ${ROWID[$n]}$CR\$" } doc_RhizomeManifest="HTTP RESTful fetch Rhizome manifest" setup_RhizomeManifest() { setup add_bundles 0 2 } test_RhizomeManifest() { for n in 0 1 2; do executeOk curl \ --silent --fail --show-error \ --output bundle$n.rhm \ --dump-header http.headers$n \ --basic --user harry:potter \ "http://$addr_localhost:$PORTA/restful/rhizome/${BID[$n]}.rhm" tfw_cat http.headers$n bundle$n.rhm tfw_preserve bundle$n.rhm done for n in 0 1 2; do assert diff file$n.manifest bundle$n.rhm assert_http_response_headers $n done } doc_RhizomePayloadRaw="HTTP RESTful fetch Rhizome raw payload" setup_RhizomePayloadRaw() { setup add_bundles 0 1 add_bundles --encrypted 2 3 } test_RhizomePayloadRaw() { for n in 0 1 2 3; do executeOk curl \ --silent --fail --show-error \ --output raw.bin$n \ --dump-header http.headers$n \ --basic --user harry:potter \ "http://$addr_localhost:$PORTA/restful/rhizome/${BID[$n]}/raw.bin" tfw_cat http.headers$n raw.bin$n done for n in 0 1 2 3; do assert cmp raw$n raw.bin$n assert_http_response_headers $n done } doc_RhizomePayloadDecrypted="HTTP RESTful fetch Rhizome decrypted payload" setup_RhizomePayloadDecrypted() { setup add_bundles 0 1 add_bundles --encrypted 2 3 } test_RhizomePayloadDecrypted() { for n in 0 1 2 3; do executeOk curl \ --silent --fail --show-error \ --output decrypted.bin$n \ --dump-header http.headers$n \ --basic --user harry:potter \ "http://$addr_localhost:$PORTA/restful/rhizome/${BID[$n]}/decrypted.bin" tfw_cat http.headers$n decrypted.bin$n done for n in 0 1 2 3; do assert cmp file$n decrypted.bin$n assert_http_response_headers $n done } extract_http_header() { local __var="$1" local __headerfile="$2" local __header="$3" local __rexp="$4" local __value=$($SED -n -e "/^$__header:[ $VT]*$__rexp$CR\$/s/^$__header:[ $VT]*\(.*\)$CR\$/\1/p" "$__headerfile") assert --message="$__headerfile contains valid '$__header' header" \ --dump-on-fail="$__headerfile" \ [ -n "$__value" ] [ -n "$__var" ] && eval $__var=\"\$__value\" } http_unquote_string() { local __var="$1" local __unq="$(eval echo '"${'$__var'}"' | sed -e 's/^"//' -e 's/"$//' -e 's/\\\(.\)/\1/g')" eval $__var=\"\$__unq\" } doc_RhizomeInsert="HTTP RESTful insert new Rhizome bundles" setup_RhizomeInsert() { IDENTITY_COUNT=3 SIDA4= setup for n in 1 2 3 4; do create_file file$n $((1000 + $n)) create_file nfile$n $((1100 + $n)) payload_filename[$n]= eval author[$n]=\$SIDA$n service[$n]=file done name[1]=blarg echo "name=blarg" >manifest1 name[2]=file2 echo "crypt=1" >manifest2 name[3]=kibble payload_filename[3]=kibble >manifest3 name[4]= service[4]=wah echo -e "service=wah\ncrypt=0" >manifest4 } test_RhizomeInsert() { for n in 1 2 3 4; do authorargs=() [ -n "${author[$n]}" ] && authorargs=(--form "bundle-author=${author[$n]}") execute curl \ --silent --show-error --write-out '%{http_code}' \ --output file$n.manifest \ --dump-header http.header$n \ --basic --user harry:potter \ "${authorargs[@]}" \ --form "manifest=@manifest$n;type=rhizome-manifest/text" \ --form "payload=@file$n${payload_filename[$n]:+;filename=\"${payload_filename[$n]}\"}" \ "http://$addr_localhost:$PORTA/restful/rhizome/insert" tfw_cat http.header$n file$n.manifest assertExitStatus == 0 assertStdoutIs 201 extract_http_header BID[$n] http.header$n Serval-Rhizome-Bundle-Id "$rexp_manifestid" extract_http_header VERSION[$n] http.header$n Serval-Rhizome-Bundle-Version "$rexp_version" extract_http_header SIZE[$n] http.header$n Serval-Rhizome-Bundle-Filesize "$rexp_filesize" extract_http_header HASH[$n] http.header$n Serval-Rhizome-Bundle-Filehash "$rexp_filehash" extract_http_header DATE[$n] http.header$n Serval-Rhizome-Bundle-Date "$rexp_date" extract_http_header ROWID[$n] http.header$n Serval-Rhizome-Bundle-Rowid "$rexp_rowid" extract_http_header INSERTTIME[$n] http.header$n Serval-Rhizome-Bundle-Inserttime "$rexp_date" extract_http_header SECRET[$n] http.header$n Serval-Rhizome-Bundle-Secret "$rexp_bundlesecret" extract_http_header SERVICE[$n] http.header$n Serval-Rhizome-Bundle-Service ".*" assert [ ${SIZE[$n]} -eq $((1000 + $n)) ] assert [ "${SERVICE[$n]}" = "${service[$n]}" ] extract_manifest_id BID file$n.manifest extract_manifest_version VERSION file$n.manifest extract_manifest_filesize SIZE file$n.manifest extract_manifest_filehash HASH file$n.manifest extract_manifest_date DATE file$n.manifest extract_manifest_service SERVICE file$n.manifest assert [ "$BID" = "${BID[$n]}" ] assert [ "$VERSION" = "${VERSION[$n]}" ] assert [ "$SIZE" = "${SIZE[$n]}" ] assert [ "$HASH" = "${HASH[$n]}" ] assert [ "$DATE" = "${DATE[$n]}" ] assert [ "$SERVICE" = "${SERVICE[$n]}" ] if [ -n "${name[$n]}" ]; then extract_http_header NAME[$n] http.header$n Serval-Rhizome-Bundle-Name ".*" http_unquote_string NAME[$n] assert [ "${NAME[$n]}" = "${name[$n]}" ] extract_manifest_name NAME file$n.manifest assert [ "$NAME" = "${NAME[$n]}" ] fi if [ -n "${author[$n]}" ]; then extract_http_header AUTHOR[$n] http.header$n Serval-Rhizome-Bundle-Author "$rexp_sid" assert [ "${AUTHOR[$n]}" = "${author[$n]}" ] extract_http_header BK[$n] http.header$n Serval-Rhizome-Bundle-BK "$rexp_bundlekey" extract_manifest_BK BK file$n.manifest assert [ "$BK" = "${BK[$n]}" ] fi done executeOk_servald rhizome list assert_rhizome_list \ --fromhere=1 \ --author=${author[1]} file1 \ --author=${author[2]} file2 \ --author=${author[3]} file3 \ --fromhere=0 \ --author=${author[4]} file4 for n in 1 2 3 4; do executeOk_servald rhizome extract bundle ${BID[$n]} xfile$n.manifest xfile$n assert diff xfile$n.manifest file$n.manifest assert diff file$n xfile$n done for n in 1 2 3 4; do $SED -e '/^version=/d;/^date=/d;/^filehash=/d;/^filesize=/d' xfile$n.manifest >nmanifest$n execute curl \ --silent --show-error --write-out '%{http_code}' \ --output nfile$n.manifest \ --dump-header http.header$n \ --basic --user harry:potter \ --form "manifest=@nmanifest$n;type=rhizome-manifest/text" \ --form "payload=@nfile$n;filename=\"nfile$n\"" \ "http://$addr_localhost:$PORTA/restful/rhizome/insert" tfw_cat http.header$n nfile$n.manifest assertExitStatus == 0 if [ -n "${author[$n]}" ]; then assertStdoutIs 201 else assertStdoutIs 403 assertJq nfile$n.manifest 'contains({"http_status_code": 403})' assertJqGrep --ignore-case nfile$n.manifest '.http_status_message' "missing bundle secret" fi done } doc_RhizomeInsertAnon="HTTP RESTful update anonymous Rhizome bundle" setup_RhizomeInsertAnon() { setup create_file file1 1001 executeOk_servald rhizome add file '' file1 file1.manifest extract_stdout_secret SECRET assert_manifest_fields file1.manifest !BK $SED -e '/^version=/d;/^date=/d;/^filehash=/d;/^filesize=/d' file1.manifest >file2.manifest assert_manifest_fields file2.manifest !version !date !filehash !filesize create_file file2 1002 } test_RhizomeInsertAnon() { execute curl \ --silent --show-error --write-out '%{http_code}' \ --output ifile2.manifest \ --dump-header http.header \ --basic --user harry:potter \ --form "bundle-secret=$SECRET" \ --form "manifest=@file2.manifest;type=rhizome-manifest/text" \ --form "payload=@file2" \ "http://$addr_localhost:$PORTA/restful/rhizome/insert" tfw_cat http.header ifile2.manifest assertExitStatus == 0 assertStdoutIs 201 executeOk_servald rhizome list assert_rhizome_list --fromhere=0 --manifest=ifile2.manifest file2 } doc_RhizomeInsertEmpty="HTTP RESTful insert empty Rhizome bundle" setup_RhizomeInsertEmpty() { setup >empty assert [ ! -s empty ] } test_RhizomeInsertEmpty() { execute curl \ --silent --show-error --write-out '%{http_code}' \ --output empty.manifest \ --dump-header http.header \ --basic --user harry:potter \ --form "manifest=;type=rhizome-manifest/text" \ --form "payload=@empty;filename=\"lucky\"" \ "http://$addr_localhost:$PORTA/restful/rhizome/insert" tfw_cat http.header empty.manifest assertExitStatus == 0 assertStdoutIs 201 extract_manifest_id BID empty.manifest executeOk_servald rhizome list assert_rhizome_list empty executeOk_servald rhizome extract bundle $BID xempty.manifest xempty assert [ ! -e xempty ] assert diff xempty.manifest empty.manifest } doc_RhizomeInsertLarge="HTTP RESTful insert 50 MiB Rhizome bundle" setup_RhizomeInsertLarge() { setup create_file file1 50m } test_RhizomeInsertLarge() { execute --timeout=120 curl \ --silent --show-error --write-out '%{http_code}' \ --output file1.manifest \ --dump-header http.header \ --basic --user harry:potter \ --form "manifest=;type=rhizome-manifest/text" \ --form "payload=@file1" \ "http://$addr_localhost:$PORTA/restful/rhizome/insert" tfw_cat http.header file1.manifest assertExitStatus == 0 assertStdoutIs 201 extract_manifest_id BID file1.manifest executeOk_servald rhizome list assert_rhizome_list file1 executeOk_servald rhizome extract bundle $BID xfile1.manifest xfile1 assert diff xfile1.manifest file1.manifest assert cmp file1 xfile1 } doc_RhizomeInsertMissingManifest="HTTP RESTful insert missing 'manifest' form part" setup_RhizomeInsertMissingManifest() { setup echo 'File one' >file1 } test_RhizomeInsertMissingManifest() { execute curl \ --silent --show-error --write-out '%{http_code}' \ --output http.body \ --dump-header http.header \ --basic --user harry:potter \ --form "payload=@file1" \ "http://$addr_localhost:$PORTA/restful/rhizome/insert" tfw_cat http.header http.body assertExitStatus == 0 assertStdoutIs 403 assertJq http.body 'contains({"http_status_code": 403})' assertJqGrep --ignore-case http.body '.http_status_message' 'missing.*manifest.*form.*part' executeOk_servald rhizome list assert_rhizome_list } doc_RhizomeInsertIncorrectManifestType="HTTP RESTful insert incorrect 'manifest' content type" setup_RhizomeInsertIncorrectManifestType() { setup echo 'File one' >file1 } test_RhizomeInsertIncorrectManifestType() { execute curl \ --silent --show-error --write-out '%{http_code}' \ --output http.body \ --dump-header http.header \ --basic --user harry:potter \ --form "manifest=;type=rhizome-manifest/something" \ --form "payload=@file1" \ "http://$addr_localhost:$PORTA/restful/rhizome/insert" tfw_cat http.header http.body assertExitStatus == 0 assertStdoutIs 403 assertJq http.body 'contains({"http_status_code": 403})' assertJqGrep --ignore-case http.body '.http_status_message' 'unsupported content-type.*manifest.*form.*part' executeOk_servald rhizome list assert_rhizome_list } doc_RhizomeInsertDuplicateManifest="HTTP RESTful insert duplicate 'manifest' form part" setup_RhizomeInsertDuplicateManifest() { setup echo 'File one' >file1 echo 'name=wah' >file1.manifest echo 'name=bee' >file2.manifest } test_RhizomeInsertDuplicateManifest() { execute curl \ --silent --show-error --write-out '%{http_code}' \ --output http.body \ --dump-header http.header \ --basic --user harry:potter \ --form "manifest=@file1.manifest;type=rhizome-manifest/text" \ --form "manifest=@file2.manifest;type=rhizome-manifest/text" \ --form "payload=@file1" \ "http://$addr_localhost:$PORTA/restful/rhizome/insert" tfw_cat http.header http.body assertExitStatus == 0 assertStdoutIs 403 assertJq http.body 'contains({"http_status_code": 403})' assertJqGrep --ignore-case http.body '.http_status_message' 'duplicate.*manifest.*form.*part' executeOk_servald rhizome list assert_rhizome_list } doc_RhizomeInsertJournal="HTTP RESTful insert does not support journals" setup_RhizomeInsertJournal() { setup echo 'File one' >file1 echo 'tail=0' >file1.manifest } test_RhizomeInsertJournal() { execute curl \ --silent --show-error --write-out '%{http_code}' \ --output http.body \ --dump-header http.header \ --basic --user harry:potter \ --form "manifest=@file1.manifest;type=rhizome-manifest/text" \ --form "payload=@file1" \ "http://$addr_localhost:$PORTA/restful/rhizome/insert" tfw_cat http.header http.body assertExitStatus == 0 assertStdoutIs 403 assertJq http.body 'contains({"http_status_code": 403})' assertJqGrep --ignore-case http.body '.http_status_message' 'not supported.*journal' executeOk_servald rhizome list assert_rhizome_list } doc_RhizomeInsertMissingPayload="HTTP RESTful insert missing 'payload' form part" setup_RhizomeInsertMissingPayload() { setup echo 'File one' >file1 echo 'name=wah' >file1.manifest } test_RhizomeInsertMissingPayload() { execute curl \ --silent --show-error --write-out '%{http_code}' \ --output http.body \ --dump-header http.header \ --basic --user harry:potter \ --form "manifest=@file1.manifest;type=rhizome-manifest/text" \ "http://$addr_localhost:$PORTA/restful/rhizome/insert" tfw_cat http.header http.body assertExitStatus == 0 assertStdoutIs 403 assertJq http.body 'contains({"http_status_code": 403})' assertJqGrep --ignore-case http.body '.http_status_message' 'missing.*payload.*form.*part' executeOk_servald rhizome list assert_rhizome_list } doc_RhizomeInsertDuplicatePayload="HTTP RESTful insert duplicate 'payload' form part" setup_RhizomeInsertDuplicatePayload() { setup echo 'File one' >file1 echo 'File two' >file2 echo 'name=wah' >file1.manifest } test_RhizomeInsertDuplicatePayload() { execute curl \ --silent --show-error --write-out '%{http_code}' \ --output http.body \ --dump-header http.header \ --basic --user harry:potter \ --form "manifest=@file1.manifest;type=rhizome-manifest/text" \ --form "payload=@file1" \ --form "payload=@file2" \ "http://$addr_localhost:$PORTA/restful/rhizome/insert" tfw_cat http.header http.body assertExitStatus == 0 assertStdoutIs 403 assertJq http.body 'contains({"http_status_code": 403})' assertJqGrep --ignore-case http.body '.http_status_message' 'duplicate.*payload.*form.*part' executeOk_servald rhizome list assert_rhizome_list } doc_RhizomeInsertPartOrder="HTTP RESTful insert 'payload' form part before 'manifest'" setup_RhizomeInsertPartOrder() { setup echo 'File one' >file1 echo 'name=wah' >file1.manifest } test_RhizomeInsertPartOrder() { execute curl \ --silent --show-error --write-out '%{http_code}' \ --output http.body \ --dump-header http.header \ --basic --user harry:potter \ --form "payload=@file1" \ --form "manifest=@file1.manifest;type=rhizome-manifest/text" \ "http://$addr_localhost:$PORTA/restful/rhizome/insert" tfw_cat http.header http.body assertExitStatus == 0 assertStdoutIs 403 assertJq http.body 'contains({"http_status_code": 403})' assertJqGrep --ignore-case http.body '.http_status_message' 'missing.*manifest.*form.*part' executeOk_servald rhizome list assert_rhizome_list } doc_RhizomeInsertPartUnsupported="HTTP RESTful insert unsupported form part" setup_RhizomeInsertPartUnsupported() { setup echo 'File one' >file1 echo 'name=wah' >file1.manifest } test_RhizomeInsertPartUnsupported() { execute curl \ --silent --show-error --write-out '%{http_code}' \ --output http.body \ --dump-header http.header \ --basic --user harry:potter \ --form "manifest=@file1.manifest;type=rhizome-manifest/text" \ --form "payload=@file1" \ --form "happyhappy=joyjoy" \ "http://$addr_localhost:$PORTA/restful/rhizome/insert" tfw_cat http.header http.body assertExitStatus == 0 assertStdoutIs 403 assertJq http.body 'contains({"http_status_code": 403})' assertJqGrep --ignore-case http.body '.http_status_message' 'unsupported.*form.*part' assertJqGrep http.body '.http_status_message' 'happyhappy' executeOk_servald rhizome list assert_rhizome_list } doc_RhizomeInsertIncorrectFilesize="HTTP RESTful insert with incorrect filesize" setup_RhizomeInsertIncorrectFilesize() { setup echo 'File one' >file1 echo 'filesize=6' >file1.manifest echo 'File two' >file2 echo 'filesize=100' >file2.manifest } test_RhizomeInsertIncorrectFilesize() { execute curl \ --silent --show-error --write-out '%{http_code}' \ --output http.body \ --dump-header http.header \ --basic --user harry:potter \ --form "manifest=@file1.manifest;type=rhizome-manifest/text" \ --form "payload=@file1" \ "http://$addr_localhost:$PORTA/restful/rhizome/insert" tfw_cat http.header http.body assertExitStatus == 0 assertStdoutIs 403 assertJq http.body 'contains({"http_status_code": 403})' assertJqGrep --ignore-case http.body '.http_status_message' 'payload size.*contradicts manifest' execute curl \ --silent --show-error --write-out '%{http_code}' \ --output http.body \ --dump-header http.header \ --basic --user harry:potter \ --form "manifest=@file2.manifest;type=rhizome-manifest/text" \ --form "payload=@file2" \ "http://$addr_localhost:$PORTA/restful/rhizome/insert" tfw_cat http.header http.body assertExitStatus == 0 assertStdoutIs 403 assertGrep --ignore-case http.body 'payload size.*contradicts manifest' executeOk_servald rhizome list assert_rhizome_list } doc_RhizomeInsertIncorrectFilehash="HTTP RESTful insert with incorrect filehash" setup_RhizomeInsertIncorrectFilehash() { setup echo 'File one' >file1 echo 'filehash=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' >file1.manifest } test_RhizomeInsertIncorrectFilehash() { execute curl \ --silent --show-error --write-out '%{http_code}' \ --output http.body \ --dump-header http.header \ --basic --user harry:potter \ --form "manifest=@file1.manifest;type=rhizome-manifest/text" \ --form "payload=@file1" \ "http://$addr_localhost:$PORTA/restful/rhizome/insert" tfw_cat http.header http.body assertExitStatus == 0 assertStdoutIs 403 assertJq http.body 'contains({"http_status_code": 403})' assertJqGrep --ignore-case http.body '.http_status_message' 'payload hash.*contradicts manifest' executeOk_servald rhizome list assert_rhizome_list } doc_MeshmsListConversations="HTTP RESTful list MeshMS conversations as JSON" setup_MeshmsListConversations() { IDENTITY_COUNT=5 setup # create 3 threads, with all permutations of incoming and outgoing messages executeOk_servald meshms send message $SIDA1 $SIDA2 "Message1" executeOk_servald meshms send message $SIDA3 $SIDA1 "Message2" executeOk_servald meshms send message $SIDA1 $SIDA4 "Message3" executeOk_servald meshms send message $SIDA4 $SIDA1 "Message4" } test_MeshmsListConversations() { executeOk curl \ --silent --fail --show-error \ --output conversationlist1.json \ --dump-header http.headers \ --basic --user harry:potter \ "http://$addr_localhost:$PORTA/restful/meshms/$SIDA1/conversationlist.json" tfw_cat http.headers conversationlist1.json tfw_preserve conversationlist1.json assert [ "$(jq '.rows | length' conversationlist1.json)" = 3 ] transform_list_json conversationlist1.json conversations1.json tfw_preserve conversations1.json assertJq conversations1.json \ "contains([ { my_sid: \"$SIDA1\", their_sid: \"$SIDA2\", read: true, last_message: 0, read_offset: 0 } ])" assertJq conversations1.json \ "contains([ { my_sid: \"$SIDA1\", their_sid: \"$SIDA3\", read: false, last_message: 11, read_offset: 0 } ])" assertJq conversations1.json \ "contains([ { my_sid: \"$SIDA1\", their_sid: \"$SIDA4\", read: false, last_message: 14, read_offset: 0 } ])" # mark all incoming messages as read executeOk_servald meshms read messages $SIDA1 tfw_cat --stderr executeOk curl \ --silent --fail --show-error \ --output conversationlist2.json \ --dump-header http.headers \ --basic --user harry:potter \ "http://$addr_localhost:$PORTA/restful/meshms/$SIDA1/conversationlist.json" tfw_cat http.headers conversationlist2.json tfw_preserve conversationlist2.json assert [ "$(jq '.rows | length' conversationlist2.json)" = 3 ] transform_list_json conversationlist2.json conversations2.json tfw_preserve conversations2.json assertJq conversations2.json \ "contains([ { my_sid: \"$SIDA1\", their_sid: \"$SIDA2\", read: true, last_message: 0, read_offset: 0 } ])" assertJq conversations2.json \ "contains([ { my_sid: \"$SIDA1\", their_sid: \"$SIDA3\", read: true, last_message: 11, read_offset: 11 } ])" assertJq conversations2.json \ "contains([ { my_sid: \"$SIDA1\", their_sid: \"$SIDA4\", read: true, last_message: 14, read_offset: 14 } ])" } doc_MeshmsListMessages="HTTP RESTful list MeshMS messages in one conversation as JSON" setup_MeshmsListMessages() { IDENTITY_COUNT=2 setup meshms_add_messages $SIDA1 $SIDA2 '><>>A>A<>><><><>>>A>A><<<><>>A<<>' let NROWS=NSENT+NRECV+(NACK?1:0) executeOk_servald meshms list messages $SIDA1 $SIDA2 delivered_offset=$(sed -n -e '/^[0-9]\+:[0-9]\+:ACK:delivered$/{n;s/^[0-9]\+:\([0-9]\+\):>:.*/\1/p;q}' "$TFWSTDOUT") [ -z "$delivered_offset" ] && delivered_offset=0 read_offset=$(sed -n -e 's/^[0-9]\+:\([0-9]\+\):MARK:read$/\1/p' "$TFWSTDOUT") [ -z "$read_offset" ] && read_offset=0 } test_MeshmsListMessages() { executeOk curl \ --silent --fail --show-error \ --output messagelist.json \ --dump-header http.headers \ --basic --user harry:potter \ "http://$addr_localhost:$PORTA/restful/meshms/$SIDA1/$SIDA2/messagelist.json" tfw_cat http.headers messagelist.json tfw_preserve messagelist.json assert [ "$(jq '.rows | length' messagelist.json)" = $NROWS ] transform_list_json messagelist.json messages.json tfw_preserve messages.json seen_ack=false let i=0 for ((j = NMESSAGE-1; j >= 0; --j)); do case ${MESSAGE[$j]} in 'ACK') $seen_ack && continue esac assertJq messages.json '(.['$i'].token | length) > 0' assertJq messages.json '.['$i'].my_sid == "'$SIDA1'"' assertJq messages.json '.['$i'].their_sid == "'$SIDA2'"' case ${MESSAGE[$j]} in '>') assertJq messages.json '.['$i'].type == ">"' assertJqIs messages.json '.['$i'].text' "${TEXT[$j]}" assertJq messages.json '.['$i'].delivered == (.['$i'].offset <= '$delivered_offset')' let ++i ;; '<') assertJq messages.json '.['$i'].type == "<"' assertJqIs messages.json '.['$i'].text' "${TEXT[$j]}" assertJq messages.json '.['$i'].read == (.['$i'].offset <= '$read_offset')' let ++i ;; 'ACK') assertJq messages.json '.['$i'].type == "ACK"' assertJq messages.json '.['$i'].text == null' assertJq messages.json '.['$i'].ack_offset == '$delivered_offset let ++i seen_ack=true ;; esac done } doc_MeshmsListMessagesNoIdentity="HTTP RESTful list MeshMS messages from unknown identity" setup_MeshmsListMessagesNoIdentity() { setup SIDX=0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF } test_MeshmsListMessagesNoIdentity() { execute curl \ --silent --show-error --write-out '%{http_code}' \ --output http.body \ --dump-header http.header \ --basic --user harry:potter \ "http://$addr_localhost:$PORTA/restful/meshms/$SIDX/$SIDA/messagelist.json" tfw_cat http.header http.body assertExitStatus == 0 assertStdoutIs 403 assertJq http.body 'contains({"http_status_code": 403})' assertJq http.body 'contains({"meshms_status_code": 2})' assertJqGrep --ignore-case http.body '.http_status_message' 'identity.*unknown' } doc_MeshmsListMessagesNewSince="HTTP RESTful list MeshMS messages in one conversation since token as JSON" setup_MeshmsListMessagesNewSince() { IDENTITY_COUNT=2 set_extra_config() { executeOk_servald config set rhizome.api.restful.newsince_timeout 1s \ set rhizome.api.restful.newsince_poll_ms 500 } setup meshms_add_messages $SIDA1 $SIDA2 '><>>A>A<>><><><>>>A>A><<<><>>A<<>' let NROWS=NSENT+NRECV+(NACK?1:0) executeOk curl \ --silent --fail --show-error \ --output messagelist.json \ --dump-header http.headers \ --basic --user harry:potter \ "http://$addr_localhost:$PORTA/restful/meshms/$SIDA1/$SIDA2/messagelist.json" assert [ "$(jq '.rows | length' messagelist.json)" = $NROWS ] transform_list_json messagelist.json messages.json tfw_preserve messages.json for ((i = 0; i < NROWS; i += 3)); do token[$i]=$(jq --raw-output '.['$i'].token' messages.json) done } test_MeshmsListMessagesNewSince() { for ((i = 0; i < NROWS; i += 3)); do # At most five requests going at once [ $i -ge 15 ] && fork_wait %curl$((i-15)) fork %curl$i executeOk curl \ --silent --fail --show-error \ --output messagelist$i.json \ --dump-header http.headers$i \ --basic --user harry:potter \ "http://$addr_localhost:$PORTA/restful/meshms/$SIDA1/$SIDA2/newsince/${token[$i]}/messagelist.json" done fork_wait_all for ((i = 0; i < NROWS; i += 3)); do transform_list_json messagelist$i.json messages$i.json tfw_preserve messages$i.json { echo '{"a":'; cat messages.json; echo ',"b":'; cat messages$i.json; echo '}'; } >tmp.json assertJq tmp.json '.a[:'$i'] == .b' done } grepall() { local pattern="$1" shift for file; do grep "$pattern" "$file" || return $? done return 0 } doc_MeshmsListMessagesNewSinceArrival="HTTP RESTful list newly arriving MeshMS messages in one conversation as JSON" setup_MeshmsListMessagesNewSinceArrival() { IDENTITY_COUNT=2 set_extra_config() { executeOk_servald config set rhizome.api.restful.newsince_timeout 60s \ set rhizome.api.restful.newsince_poll_ms 500 } setup meshms_add_messages $SIDA1 $SIDA2 '><>A>' let NROWS=NSENT+NRECV+(NACK?1:0) executeOk curl \ --silent --fail --show-error \ --output messagelist.json \ --dump-header http.headers \ --basic --user harry:potter \ "http://$addr_localhost:$PORTA/restful/meshms/$SIDA1/$SIDA2/messagelist.json" assert [ "$(jq '.rows | length' messagelist.json)" = $NROWS ] transform_list_json messagelist.json messages.json tfw_preserve messages.json token=$(jq --raw-output '.[0].token' messages.json) assert [ -n "$token" ] } test_MeshmsListMessagesNewSinceArrival() { for i in 1 2 3; do fork %curl$i executeOk curl \ --silent --fail --show-error \ --no-buffer \ --output newsince$i.json \ --basic --user harry:potter \ "http://$addr_localhost:$PORTA/restful/meshms/$SIDA1/$SIDA2/newsince/$token/messagelist.json" done wait_until [ -e newsince1.json -a -e newsince2.json -a -e newsince3.json ] for message in '>Rumplestiltskin' 'A' 'Eulenspiegel'; do meshms_add_messages $SIDA1 $SIDA2 "${message:0:1}" "${message:1}" wait_until --timeout=60 grepall "${message:1}" newsince{1,2,3}.json done fork_terminate_all fork_wait_all } teardown_MeshmsListMessagesNewSinceArrival() { tfw_preserve newsince{1,2,3}.json teardown } doc_MeshmsSend="HTTP RESTful send MeshMS message" setup_MeshmsSend() { IDENTITY_COUNT=2 setup } test_MeshmsSend() { executeOk curl \ --silent --fail --show-error \ --output sendmessage.json \ --basic --user harry:potter \ --form "message=Hello World;type=text/plain;charset=utf-8" \ "http://$addr_localhost:$PORTA/restful/meshms/$SIDA1/$SIDA2/sendmessage" executeOk_servald meshms list messages $SIDA1 $SIDA2 assertStdoutGrep --matches=1 ':>:Hello World' executeOk curl \ --silent --fail --show-error \ --output sendmessage.json \ --basic --user ron:weasley \ --form "message=Hello back!;type=text/plain;charset=utf-8" \ "http://$addr_localhost:$PORTA/restful/meshms/$SIDA2/$SIDA1/sendmessage" executeOk_servald meshms list messages $SIDA1 $SIDA2 assertStdoutGrep --matches=1 ':>:Hello World$' assertStdoutGrep --matches=1 ':<:Hello back!$' } doc_MeshmsSendMissingMessage="HTTP RESTful MeshMS send missing 'message' form part" setup_MeshmsSendMissingMessage() { IDENTITY_COUNT=2 setup } test_MeshmsSendMissingMessage() { execute curl \ --silent --show-error --write-out '%{http_code}' \ --output http.body \ --dump-header http.header \ --basic --user harry:potter \ --data '' \ "http://$addr_localhost:$PORTA/restful/meshms/$SIDA2/$SIDA1/sendmessage" tfw_cat http.header http.body assertExitStatus == 0 assertStdoutIs 403 assertJq http.body 'contains({"http_status_code": 403})' assertJqGrep --ignore-case http.body '.http_status_message' 'missing.*message.*form.*part' executeOk_servald meshms list messages $SIDA1 $SIDA2 assertStdoutLineCount '==' 2 } doc_MeshmsSendDuplicateMessage="HTTP RESTful MeshMS send duplicate 'message' form parts" setup_MeshmsSendDuplicateMessage() { IDENTITY_COUNT=2 setup } test_MeshmsSendDuplicateMessage() { execute curl \ --silent --show-error --write-out '%{http_code}' \ --output http.body \ --dump-header http.header \ --basic --user harry:potter \ --form "message=Hello one;type=text/plain;charset=utf-8" \ --form "message=Hello two;type=text/plain;charset=utf-8" \ "http://$addr_localhost:$PORTA/restful/meshms/$SIDA2/$SIDA1/sendmessage" tfw_cat http.header http.body assertExitStatus == 0 assertStdoutIs 403 assertJq http.body 'contains({"http_status_code": 403})' assertJqGrep --ignore-case http.body '.http_status_message' 'duplicate.*message.*form.*part' executeOk_servald meshms list messages $SIDA1 $SIDA2 assertStdoutLineCount '==' 2 } doc_MeshmsSendMessageMissingContentType="HTTP RESTful MeshMS send 'message' form part missing Content-Type" setup_MeshmsSendMessageMissingContentType() { IDENTITY_COUNT=2 setup } test_MeshmsSendMessageMissingContentType() { execute curl \ --silent --show-error --write-out '%{http_code}' \ --output http.body \ --dump-header http.header \ --basic --user harry:potter \ --form "message=Hello there" \ "http://$addr_localhost:$PORTA/restful/meshms/$SIDA2/$SIDA1/sendmessage" tfw_cat http.header http.body assertExitStatus == 0 assertStdoutIs 403 assertJq http.body 'contains({"http_status_code": 403})' assertJqGrep --ignore-case http.body '.http_status_message' 'missing.*content.*type' executeOk_servald meshms list messages $SIDA1 $SIDA2 assertStdoutLineCount '==' 2 } doc_MeshmsSendMessageUnsupportedContentType="HTTP RESTful MeshMS send 'message' form part unsupported Content-Type" setup_MeshmsSendMessageUnsupportedContentType() { IDENTITY_COUNT=2 setup } test_MeshmsSendMessageUnsupportedContentType() { execute curl \ --silent --show-error --write-out '%{http_code}' \ --output http.body \ --dump-header http.header \ --basic --user harry:potter \ --form "message=Hello there;type=text/rich" \ "http://$addr_localhost:$PORTA/restful/meshms/$SIDA2/$SIDA1/sendmessage" tfw_cat http.header http.body assertExitStatus == 0 assertStdoutIs 403 assertJq http.body 'contains({"http_status_code": 403})' assertJqGrep --ignore-case http.body '.http_status_message' 'unsupported.*content.*type' executeOk_servald meshms list messages $SIDA1 $SIDA2 assertStdoutLineCount '==' 2 } doc_MeshmsSendMessageMissingCharset="HTTP RESTful MeshMS send 'message' form part missing charset" setup_MeshmsSendMessageMissingCharset() { IDENTITY_COUNT=2 setup } test_MeshmsSendMessageMissingCharset() { execute curl \ --silent --show-error --write-out '%{http_code}' \ --output http.body \ --dump-header http.header \ --basic --user harry:potter \ --form "message=Hello there;type=text/plain" \ "http://$addr_localhost:$PORTA/restful/meshms/$SIDA2/$SIDA1/sendmessage" tfw_cat http.header http.body assertExitStatus == 0 assertStdoutIs 403 assertJq http.body 'contains({"http_status_code": 403})' assertJqGrep --ignore-case http.body '.http_status_message' 'missing.*charset' executeOk_servald meshms list messages $SIDA1 $SIDA2 assertStdoutLineCount '==' 2 } doc_MeshmsSendMessageUnsupportedCharset="HTTP RESTful MeshMS send 'message' form part unsupported charset" setup_MeshmsSendMessageUnsupportedCharset() { IDENTITY_COUNT=2 setup } test_MeshmsSendMessageUnsupportedCharset() { execute curl \ --silent --show-error --write-out '%{http_code}' \ --output http.body \ --dump-header http.header \ --basic --user harry:potter \ --form "message=Hello there;type=text/plain;charset=latin-1" \ "http://$addr_localhost:$PORTA/restful/meshms/$SIDA2/$SIDA1/sendmessage" tfw_cat http.header http.body assertExitStatus == 0 assertStdoutIs 403 assertJq http.body 'contains({"http_status_code": 403})' assertJqGrep --ignore-case http.body '.http_status_message' 'unsupported.*charset' executeOk_servald meshms list messages $SIDA1 $SIDA2 assertStdoutLineCount '==' 2 } doc_MeshmsSendNoIdentity="HTTP RESTful MeshMS send from unknown identity" setup_MeshmsSendNoIdentity() { setup SIDX=0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF } test_MeshmsSendNoIdentity() { execute curl \ --silent --show-error --write-out '%{http_code}' \ --output http.body \ --dump-header http.header \ --basic --user harry:potter \ --form "message=Hello;type=text/plain;charset=utf-8" \ "http://$addr_localhost:$PORTA/restful/meshms/$SIDX/$SIDA/sendmessage" tfw_cat http.header http.body assertExitStatus == 0 assertStdoutIs 403 assertJq http.body 'contains({"http_status_code": 403})' assertJq http.body 'contains({"meshms_status_code": 2})' assertJqGrep --ignore-case http.body '.http_status_message' 'identity.*unknown' } runTests "$@"