From 0c1d631cca7a7f42de1518f3bb6f4e35e700e848 Mon Sep 17 00:00:00 2001 From: Eddie Kohler Date: Fri, 6 Dec 2024 10:47:47 -0500 Subject: [PATCH] API spec; rename `dryrun` parameter to `dry_run` --- devel/apidoc/submissions.md | 34 +++++++++--- etc/apiexpansions.json | 12 +++-- etc/apifunctions.json | 10 ++-- etc/openapi.json | 103 ++++++++++++++++++++++++++---------- lib/messageset.php | 8 +-- src/api/api_paper.php | 23 ++++---- src/api/api_settings.php | 17 +++--- src/paperoptionlist.php | 40 ++++++++++---- src/paperstatus.php | 18 +++---- src/settings/s_json.php | 2 +- test/t_paperapi.php | 4 +- test/t_settings.php | 18 +++---- 12 files changed, 194 insertions(+), 95 deletions(-) diff --git a/devel/apidoc/submissions.md b/devel/apidoc/submissions.md index c37c30635..36912cb1b 100644 --- a/devel/apidoc/submissions.md +++ b/devel/apidoc/submissions.md @@ -34,16 +34,27 @@ A request with a `p` parameter (as a path parameter `/{p}/paper` or a query parameter) modifies the submission with that ID. The special ID `new` can be used to create a submission. -The body of the request may be formatted as an HTML form -(`application/x-www-form-urlencoded` or `multipart/form-data`), a JSON object -(`application/json`), or a ZIP file (`application/zip`—see below). HTML form -input follows the conventions of the HotCRP web application and is subject to -change at any time. +Modifications are specified using a JSON object. There are three ways to +provide that JSON, depending on the content-type of the request: + +1. As a request body with content-type `application/json`. +2. As a file named `data.json` in an uploaded ZIP archive, with content-type + `application/zip`. +3. As a parameter named `json` (body type + `application/x-www-form-urlencoded` or `multipart/form-data`). + +The JSON upload must be formatted as an object. + +ZIP and form uploads also support document upload. A document is referenced +via `content_file` fields in the JSON. ### Multiple submissions A request with no `p` parameter can create or modify any number of -submissions. +submissions. Upload types are the same as for single submissions, but the JSON +upload is defined as an array of objects. These objects are processed in turn. + +Currently, multiple-submission upload is only allowed for administrators. ### ZIP uploads @@ -66,3 +77,14 @@ $ cat data.json $ zip upload.zip data.json paper.pdf $ curl -H "Authorization: bearer hct_XXX" --data-binary @upload.zip -H "Content-Type: application/zip" SITEURL/api/paper ``` +### Parameters + +Set `dry_run=1` to check the upload for errors without modifying the +database. + +Three additional parameters are available to administrators. Set +`disable_users=1` to disable newly-created users; set `add_topics=1` to +automatically add newly-referenced topics; and set `notify=0` to make changes +without notifying contacts. + +### Responses diff --git a/etc/apiexpansions.json b/etc/apiexpansions.json index c4cacb12d..8eefdfea5 100644 --- a/etc/apiexpansions.json +++ b/etc/apiexpansions.json @@ -5,7 +5,12 @@ }, { "name": "paper", "post": true, "merge": true, - "tags": ["Submissions"], "order": 0 + "tags": ["Submissions"], "order": 0, + "response": "?paper ?papers ?+change_list ?+change_lists ?+valid", + "response_info": {"paper": "paper", "papers": "[paper]", + "change_list": "[string]", "change_lists": "[[string]]", + "valid": "boolean", "dry_run": "boolean" + } }, { "name": "formatcheck", "get": true, "merge": true, @@ -167,8 +172,7 @@ "name": "comment", "get": true, "merge": true, "tags": ["Comments"], "order": 0, "parameter_info": {"content": {"type": "boolean", "description": "False omits comment content from response"}}, - "response_info": {"comment": {"$ref": "#/components/schemas/comment"}, - "comments": {"type": "list", "items": {"$ref": "#/components/schemas/comment"}}} + "response_info": {"comment": "comment", "comments": "[comment]"} }, { "name": "comment", "post": true, "merge": true, @@ -185,7 +189,7 @@ "blind": {"type": "boolean"}, "by_author": {"type": "boolean"}, "review_token": {"type": "string"}}, - "response_info": {"comment": {"$ref": "#/components/schemas/comment"}} + "response_info": {"comment": "comment"} }, { "name": "mentioncompletion", "get": true, "merge": true, diff --git a/etc/apifunctions.json b/etc/apifunctions.json index b4aa9b83b..00f6cdcf8 100644 --- a/etc/apifunctions.json +++ b/etc/apifunctions.json @@ -88,7 +88,7 @@ { "name": "fieldhtml", "get": true, "function": "Search_API::fieldhtml", - "parameters": "f ?aufull ?session q ?t ?sort ?qt ?reviewer", + "parameters": "f ?aufull ?session q ?t ?qt ?reviewer ?sort ?scoresort", "response": "fields data ?stat ?classes ?attr" }, { @@ -188,9 +188,9 @@ "response": "?paper ?papers" }, { - "name": "paper", "post": true, "allow_if": "chair", + "name": "paper", "post": true, "function": "Paper_API::run", - "parameters": "?p ?dryrun" + "parameters": "?p ?dry_run ?disable_users ?add_topics ?notify" }, { "name": "pc", "get": true, @@ -300,8 +300,8 @@ { "name": "settings", "get": true, "post": true, "function": "Settings_API::run", - "parameters": "?=settings ?dryrun", - "response": "?dry_run ?changes" + "parameters": "?=settings ?+dry_run ?+reset ?+filename", + "response": "?+dry_run ?+change_list" }, { "name": "shepherd", "get": true, "paper": true, diff --git a/etc/openapi.json b/etc/openapi.json index c9d32b30d..0a8c1d851 100644 --- a/etc/openapi.json +++ b/etc/openapi.json @@ -91,7 +91,46 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/minimal_response" + "allOf": [ + { + "$ref": "#/components/schemas/minimal_response" + }, + { + "type": "object", + "properties": { + "paper": { + "$ref": "#/components/schemas/paper" + }, + "papers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/paper" + } + }, + "change_list": { + "type": "array", + "items": { + "type": "string" + } + }, + "change_lists": { + "type": "array", + "items": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "valid": { + "type": "array", + "items": { + "type": "boolean" + } + } + } + } + ] } } } @@ -112,14 +151,32 @@ "$ref": "#/components/parameters/p.opt" }, { - "name": "dryrun", + "name": "dry_run", + "in": "query", + "required": false, + "schema": {} + }, + { + "name": "disable_users", + "in": "query", + "required": false, + "schema": {} + }, + { + "name": "add_topics", + "in": "query", + "required": false, + "schema": {} + }, + { + "name": "notify", "in": "query", "required": false, "schema": {} } ], "summary": "Create or modify submission(s)", - "description": "### Single submission\n\nA request with a `p` parameter (as a path parameter `/{p}/paper` or a query\nparameter) modifies the submission with that ID. The special ID `new` can be\nused to create a submission.\n\nThe body of the request may be formatted as an HTML form\n(`application/x-www-form-urlencoded` or `multipart/form-data`), a JSON object\n(`application/json`), or a ZIP file (`application/zip`—see below). HTML form\ninput follows the conventions of the HotCRP web application and is subject to\nchange at any time.\n\n### Multiple submissions\n\nA request with no `p` parameter can create or modify any number of\nsubmissions.\n\n### ZIP uploads\n\nA ZIP upload should contain a file named `data.json` (`PREFIX-data.json` is\nalso acceptable). This file’s content is parsed as JSON and treated a\nsubmission object (or array of submission objects). Attachment fields in the\nJSON content can refer to other files in the ZIP. For instance, this shell\nsession might upload a submission with content `paper.pdf`:\n\n```\n$ cat data.json\n{\n\t\"object\": \"paper\",\n\t\"pid\": \"new\",\n\t\"title\": \"Aught: A Methodology for the Visualization of Scheme\",\n\t\"authors\": [{\"name\": \"Nevaeh Gomez\", \"email\": \"ngomez@example.edu\"}],\n\t\"submission\": {\"content_file\": \"paper.pdf\"},\n\t\"status\": \"submitted\"\n}\n$ zip upload.zip data.json paper.pdf\n$ curl -H \"Authorization: bearer hct_XXX\" --data-binary @upload.zip -H \"Content-Type: application/zip\" SITEURL/api/paper\n```\n" + "description": "### Single submission\n\nA request with a `p` parameter (as a path parameter `/{p}/paper` or a query\nparameter) modifies the submission with that ID. The special ID `new` can be\nused to create a submission.\n\nModifications are specified using a JSON object. There are three ways to\nprovide that JSON, depending on the content-type of the request:\n\n1. As a request body with content-type `application/json`.\n2. As a file named `data.json` in an uploaded ZIP archive, with content-type\n `application/zip`.\n3. As a parameter named `json` (body type\n `application/x-www-form-urlencoded` or `multipart/form-data`).\n\nThe JSON upload must be formatted as an object.\n\nZIP and form uploads also support document upload. A document is referenced\nvia `content_file` fields in the JSON.\n\n### Multiple submissions\n\nA request with no `p` parameter can create or modify any number of\nsubmissions. Upload types are the same as for single submissions, but the JSON\nupload is defined as an array of objects. These objects are processed in turn.\n\nCurrently, multiple-submission upload is only allowed for administrators.\n\n### ZIP uploads\n\nA ZIP upload should contain a file named `data.json` (`PREFIX-data.json` is\nalso acceptable). This file’s content is parsed as JSON and treated a\nsubmission object (or array of submission objects). Attachment fields in the\nJSON content can refer to other files in the ZIP. For instance, this shell\nsession might upload a submission with content `paper.pdf`:\n\n```\n$ cat data.json\n{\n\t\"object\": \"paper\",\n\t\"pid\": \"new\",\n\t\"title\": \"Aught: A Methodology for the Visualization of Scheme\",\n\t\"authors\": [{\"name\": \"Nevaeh Gomez\", \"email\": \"ngomez@example.edu\"}],\n\t\"submission\": {\"content_file\": \"paper.pdf\"},\n\t\"status\": \"submitted\"\n}\n$ zip upload.zip data.json paper.pdf\n$ curl -H \"Authorization: bearer hct_XXX\" --data-binary @upload.zip -H \"Content-Type: application/zip\" SITEURL/api/paper\n```\n### Parameters\n\nSet `dry_run=1` to check the upload for errors without modifying the\ndatabase.\n\nThree additional parameters are available to administrators. Set\n`disable_users=1` to disable newly-created users; set `add_topics=1` to\nautomatically add newly-referenced topics; and set `notify=0` to make changes\nwithout notifying contacts.\n\n### Responses\n" } }, "/formatcheck": { @@ -1003,7 +1060,8 @@ } } } - } + }, + "summary": "Retrieve search results" } }, "/fieldhtml": { @@ -3557,27 +3615,6 @@ "tags": [ "Settings" ], - "parameters": [ - { - "name": "dryrun", - "in": "query", - "required": false, - "schema": {} - } - ], - "requestBody": { - "description": "", - "content": { - "application/x-www-form-urlencoded": { - "schema": { - "type": "object", - "properties": { - "settings": {} - } - } - } - } - }, "responses": { "200": { "description": "", @@ -3618,7 +3655,19 @@ ], "parameters": [ { - "name": "dryrun", + "name": "dry_run", + "in": "query", + "required": false, + "schema": {} + }, + { + "name": "reset", + "in": "query", + "required": false, + "schema": {} + }, + { + "name": "filename", "in": "query", "required": false, "schema": {} @@ -3651,7 +3700,7 @@ "type": "object", "properties": { "dry_run": {}, - "changes": {} + "change_list": {} } } ] diff --git a/lib/messageset.php b/lib/messageset.php index 3a9da54ce..90de208b9 100644 --- a/lib/messageset.php +++ b/lib/messageset.php @@ -15,7 +15,7 @@ class MessageItem implements JsonSerializable { public $pos2; /** @var ?string */ public $context; - /** @var ?string */ + /** @var null|int|string */ public $landmark; /** @param ?string $field @@ -103,20 +103,22 @@ function with_prefix($text) { #[\ReturnTypeWillChange] function jsonSerialize() { - $x = []; + $x = ["status" => $this->status]; if ($this->field !== null) { $x["field"] = $this->field; } if ($this->message !== "") { $x["message"] = $this->message; } - $x["status"] = $this->status; if ($this->pos1 !== null && $this->context !== null) { $x["context"] = Ht::make_mark_substring($this->context, $this->pos1, $this->pos2); } else if ($this->pos1 !== null) { $x["pos1"] = $this->pos1; $x["pos2"] = $this->pos2; } + if ($this->landmark !== null) { + $x["landmark"] = $this->landmark; + } return (object) $x; } diff --git a/src/api/api_paper.php b/src/api/api_paper.php index 9ce85faf6..16b182dd0 100644 --- a/src/api/api_paper.php +++ b/src/api/api_paper.php @@ -95,18 +95,18 @@ private function run_post(Qrequest $qreq, ?PaperInfo $prow) { // set parameters if ($this->user->privChair) { - if (friendly_boolean($qreq->disableusers)) { + if (friendly_boolean($qreq->disable_users)) { $this->disable_users = true; } if (friendly_boolean($qreq->notify) === false) { $this->notify = false; } - if (friendly_boolean($qreq->addtopics)) { + if (friendly_boolean($qreq->add_topics)) { $this->conf->topic_set()->set_auto_add(true); $this->conf->options()->refresh_topics(); } } - if (friendly_boolean($qreq->dryrun)) { + if (friendly_boolean($qreq->dry_run ?? $qreq->dryrun)) { $this->dry_run = true; } @@ -234,18 +234,17 @@ private function paper_status() { /** @param PaperStatus $ps */ private function execute_save($ok, $ps) { - $this->ok = $this->ok && $ok; - if ($this->ok && !$this->dry_run) { - $this->ok = $ok = $ps->execute_save(); + if ($ok && !$this->dry_run) { + $ok = $ps->execute_save(); } foreach ($ps->message_list() as $mi) { - if (!$this->single && $this->landmark) { + if (!$this->single) { $mi->landmark = $this->landmark; } $this->append_item($mi); } $this->change_lists[] = $ps->changed_keys(); - if ($this->ok && !$this->dry_run) { + if ($ok && !$this->dry_run) { if ($ps->has_change()) { $ps->log_save_activity("via API"); } @@ -256,6 +255,7 @@ private function execute_save($ok, $ps) { $this->papers[] = null; } $this->valid[] = $ok; + $this->ok = $this->ok && $ok; } private function execute_fail() { @@ -271,6 +271,9 @@ private function make_result() { "ok" => $this->ok, "message_list" => $this->message_list() ]); + if ($this->dry_run) { + $jr->content["dry_run"] = true; + } if ($this->single) { $jr->content["change_list"] = $this->change_lists[0]; if ($this->npapers > 0) { @@ -318,14 +321,14 @@ static function analyze_json_pid(Conf $conf, $j, $pidflags = 0) { private function set_json_landmark($index, $jp, $expected = null) { $pidish = self::analyze_json_pid($this->conf, $jp, 0); if ($pidish && ($expected === null || $pidish === $expected)) { - $this->landmark = $pidish === "new" ? "index {$index}" : "#{$pidish}"; + $this->landmark = $index; return true; } $pidkey = isset($jp->pid) || !isset($jp->id) ? "pid" : "id"; $msg = $pidish ? "<0>ID does not match" : "<0>Format error"; $mi = $this->error_at($pidkey, $msg); if (!$this->single) { - $mi->landmark = "index {$index}"; + $mi->landmark = $index; } return false; } diff --git a/src/api/api_settings.php b/src/api/api_settings.php index 44736e986..1dd95f6d3 100644 --- a/src/api/api_settings.php +++ b/src/api/api_settings.php @@ -5,11 +5,12 @@ class Settings_API { static function run(Contact $user, Qrequest $qreq) { $content = ["ok" => true]; + $reset = !!friendly_boolean($qreq->reset); if ($qreq->valid_post()) { - if (isset($qreq->settings)) { - $jtext = $qreq->settings; - } else if ($qreq->body_content_type() === Mimetype::JSON_TYPE) { + if ($qreq->body_content_type() === Mimetype::JSON_TYPE) { $jtext = $qreq->body(); + } else if (isset($qreq->settings)) { + $jtext = $qreq->settings; } else { return JsonResult::make_missing_error("settings"); } @@ -18,11 +19,9 @@ static function run(Contact $user, Qrequest $qreq) { return JsonResult::make_permission_error(); } $sv->add_json_string($jtext, $qreq->filename); - if (isset($qreq->reset)) { - $sv->set_req("reset", friendly_boolean($qreq->reset) ? "1" : ""); - } + $sv->set_req("reset", $reset ? "1" : ""); $sv->parse(); - $dry_run = $qreq->dryrun || $qreq->dry_run; + $dry_run = friendly_boolean($qreq->dry_run ?? $qreq->dryrun); if ($dry_run) { $content["dry_run"] = true; } else { @@ -30,7 +29,7 @@ static function run(Contact $user, Qrequest $qreq) { } $content["ok"] = !$sv->has_error(); $content["message_list"] = $sv->message_list(); - $content["changes"] = $sv->changed_keys(); + $content["change_list"] = $sv->changed_keys(); if ($dry_run || $sv->has_error()) { return new JsonResult($content); } @@ -39,7 +38,7 @@ static function run(Contact $user, Qrequest $qreq) { if (!$sv->viewable_by_user()) { return JsonResult::make_permission_error(); } - $content["settings"] = $sv->all_jsonv(["reset" => !!$qreq->reset]); + $content["settings"] = $sv->all_jsonv(["reset" => $reset]); return new JsonResult($content); } diff --git a/src/paperoptionlist.php b/src/paperoptionlist.php index 726176679..35f217658 100644 --- a/src/paperoptionlist.php +++ b/src/paperoptionlist.php @@ -21,6 +21,10 @@ class PaperOptionList implements IteratorAggregate { private $_olist_nonfinal; /** @var AbbreviationMatcher */ private $_nonpaper_am; + /** @var array */ + private $_keymap; + /** @var bool */ + private $_keymap_options = false; function __construct(Conf $conf) { $this->conf = $conf; @@ -237,18 +241,36 @@ function checked_option_by_id($id) { return $o; } - /** @param string $key + /** @param ?string $key * @return ?PaperOption */ - function option_by_field_key($key) { - // Since this function is rarely used, don’t bother optimizing it. - if (($colon = strpos($key, ":"))) { + function option_by_key($key) { + if ($key === null) { + return null; + } + if (($colon = strpos($key, ":")) !== false) { $key = substr($key, 0, $colon); } - foreach ($this->unsorted_field_list(null, null) as $f) { - if ($f->field_key() === $key) - return $f; + if (str_starts_with($key, "opt") + && ctype_digit(substr($key, 3))) { + return $this->option_by_id(intval(substr($key, 3))); + } + if ($this->_keymap === null) { + $this->_keymap = []; + $this->_keymap_options = false; + foreach ($this->intrinsic_json_map() as $id => $oj) { + if (($oj->nonpaper ?? false) !== true) { + $this->_keymap[$oj->json_key] = $id; + } + } + } + if (!$this->_keymap_options && !isset($this->_keymap[$key])) { + $this->_keymap_options = true; + foreach ($this->normal() as $id => $opt) { + $this->_keymap[$opt->json_key()] = $id; + } } - return null; + $oid = $this->_keymap[$key] ?? null; + return $oid !== null ? $this->option_by_id($oid) : null; } /** @return array */ @@ -365,7 +387,7 @@ function page_fields(?PaperInfo $prow = null) { /** @suppress PhanAccessReadOnlyProperty */ function invalidate_options() { if ($this->_jmap !== null || $this->_ijmap !== null) { - $this->_jmap = $this->_ijmap = null; + $this->_jmap = $this->_ijmap = $this->_keymap = null; $this->_omap = $this->_imap = []; $this->_olist = $this->_olist_nonfinal = $this->_nonpaper_am = null; } diff --git a/src/paperstatus.php b/src/paperstatus.php index 9db627b38..dfceb3ac3 100644 --- a/src/paperstatus.php +++ b/src/paperstatus.php @@ -207,23 +207,21 @@ function append_messages_from($ov) { /** @param ?list $oids * @return list */ function decorated_message_list($oids = null) { - assert(!$this->json_fields); $ms = []; foreach ($this->message_list() as $mi) { if (($mi->field ?? "") !== "" && (str_ends_with($mi->field, ":context") || $mi->status === MessageSet::INFORM) && !str_starts_with($mi->field, "status:")) { - // do not report in decorated list - } else if ($mi->field - && $mi->message !== "" - && ($o = $this->conf->options()->option_by_field_key($mi->field))) { - if ($oids === null || in_array($o->id, $oids)) { - $link = Ht::link(htmlspecialchars($o->edit_title()), "#" . $o->readable_formid()); - $ms[] = $mi->with(["message" => "<5>{$link}: " . $mi->message_as(5)]); + continue; + } + if (($o = $this->conf->options()->option_by_key($mi->field))) { + if ($oids !== null && !in_array($o->id, $oids)) { + continue; } - } else { - $ms[] = $mi; + $link = Ht::link(htmlspecialchars($o->edit_title()), "#" . $o->readable_formid()); + $mi = $mi->with(["message" => "<5>{$link}: " . $mi->message_as(5)]); } + $ms[] = $mi; } return $ms; } diff --git a/src/settings/s_json.php b/src/settings/s_json.php index 8b913766d..19701d7e7 100644 --- a/src/settings/s_json.php +++ b/src/settings/s_json.php @@ -31,7 +31,7 @@ static function print(SettingValues $sv) { if (!empty($hl) || !empty($tips)) { echo ' data-highlight-utf8-pos'; } - echo ' data-reflect-highlight-api="=api/settings?dryrun=1 settings">', + echo ' data-reflect-highlight-api="=api/settings?dry_run=1 settings">', $mainh, "\n", '
', '

