diff --git a/docs/cli-commands.md b/docs/cli-commands.md index df088e46a..759a59759 100644 --- a/docs/cli-commands.md +++ b/docs/cli-commands.md @@ -9,18 +9,39 @@ Most of the commands documented on this page assume that you know how to address the subdomains of a DSP server. There are three relevant URLs you should know about: -- Subdomain `admin` stands for the DSP-APP frontend that you look at in your browser +- Subdomains `admin`/`app` stand for the DSP-APP frontend that you look at in your browser - Subdomain `api` stands for the DSP-API (where DSP-TOOLS sends its data to) - Subdomain `iiif` stands for the SIPI server interface (where DSP-TOOLS sends the multimedia files to) -This means that for uploading data to a DSP server +This means that for uploading data to the DSP server on the domain `dasch.swiss`, you have to type the following: ```bash -dsp-tools xmlupload -s https://api.dasch.swiss -u root@example.com -p 'test' -S https://iiif.dasch.swiss xml_data_file.xml +dsp-tools xmlupload -s https://api.dasch.swiss -u 'your@email.com' -p 'password' xml_data_file.xml ``` +If the user input is not correct, +DSP-TOOLS tries to guess the correct subdomains. +If the provided server is any one of the following: + +```text +http(s)://admin.dasch.swiss +http(s)://app.dasch.swiss +http(s)://api.dasch.swiss +http(s)://iiif.dasch.swiss +http(s)://dasch.swiss +dasch.swiss +``` + +then DSP-TOOLS will treat it as `https://api.dasch.swiss`, +and derive the SIPI server URL `https://iiif.dasch.swiss` from it. + +This guessing feature comes with a price, though: + +- Only servers ending with `dasch.swiss` are supported. +- If a server's configuration differs from the convention described above, DSP-TOOLS will fail. + ## `create` @@ -55,7 +76,7 @@ on the DSP server `https://admin.dasch.swiss`, it is necessary to specify the following options: ```bash -dsp-tools create -s https://api.dasch.swiss -u root@example.com -p 'test' project_definition.json +dsp-tools create -s https://api.dasch.swiss -u 'your@email.com' -p 'password' project_definition.json ``` The expected JSON format is [documented here](./file-formats/json-project/overview.md). @@ -81,11 +102,19 @@ The following options are available: - `-P` | `--project` (mandatory): shortcode, shortname or IRI of the project - `-v` | `--verbose` (optional): print more information about the progress to the console -The following example shows -how to get a project from the DSP server `https://admin.dasch.swiss`: +The defaults are intended for local testing: + +```bash +dsp-tools get -P my_project project_definition.json +``` + +will get `my_project` from `localhost`. + +In order to get a project from the DSP server `https://admin.dasch.swiss`, +it is necessary to specify the following options: ```bash -dsp-tools get -s https://api.dasch.swiss -u root@example.com -p 'test' -P my_project project_definition.json +dsp-tools get -s https://api.dasch.swiss -u 'your@email.com' -p 'password' -P my_project project_definition.json ``` The expected JSON format is [documented here](./file-formats/json-project/overview.md). @@ -105,8 +134,6 @@ The following options are available: - `-s` | `--server` (optional, default: `0.0.0.0:3333`): URL of the DSP server where DSP-TOOLS sends the data to - `-u` | `--user` (optional, default: `root@example.com`): username (e-mail) used for authentication with the DSP-API - `-p` | `--password` (optional, default: `test`): password used for authentication with the DSP-API -- `-S` | `--sipi` (optional, default: `http://0.0.0.0:1024`): - URL of the SIPI server where DSP-TOOLS sends the multimedia files to - `-i` | `--imgdir` (optional, default: `.`): folder from where the paths in the `` tags are evaluated - `-I` | `--incremental` (optional) : The links in the XML file point to IRIs (on the server) instead of IDs (in the same XML file). @@ -132,7 +159,7 @@ to the DSP server `https://admin.dasch.swiss`, it is necessary to specify the following options: ```bash -dsp-tools xmlupload -s https://api.dasch.swiss -u root@example.com -p 'test' -S https://iiif.dasch.swiss xml_data_file.xml +dsp-tools xmlupload -s https://api.dasch.swiss -u 'your@email.com' -p 'password' xml_data_file.xml ``` The expected XML format is [documented here](./file-formats/xml-data-file.md). diff --git a/docs/internal/fast-xmlupload.md b/docs/internal/fast-xmlupload.md index 5e848eb9b..49c277759 100644 --- a/docs/internal/fast-xmlupload.md +++ b/docs/internal/fast-xmlupload.md @@ -119,7 +119,6 @@ The following options are available: - `-n` | `--nthreads` (optional, default 4): number of threads to use for uploading (optimum depends on the number of CPUs on the server) - `-s` | `--server` (optional, default: `0.0.0.0:3333`): URL of the DSP server -- `-S` | `--sipi-url` (optional, default: `0.0.0.0:1024`): URL of the SIPI server - `-u` | `--user` (optional, default: `root@example.com`): username (e-mail) used for authentication with the DSP-API - `-p` | `--password` (optional, default: `test`): password used for authentication with the DSP-API @@ -134,6 +133,5 @@ The following options are available: - `-f` | `--pkl-file` (mandatory): path to the pickle file that was written by the processing step - `-s` | `--server` (optional, default: `0.0.0.0:3333`): URL of the DSP server -- `-S` | `--sipi-url` (optional, default: `0.0.0.0:1024`): URL of the SIPI server - `-u` | `--user` (optional, default: `root@example.com`): username (e-mail) used for authentication with the DSP-API - `-p` | `--password` (optional, default: `test`): password used for authentication with the DSP-API diff --git a/src/dsp_tools/dsp_tools.py b/src/dsp_tools/dsp_tools.py index ea9f64e3d..28480d33b 100644 --- a/src/dsp_tools/dsp_tools.py +++ b/src/dsp_tools/dsp_tools.py @@ -6,6 +6,8 @@ import sys from importlib.metadata import version +import regex + from dsp_tools.excel2xml import excel2xml from dsp_tools.fast_xmlupload.process_files import process_files from dsp_tools.fast_xmlupload.upload_files import upload_files @@ -30,7 +32,11 @@ logger = get_logger(__name__) -def make_parser() -> argparse.ArgumentParser: +def _make_parser( + default_dsp_api_url: str, + root_user_email: str, + root_user_pw: str, +) -> argparse.ArgumentParser: """ Create a parser for the command line arguments @@ -38,17 +44,10 @@ def make_parser() -> argparse.ArgumentParser: parser """ # help texts - username_text = "username (e-mail) used for authentication with the DSP-API " - password_text = "password used for authentication with the DSP-API " + username_text = "username (e-mail) used for authentication with the DSP-API" + password_text = "password used for authentication with the DSP-API" dsp_server_text = "URL of the DSP server" verbose_text = "print more information about the progress to the console" - sipi_text = "URL of the Sipi server" - - # default values - default_dsp_api_url = "http://0.0.0.0:3333" - default_user = "root@example.com" - default_pw = "test" - default_sipi = "http://0.0.0.0:1024" # make a parser parser = argparse.ArgumentParser( @@ -66,8 +65,8 @@ def make_parser() -> argparse.ArgumentParser: ) parser_create.set_defaults(action="create") parser_create.add_argument("-s", "--server", default=default_dsp_api_url, help=dsp_server_text) - parser_create.add_argument("-u", "--user", default=default_user, help=username_text) - parser_create.add_argument("-p", "--password", default=default_pw, help=password_text) + parser_create.add_argument("-u", "--user", default=root_user_email, help=username_text) + parser_create.add_argument("-p", "--password", default=root_user_pw, help=password_text) parser_create.add_argument( "-V", "--validate-only", @@ -91,8 +90,8 @@ def make_parser() -> argparse.ArgumentParser: ) parser_get.set_defaults(action="get") parser_get.add_argument("-s", "--server", default=default_dsp_api_url, help=dsp_server_text) - parser_get.add_argument("-u", "--user", default=default_user, help=username_text) - parser_get.add_argument("-p", "--password", default=default_pw, help=password_text) + parser_get.add_argument("-u", "--user", default=root_user_email, help=username_text) + parser_get.add_argument("-p", "--password", default=root_user_pw, help=password_text) parser_get.add_argument("-P", "--project", help="shortcode, shortname or IRI of the project", required=True) parser_get.add_argument("-v", "--verbose", action="store_true", help=verbose_text) parser_get.add_argument("project_definition", help="path to the file the project should be written to") @@ -103,14 +102,8 @@ def make_parser() -> argparse.ArgumentParser: parser_upload.add_argument( "-s", "--server", default=default_dsp_api_url, help="URL of the DSP server where DSP-TOOLS sends the data to" ) - parser_upload.add_argument("-u", "--user", default=default_user, help=username_text) - parser_upload.add_argument("-p", "--password", default=default_pw, help=password_text) - parser_upload.add_argument( - "-S", - "--sipi", - default=default_sipi, - help="URL of the SIPI server where DSP-TOOLS sends the multimedia files to", - ) + parser_upload.add_argument("-u", "--user", default=root_user_email, help=username_text) + parser_upload.add_argument("-p", "--password", default=root_user_pw, help=password_text) parser_upload.add_argument( "-i", "--imgdir", default=".", help="folder from where the paths in the tags are evaluated" ) @@ -155,9 +148,8 @@ def make_parser() -> argparse.ArgumentParser: parser_upload_files.add_argument("-d", "--processed-dir", help="path to the directory with the processed files") parser_upload_files.add_argument("-n", "--nthreads", type=int, default=4, help="number of threads to use") parser_upload_files.add_argument("-s", "--server", default=default_dsp_api_url, help=dsp_server_text) - parser_upload_files.add_argument("-S", "--sipi-url", default=default_sipi, help=sipi_text) - parser_upload_files.add_argument("-u", "--user", default=default_user, help=username_text) - parser_upload_files.add_argument("-p", "--password", default=default_pw, help=password_text) + parser_upload_files.add_argument("-u", "--user", default=root_user_email, help=username_text) + parser_upload_files.add_argument("-p", "--password", default=root_user_pw, help=password_text) # fast-xmlupload parser_fast_xmlupload_files = subparsers.add_parser( @@ -167,9 +159,8 @@ def make_parser() -> argparse.ArgumentParser: parser_fast_xmlupload_files.set_defaults(action="fast-xmlupload") parser_fast_xmlupload_files.add_argument("-f", "--pkl-file", help="path to pickle file written by 'process-files'") parser_fast_xmlupload_files.add_argument("-s", "--server", default=default_dsp_api_url, help=dsp_server_text) - parser_fast_xmlupload_files.add_argument("-S", "--sipi-url", default=default_sipi, help=sipi_text) - parser_fast_xmlupload_files.add_argument("-u", "--user", default=default_user, help=username_text) - parser_fast_xmlupload_files.add_argument("-p", "--password", default=default_pw, help=password_text) + parser_fast_xmlupload_files.add_argument("-u", "--user", default=root_user_email, help=username_text) + parser_fast_xmlupload_files.add_argument("-p", "--password", default=root_user_pw, help=password_text) parser_fast_xmlupload_files.add_argument("xml_file", help="path to XML file containing the data") # excel2json @@ -273,6 +264,29 @@ def make_parser() -> argparse.ArgumentParser: return parser +def _parse_arguments( + user_args: list[str], + parser: argparse.ArgumentParser, +) -> argparse.Namespace: + """ + Parse the user-provided CLI arguments. + If no action is provided, + print the help text and exit with error code 1. + + Args: + user_args: user-provided CLI arguments + parser: parser used to parse the arguments + + Returns: + parsed arguments + """ + args = parser.parse_args(user_args) + if not hasattr(args, "action"): + parser.print_help(sys.stderr) + sys.exit(1) + return args + + def _log_cli_arguments(parsed_args: argparse.Namespace) -> None: """ Log the CLI arguments passed by the user from the command line. @@ -302,16 +316,96 @@ def _log_cli_arguments(parsed_args: argparse.Namespace) -> None: logger.info("*" * asterisk_count) -def call_requested_action( - user_args: list[str], - parser: argparse.ArgumentParser, -) -> bool: +def _get_canonical_server_and_sipi_url( + server: str, + default_dsp_api_url: str, + default_sipi_url: str, +) -> tuple[str, str]: """ - With the help of the parser, parse the user arguments and call the appropriate method of DSP-TOOLS. + Based on the DSP server URL passed by the user, + transform it to its canonical form, + and derive the SIPI URL from it. + + If the DSP server URL points to port 3333 on localhost, + the SIPI URL will point to port 1024 on localhost. + + If the DSP server URL points to a remote server ending in "dasch.swiss", + modify it (if necessary) to point to the "api" subdomain of that server, + and add a new "sipi_url" argument pointing to the "iiif" subdomain of that server. Args: - user_args: list of arguments passed by the user from the command line - parser: parser to parse the user arguments + server: DSP server URL passed by the user + default_dsp_api_url: default DSP server on localhost + default_sipi_url: default SIPI server on localhost + + Raises: + UserError: if the DSP server URL passed by the user is invalid + + Returns: + canonical DSP URL and SIPI URL + """ + localhost_match = regex.search(r"(0\.0\.0\.0|localhost):3333", server) + remote_url_match = regex.search(r"^(?:https?:\/\/)?(?:admin\.|api\.|iiif\.|app\.)?((?:.+\.)?dasch)\.swiss", server) + + if localhost_match: + server = default_dsp_api_url + sipi_url = default_sipi_url + elif remote_url_match: + server = f"https://api.{remote_url_match.group(1)}.swiss" + sipi_url = f"https://iiif.{remote_url_match.group(1)}.swiss" + else: + logger.error(f"Invalid DSP server URL '{server}'") + raise UserError(f"ERROR: Invalid DSP server URL '{server}'") + + logger.info(f"Using DSP server '{server}' and SIPI server '{sipi_url}'") + print(f"Using DSP server '{server}' and SIPI server '{sipi_url}'") + + return server, sipi_url + + +def _derive_sipi_url( + parsed_arguments: argparse.Namespace, + default_dsp_api_url: str, + default_sipi_url: str, +) -> argparse.Namespace: + """ + Modify the parsed arguments so that the DSP and SIPI URLs are correct. + Based on the DSP server URL passed by the user, + transform it to its canonical form, + and derive the SIPI URL from it. + + Args: + parsed_arguments: CLI arguments passed by the user, parsed by argparse + default_dsp_api_url: default DSP server on localhost + default_sipi_url: default SIPI server on localhost + + Raises: + UserError: if the DSP server URL passed by the user is invalid + + Returns: + the modified arguments + """ + if not hasattr(parsed_arguments, "server"): + # some CLI actions (like excel2json, excel2xml, start-stack, ...) don't have a server at all + return parsed_arguments + + server, sipi_url = _get_canonical_server_and_sipi_url( + server=parsed_arguments.server, + default_dsp_api_url=default_dsp_api_url, + default_sipi_url=default_sipi_url, + ) + parsed_arguments.server = server + parsed_arguments.sipi_url = sipi_url + + return parsed_arguments + + +def _call_requested_action(args: argparse.Namespace) -> bool: + """ + Call the appropriate method of DSP-TOOLS. + + Args: + args: parsed CLI arguments Raises: BaseError from the called methods @@ -321,13 +415,6 @@ def call_requested_action( Returns: success status """ - args = parser.parse_args(user_args) - if not hasattr(args, "action"): - parser.print_help(sys.stderr) - sys.exit(1) - - _log_cli_arguments(args) - if args.action == "create": if args.lists_only: if args.validate_only: @@ -373,7 +460,7 @@ def call_requested_action( user=args.user, password=args.password, imgdir=args.imgdir, - sipi=args.sipi, + sipi=args.sipi_url, verbose=args.verbose, incremental=args.incremental, save_metrics=args.metrics, @@ -466,10 +553,32 @@ def call_requested_action( def main() -> None: - """Main entry point of the program as referenced in pyproject.toml""" - parser = make_parser() + """ + Main entry point of the program as referenced in pyproject.toml + """ + default_dsp_api_url = "http://0.0.0.0:3333" + default_sipi_url = "http://0.0.0.0:1024" + root_user_email = "root@example.com" + root_user_pw = "test" + + parser = _make_parser( + default_dsp_api_url=default_dsp_api_url, + root_user_email=root_user_email, + root_user_pw=root_user_pw, + ) + parsed_arguments = _parse_arguments( + user_args=sys.argv[1:], + parser=parser, + ) + _log_cli_arguments(parsed_arguments) + try: - success = call_requested_action(user_args=sys.argv[1:], parser=parser) + parsed_arguments = _derive_sipi_url( + parsed_arguments=parsed_arguments, + default_dsp_api_url=default_dsp_api_url, + default_sipi_url=default_sipi_url, + ) + success = _call_requested_action(parsed_arguments) except UserError as err: print(err.message) sys.exit(1) diff --git a/test/e2e/test_connection.py b/test/e2e/test_connection.py index ca4d8a729..ca439a8e1 100644 --- a/test/e2e/test_connection.py +++ b/test/e2e/test_connection.py @@ -13,7 +13,7 @@ class TestConnection(unittest.TestCase): def setUp(self) -> None: """ - is executed before all tests; sets up a connection and logs in as user root + is executed before each test method; sets up a connection and logs in as user root """ self.con = Connection("http://0.0.0.0:3333") self.con.login("root@example.com", "test") diff --git a/test/e2e/test_fast_xmlupload.py b/test/e2e/test_fast_xmlupload.py index e26f983de..6602c87fa 100644 --- a/test/e2e/test_fast_xmlupload.py +++ b/test/e2e/test_fast_xmlupload.py @@ -29,7 +29,7 @@ class TestFastXmlUpload(unittest.TestCase): @classmethod def setUpClass(cls) -> None: """ - Is executed before the methods of this class are run + Is executed once before the methods of this class are run """ create_project( project_file_as_path_or_parsed=cls.json_file, diff --git a/test/e2e/test_group.py b/test/e2e/test_group.py index 7f8947c02..65cb7cb25 100644 --- a/test/e2e/test_group.py +++ b/test/e2e/test_group.py @@ -14,7 +14,7 @@ class TestGroup(unittest.TestCase): # pylint: disable=missing-class-docstring def setUp(self) -> None: """ - is executed before all tests; sets up a connection and logs in as user root + is executed before each test method; sets up a connection and logs in as user root """ self.con = Connection("http://0.0.0.0:3333") self.con.login("root@example.com", "test") diff --git a/test/e2e/test_listnode.py b/test/e2e/test_listnode.py index 23053981d..ccf7afede 100644 --- a/test/e2e/test_listnode.py +++ b/test/e2e/test_listnode.py @@ -14,7 +14,7 @@ class TestListNode(unittest.TestCase): # pylint: disable=missing-class-docstrin def setUp(self) -> None: """ - is executed before all tests; sets up a connection and logs in as user root + is executed before each test method; sets up a connection and logs in as user root """ self.con = Connection("http://0.0.0.0:3333") self.con.login("root@example.com", "test") diff --git a/test/e2e/test_ontology.py b/test/e2e/test_ontology.py index 3bb5fad40..c994eb075 100644 --- a/test/e2e/test_ontology.py +++ b/test/e2e/test_ontology.py @@ -17,7 +17,7 @@ class TestOntology(unittest.TestCase): def setUp(self) -> None: """ - is executed before all tests; sets up a connection and logs in as user root + is executed before each test method; sets up a connection and logs in as user root """ self.con = Connection("http://0.0.0.0:3333") self.con.login("root@example.com", "test") diff --git a/test/e2e/test_project.py b/test/e2e/test_project.py index 3c318cea1..10d08b04b 100644 --- a/test/e2e/test_project.py +++ b/test/e2e/test_project.py @@ -16,7 +16,7 @@ class TestProject(unittest.TestCase): def setUp(self) -> None: """ - is executed before all tests; sets up a connection and logs in as user root + is executed before each test method; sets up a connection and logs in as user root """ self.con = Connection("http://0.0.0.0:3333") self.con.login("root@example.com", "test") diff --git a/test/e2e/test_propertyclass.py b/test/e2e/test_propertyclass.py index 1cd8cb4a1..5dcc2aca0 100644 --- a/test/e2e/test_propertyclass.py +++ b/test/e2e/test_propertyclass.py @@ -29,7 +29,7 @@ class TestPropertyClass(unittest.TestCase): def setUp(self) -> None: """ - is executed before all tests; sets up a connection and logs in as user root; creates a new ontology + is executed before each test method; sets up a connection and logs in as user root; creates a new ontology """ self.con = Connection("http://0.0.0.0:3333") self.con.login("root@example.com", "test") diff --git a/test/e2e/test_resource.py b/test/e2e/test_resource.py index b1790b6d9..363e8eb5d 100644 --- a/test/e2e/test_resource.py +++ b/test/e2e/test_resource.py @@ -19,6 +19,9 @@ class TestResource(unittest.TestCase): @classmethod def setUpClass(cls) -> None: + """ + Is executed once before the methods of this class are run + """ cls.con.login("root@example.com", "test") @classmethod diff --git a/test/e2e/test_resourceclass.py b/test/e2e/test_resourceclass.py index fb4291c5a..c8e692b1c 100644 --- a/test/e2e/test_resourceclass.py +++ b/test/e2e/test_resourceclass.py @@ -21,7 +21,7 @@ class TestResourceClass(unittest.TestCase): def setUp(self) -> None: """ - is executed before all tests; sets up a connection and logs in as user root + is executed before each test method; sets up a connection and logs in as user root """ self.con = Connection("http://0.0.0.0:3333") self.con.login("root@example.com", "test") diff --git a/test/e2e/test_tools.py b/test/e2e/test_tools.py index a317ef637..ccabb0379 100644 --- a/test/e2e/test_tools.py +++ b/test/e2e/test_tools.py @@ -45,7 +45,7 @@ class TestTools(unittest.TestCase): @classmethod def setUpClass(cls) -> None: - """Is executed before the methods of this class are run""" + """Is executed once before the methods of this class are run""" os.makedirs("testdata/tmp", exist_ok=True) @classmethod diff --git a/test/e2e/test_user.py b/test/e2e/test_user.py index 33072469a..8a6c40481 100644 --- a/test/e2e/test_user.py +++ b/test/e2e/test_user.py @@ -20,7 +20,7 @@ class TestUser(unittest.TestCase): def setUp(self) -> None: """ - is executed before all tests; sets up a connection and logs in as user root + is executed before each test method; sets up a connection and logs in as user root """ self.con = Connection("http://0.0.0.0:3333") self.con.login("root@example.com", "test") diff --git a/test/unittests/test_cli.py b/test/unittests/test_cli.py new file mode 100644 index 000000000..5f5af2ac7 --- /dev/null +++ b/test/unittests/test_cli.py @@ -0,0 +1,189 @@ +"""unit tests for the command line interface""" + +import argparse +import unittest + +import pytest + +from dsp_tools.dsp_tools import _derive_sipi_url, _get_canonical_server_and_sipi_url +from dsp_tools.models.exceptions import UserError + + +class TestCLI(unittest.TestCase): + """ + unit tests for the command line interface + """ + + default_dsp_api_url = "http://0.0.0.0:3333" + default_sipi_url = "http://0.0.0.0:1024" + root_user_email = "root@example.com" + root_user_pw = "test" + positive_testcases: dict[str, list[str]] + negative_testcases: list[str] + + @classmethod + def setUpClass(cls) -> None: + """ + Populate the positive testcases and the negative testcases. + Is executed once before the methods of this class are run. + """ + cls.positive_testcases = { + "https://0.0.0.0:3333/": [ + cls.default_dsp_api_url, + cls.default_sipi_url, + ], + "0.0.0.0:3333": [ + cls.default_dsp_api_url, + cls.default_sipi_url, + ], + "localhost:3333": [ + cls.default_dsp_api_url, + cls.default_sipi_url, + ], + "https://admin.dasch.swiss": [ + "https://api.dasch.swiss", + "https://iiif.dasch.swiss", + ], + "https://api.dasch.swiss": [ + "https://api.dasch.swiss", + "https://iiif.dasch.swiss", + ], + "https://app.dasch.swiss": [ + "https://api.dasch.swiss", + "https://iiif.dasch.swiss", + ], + "https://iiif.dasch.swiss": [ + "https://api.dasch.swiss", + "https://iiif.dasch.swiss", + ], + "https://dasch.swiss": [ + "https://api.dasch.swiss", + "https://iiif.dasch.swiss", + ], + "dasch.swiss": [ + "https://api.dasch.swiss", + "https://iiif.dasch.swiss", + ], + "http://admin.test.dasch.swiss/": [ + "https://api.test.dasch.swiss", + "https://iiif.test.dasch.swiss", + ], + "http://app.staging.dasch.swiss/": [ + "https://api.staging.dasch.swiss", + "https://iiif.staging.dasch.swiss", + ], + "https://demo.dasch.swiss/": [ + "https://api.demo.dasch.swiss", + "https://iiif.demo.dasch.swiss", + ], + "http://api.dev.dasch.swiss/": [ + "https://api.dev.dasch.swiss", + "https://iiif.dev.dasch.swiss", + ], + "dev-02.dasch.swiss": [ + "https://api.dev-02.dasch.swiss", + "https://iiif.dev-02.dasch.swiss", + ], + "082a-test-server.dasch.swiss": [ + "https://api.082a-test-server.dasch.swiss", + "https://iiif.082a-test-server.dasch.swiss", + ], + "admin.08F4-test-server.dasch.swiss": [ + "https://api.08F4-test-server.dasch.swiss", + "https://iiif.08F4-test-server.dasch.swiss", + ], + "app.08F4-test-server.dasch.swiss": [ + "https://api.08F4-test-server.dasch.swiss", + "https://iiif.08F4-test-server.dasch.swiss", + ], + "iiif.E5bC-test-server.dasch.swiss": [ + "https://api.E5bC-test-server.dasch.swiss", + "https://iiif.E5bC-test-server.dasch.swiss", + ], + "not-yet-0826-test-server.dasch.swiss": [ + "https://api.not-yet-0826-test-server.dasch.swiss", + "https://iiif.not-yet-0826-test-server.dasch.swiss", + ], + "https://admin.ls-prod-server.dasch.swiss": [ + "https://api.ls-prod-server.dasch.swiss", + "https://iiif.ls-prod-server.dasch.swiss", + ], + "https://ls-test-server.dasch.swiss": [ + "https://api.ls-test-server.dasch.swiss", + "https://iiif.ls-test-server.dasch.swiss", + ], + } + cls.negative_testcases = [ + "https://0.0.0.0:1234", + "https://api.unkown-host.ch", + ] + + def test_derive_sipi_url_without_server(self) -> None: + """ + If the argparse.Namespace does not contain a server, + the function should return the same object. + """ + args_without_server = argparse.Namespace( + action="xmlupload", + xmlfile="data.xml", + ) + args_returned = _derive_sipi_url( + parsed_arguments=args_without_server, + default_dsp_api_url=self.default_dsp_api_url, + default_sipi_url=self.default_sipi_url, + ) + self.assertEqual(args_without_server, args_returned) + + def test_derive_sipi_url_with_server(self) -> None: + """ + If the argparse.Namespace contains a server, + the function should return a modified argparse.Namespace, + with the correct DSP URL and SIPI URL. + """ + args_with_server = argparse.Namespace( + action="xmlupload", + server="dasch.swiss", + user="some.user@dasch.swiss", + password="password", + xmlfile="data.xml", + ) + args_returned = _derive_sipi_url( + parsed_arguments=args_with_server, + default_dsp_api_url=self.default_dsp_api_url, + default_sipi_url=self.default_sipi_url, + ) + args_expected = argparse.Namespace( + action="xmlupload", + server="https://api.dasch.swiss", + sipi_url="https://iiif.dasch.swiss", + user="some.user@dasch.swiss", + password="password", + xmlfile="data.xml", + ) + self.assertEqual(args_expected, args_returned) + + def test_get_canonical_server_and_sipi_url(self) -> None: + """ + Test the method that canonicalizes the DSP URL and derives the SIPI URL from it. + """ + for api_url_orig, expected in self.positive_testcases.items(): + api_url_expected, sipi_url_expected = expected + api_url_returned, sipi_url_returned = _get_canonical_server_and_sipi_url( + server=api_url_orig, + default_dsp_api_url=self.default_dsp_api_url, + default_sipi_url=self.default_sipi_url, + ) + self.assertEqual(api_url_expected, api_url_returned) + self.assertEqual(sipi_url_expected, sipi_url_returned) + + for invalid in self.negative_testcases: + with self.assertRaisesRegex(UserError, r"Invalid DSP server URL"): + _ = _get_canonical_server_and_sipi_url( + server=invalid, + default_dsp_api_url=self.default_dsp_api_url, + default_sipi_url=self.default_sipi_url, + ) + + +if __name__ == "__main__": + pytest.main([__file__]) diff --git a/test/unittests/test_excel_to_json_lists.py b/test/unittests/test_excel_to_json_lists.py index b25f589a9..34e504800 100644 --- a/test/unittests/test_excel_to_json_lists.py +++ b/test/unittests/test_excel_to_json_lists.py @@ -21,7 +21,7 @@ class TestExcelToJSONList(unittest.TestCase): @classmethod def setUpClass(cls) -> None: - """Is executed before the methods of this class are run""" + """Is executed once before the methods of this class are run""" os.makedirs("testdata/tmp", exist_ok=True) @classmethod diff --git a/test/unittests/test_excel_to_json_properties.py b/test/unittests/test_excel_to_json_properties.py index 581fa242b..adf0520dc 100644 --- a/test/unittests/test_excel_to_json_properties.py +++ b/test/unittests/test_excel_to_json_properties.py @@ -20,7 +20,7 @@ class TestExcelToProperties(unittest.TestCase): @classmethod def setUpClass(cls) -> None: - """Is executed before the methods of this class are run""" + """Is executed once before the methods of this class are run""" os.makedirs("testdata/tmp", exist_ok=True) @classmethod diff --git a/test/unittests/test_excel_to_json_resources.py b/test/unittests/test_excel_to_json_resources.py index ca1970cd8..1910c6beb 100644 --- a/test/unittests/test_excel_to_json_resources.py +++ b/test/unittests/test_excel_to_json_resources.py @@ -20,7 +20,7 @@ class TestExcelToResource(unittest.TestCase): @classmethod def setUpClass(cls) -> None: - """Is executed before the methods of this class are run""" + """Is executed once before the methods of this class are run""" os.makedirs("testdata/tmp", exist_ok=True) @classmethod diff --git a/test/unittests/test_id_to_iri.py b/test/unittests/test_id_to_iri.py index b6f806ecf..525b9fefa 100644 --- a/test/unittests/test_id_to_iri.py +++ b/test/unittests/test_id_to_iri.py @@ -17,7 +17,7 @@ class TestIdToIri(unittest.TestCase): @classmethod def setUpClass(cls) -> None: - """Is executed before the methods of this class are run""" + """Is executed once before the methods of this class are run""" os.makedirs("testdata/tmp", exist_ok=True) @classmethod