Selected settings

', diff --git a/test/t_paperapi.php b/test/t_paperapi.php index ca068a893..5212c55e3 100644 --- a/test/t_paperapi.php +++ b/test/t_paperapi.php @@ -159,7 +159,7 @@ function test_assigned_paper_id() { function test_dry_run() { $prow = $this->conf->checked_paper_by_id($this->npid); $original_title = $prow->title; - $qreq = $this->make_post_form_qreq(["dryrun" => 1, "title" => "New paper with changed ID", "p" => $prow->paperId]); + $qreq = $this->make_post_form_qreq(["dry_run" => 1, "title" => "New paper with changed ID", "p" => $prow->paperId]); $jr = call_api("=paper", $this->u_estrin, $qreq, $prow); xassert_eqq($jr->ok, true); xassert_eqq($jr->paper ?? null, null); @@ -170,7 +170,7 @@ function test_dry_run() { // dry run does not create new paper $npapers = $this->conf->fetch_ivalue("select count(*) from Paper"); - $qreq = $this->make_post_form_qreq(["p" => "new", "status:submit" => 1, "title" => "Goddamnit", "abstract" => "This is an abstract", "has_authors" => 1, "authors:1:name" => "Bobby Flay", "authors:1:email" => "flay@_.com", "dryrun" => 1]); + $qreq = $this->make_post_form_qreq(["p" => "new", "status:submit" => 1, "title" => "Goddamnit", "abstract" => "This is an abstract", "has_authors" => 1, "authors:1:name" => "Bobby Flay", "authors:1:email" => "flay@_.com", "dry_run" => 1]); $jr = call_api("=paper", $this->u_estrin, $qreq); xassert_eqq($jr->ok, true); xassert_eqq($jr->paper ?? null, null); diff --git a/test/t_settings.php b/test/t_settings.php index a6a248936..82a099b52 100644 --- a/test/t_settings.php +++ b/test/t_settings.php @@ -1280,14 +1280,14 @@ function test_subform_condition() { function test_json_settings_api() { $x = call_api("settings", $this->u_chair, []); xassert($x->ok); - xassert(!isset($x->changes)); + xassert(!isset($x->change_list)); xassert(is_object($x->settings)); xassert_eqq($x->settings->review_blind, "blind"); $x = call_api("=settings", $this->u_chair, ["settings" => "{}"]); xassert($x->ok); xassert_eqq($x->message_list, []); - xassert_eqq($x->changes, []); + xassert_eqq($x->change_list, []); $x = call_api("=settings", $this->u_chair, ["settings" => "{\"notgood\":true}"]); xassert($x->ok); @@ -1307,13 +1307,13 @@ function test_json_settings_api() { $x = call_api("=settings", $this->u_chair, ["settings" => "{\"review_blind\":\"open\"}"]); xassert($x->ok); - xassert_eqq($x->changes, ["rev_blind"]); + xassert_eqq($x->change_list, ["rev_blind"]); xassert_eqq($x->settings->review_blind, "open"); xassert_eqq($this->conf->fetch_ivalue("select value from Settings where name='rev_blind'"), 0); $x = call_api("=settings", $this->u_chair, ["settings" => "{\"review_blind\":\"blind\"}"]); xassert($x->ok); - xassert_eqq($x->changes, ["rev_blind"]); + xassert_eqq($x->change_list, ["rev_blind"]); xassert_eqq($x->settings->review_blind, "blind"); xassert_eqq($this->conf->fetch_ivalue("select value from Settings where name='rev_blind'"), null); @@ -1374,7 +1374,7 @@ function test_json_settings_roundtrip() { $x = call_api("settings", $this->u_chair, []); xassert($x->ok); - xassert(!isset($x->changes)); + xassert(!isset($x->change_list)); xassert(is_object($x->settings)); xassert_eqq($x->settings->review_blind, "blind"); xassert_eqq($x->settings->rf[5]->required, false); @@ -1384,7 +1384,7 @@ function test_json_settings_roundtrip() { $x = call_api("=settings", $this->u_chair, ["settings" => $sa]); xassert($x->ok); xassert_eqq($x->message_list, []); - xassert_eqq($x->changes, []); + xassert_eqq($x->change_list, []); xassert_eqq($this->conf->setting_data("ioptions"), null); xassert_eqq($this->conf->fetch_ivalue("select value from Settings where name='rev_blind'"), null); @@ -1396,7 +1396,7 @@ function test_json_settings_roundtrip() { $x = call_api("=settings", $this->u_chair, ["settings" => $sb]); xassert($x->ok); xassert_eqq($x->message_list, []); - xassert_eqq($x->changes, []); + xassert_eqq($x->change_list, []); xassert_eqq($this->conf->fetch_ivalue("select value from Settings where name='rev_blind'"), null); $sc = json_encode_browser($x->settings, JSON_PRETTY_PRINT); @@ -1408,7 +1408,7 @@ function test_json_settings_roundtrip() { $x = call_api("=settings", $this->u_chair, ["settings" => json_encode_browser($x->settings)]); xassert($x->ok); xassert_eqq($x->message_list, []); - xassert_eqq($x->changes, []); + xassert_eqq($x->change_list, []); $sd = json_encode_browser($x->settings, JSON_PRETTY_PRINT); if ($sc !== $sd) { @@ -1417,7 +1417,7 @@ function test_json_settings_roundtrip() { } function test_json_settings_errors() { - $x = call_api("=settings", $this->u_chair, ["dryrun" => 1, "settings" => "{\"review\":[\"a\"]}"]); + $x = call_api("=settings", $this->u_chair, ["dry_run" => 1, "settings" => "{\"review\":[\"a\"]}"]); xassert(!$x->ok); $mi = $x->message_list[0] ?? null; xassert_eqq($mi->status, 2);