diff --git a/.init/00_pyonepassword b/.init/00_pyonepassword index 9345f236..7cfccd66 100644 --- a/.init/00_pyonepassword +++ b/.init/00_pyonepassword @@ -10,6 +10,11 @@ then alias cdpyonepw='cd "$_pyonepasswd_src_root"; ./scripts/archive_op_binary.sh' fi +_readlink(){ readlink "$1" || echo "$1"; } +# Don't shadow the 'realpath' executable which may be installed on +# some systems (e.g., via homebrew) +_realpath() { _path="$1"; cd "$(dirname "$_path")" && _readlink "$(pwd)"/"$(basename "$_path")"; } + function _local_branches() { if [ "$PWD" != "$_pyonepasswd_src_root" ]; then @@ -27,6 +32,27 @@ function _local_branch_completion(){ _arguments "1:first:($(local_branches "$branch"))" } +_is_subdir() { + local _ret + local parent_dir sub_dir + parent_dir=$(_realpath "$1") + sub_dir=$(_realpath "$2") + if [ -d "$sub_dir" ]; + then + if [[ $sub_dir = $parent_dir/* ]]; + then + _ret=0 + else + _ret=1 + fi + else + echo "sub_dir: $sub_dir is not a directory" >&2 + _ret=1 + fi + return $_ret +} + + function deletebranch_pyop(){ local branch="$1" if [ "$PWD" != "$_pyonepasswd_src_root" ]; @@ -38,6 +64,14 @@ function deletebranch_pyop(){ } function pytest_pyop(){ + + local _popd=0 + if _is_subdir "$_pyonepasswd_src_root" "$PWD"; + then + pushd "$_pyonepasswd_src_root" + _popd=1 + fi + if [ "$PWD" != "$_pyonepasswd_src_root" ]; then echo "wrong directory to run 'pytest_pyop'" @@ -47,7 +81,16 @@ function pytest_pyop(){ # _nproc="$(nproc)" # echo "pytest: $_nproc parallel processes" # pytest -n "$_nproc" + pytest -n auto + + if [ $_popd -eq 1 ]; + then + dirs + popd + _popd=0 + fi + } function mypy_pyop(){ diff --git a/CHANGELOG.md b/CHANGELOG.md index d7a4a276..c7c66d9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ All notable changes to this project will be documented in this file. +## [4.1.0] 2023-11-28 + +### Added + +- Document editing (gh-150): + - `OP.document_edit()` + +### Documentation + +- Describe document editing in `docs/document-editing.md` +- Added set of document editing examples under `examples/document_editing` + +### Misc + +Substantial reorganization of `tests/` + ## [4.0.0] 2023-11-15 ### Fixed diff --git a/README.md b/README.md index 73892c24..e3644b2c 100644 --- a/README.md +++ b/README.md @@ -352,6 +352,12 @@ For details on editing existing items in a 1Password vault, see [item-editing.md Also see the examles in [examples/item_editing](examples/item_editing/) +### Document Editing + +For details on editing existing document item file contents, see [document-editing.md](docs/document-editing.md) + +See examples in [examples/document_editing](examples/document_editing.py) + ### More Examples Lots more examples are available in the `examples` directory diff --git a/docs/document-editing.md b/docs/document-editing.md new file mode 100644 index 00000000..275d5e53 --- /dev/null +++ b/docs/document-editing.md @@ -0,0 +1,26 @@ +# PYONEPASSWORD DOCUMENT EDITING + +## Description +As of version 4.1.0, `pyonepassword` supports in-place document editing. There is API to match the operations supported by the `op document edit` command. + +The API for document editing is the `OP.docuemnt_edit()` method. This method replaces the bytes of an existing document item with the contents of a new file. + + +## Use and arguments +The `document_edit()` method takes two mandatory arguments: + +- `document_identitifer`: A string representing the title or unique ID of a document item +- `file_path_or_document_bytes`: This is what the document should be replaced with. It may be either: + - A `str` or `Path` object referencing existing file on disk to read + - A `bytes` object that is the new file's contents + +Additionally, you may *also* change the document item's: + + - filename via the `file_name=` kwarg + - title via the `new_title=` kwarg + +**Note**: You may not set a new filename or document item title via document_edit() without also specifying a path or bytes to set the document's contents to. This is not supported by `op document edit`. If this behavior is required, the equivalent would be to provide the original document's contents + +### Return value + +If successful, the `document_edit()` function returns a string representing the unique ID of the document item edited. This may be useful for confirming the expected document item is the one that was edited, in the event a document title was provided. diff --git a/examples/document_editing.py b/examples/document_editing.py new file mode 100644 index 00000000..7eececf7 --- /dev/null +++ b/examples/document_editing.py @@ -0,0 +1,38 @@ +from pathlib import Path + +from pyonepassword import OP + + +def replace_document(): + op = OP() + document_title = "example document 01" + replacement_file_path = "path/to/replacement_image_01.png" + + op.document_edit(document_title, replacement_file_path) + + # or replacmenet document can be bytes instead of str/Path: + replacement_bytes = open(replacement_file_path, "rb").read() + + op.document_edit(document_title, replacement_bytes) + + +def replace_document_set_title(): + op = OP() + document_title = "example document 01" + replacement_file_path = "path/to/replacement_image_01.png" + new_document_title = "updated example document 01" + op.document_edit(document_title, replacement_file_path, + new_title=new_document_title) + + +def replace_document_set_filename(): + op = OP() + document_title = "example document 01" + + # replacement path may be Path or str + replacement_file_path = Path("path/to/replacement_image_01.png") + + # get basename: "replacement_image_01.png" + new_document_filename = replacement_file_path.name + op.document_edit(document_title, replacement_file_path, + file_name=new_document_filename) diff --git a/ipython_snippets/_util/functions.py b/ipython_snippets/_util/functions.py index 0f2003d9..0a8ff6df 100644 --- a/ipython_snippets/_util/functions.py +++ b/ipython_snippets/_util/functions.py @@ -1,5 +1,8 @@ import pathlib +from pyonepassword import OP +from pyonepassword.logging import console_debug_logger + def snippet_dir(): main_file = pathlib.Path(__file__) @@ -12,3 +15,9 @@ def scratch_dir(): scratch = pathlib.Path(main_dir, "scratch") scratch.mkdir(exist_ok=True) return scratch + + +def get_op(logger_name): + logger = console_debug_logger(logger_name) + op = OP(logger=logger) + return op diff --git a/ipython_snippets/basic.py b/ipython_snippets/basic.py index dcab77cd..dea43f7c 100644 --- a/ipython_snippets/basic.py +++ b/ipython_snippets/basic.py @@ -2,7 +2,7 @@ from pyonepassword import OP, logging from pyonepassword._op_cli_config import OPCLIAccountConfig, OPCLIConfig -from pyonepassword._py_op_commands import ( # noqa: F401 +from pyonepassword._op_commands import ( # noqa: F401 EXISTING_AUTH_AVAIL, EXISTING_AUTH_IGNORE, EXISTING_AUTH_REQD diff --git a/ipython_snippets/document_edit.py b/ipython_snippets/document_edit.py new file mode 100644 index 00000000..99f858b5 --- /dev/null +++ b/ipython_snippets/document_edit.py @@ -0,0 +1,12 @@ +from _util.functions import get_op +from dotenv import load_dotenv + +from pyonepassword.api.exceptions import OPCmdFailedException # noqa: F401 + +load_dotenv("./dot_env_files/.env_pyonepassword_test_rw") + +print("run: op = get_op(\"document-edit\")") + +vault = "Test Data 2" +document_name = "example document 10" +new_file_path = "working_data/images/replacement_image_10.png" diff --git a/ipython_snippets/pytest/misc.py b/ipython_snippets/pytest/misc.py index 235015d4..a6ac891e 100644 --- a/ipython_snippets/pytest/misc.py +++ b/ipython_snippets/pytest/misc.py @@ -2,7 +2,7 @@ from pyonepassword import OP, logging from pyonepassword._op_cli_config import OPCLIConfig -from pyonepassword._py_op_commands import EXISTING_AUTH_REQD +from pyonepassword._op_commands import EXISTING_AUTH_REQD from tests.fixtures.op_fixtures import _setup_alt_env, _setup_normal_env from tests.fixtures.valid_op_cli_config import ( VALID_OP_CONFIG_NO_SHORTHAND_KEY, diff --git a/ipython_snippets/signin.py b/ipython_snippets/signin.py index 0b6f916c..c2429993 100644 --- a/ipython_snippets/signin.py +++ b/ipython_snippets/signin.py @@ -2,7 +2,7 @@ from pyonepassword import OP, logging from pyonepassword._op_cli_config import OPCLIAccountConfig, OPCLIConfig -from pyonepassword._py_op_commands import ( # noqa: F401 +from pyonepassword._op_commands import ( # noqa: F401 EXISTING_AUTH_AVAIL, EXISTING_AUTH_IGNORE, EXISTING_AUTH_REQD diff --git a/item-editing-todo.md b/item-editing-todo.md deleted file mode 100644 index 9dafca28..00000000 --- a/item-editing-todo.md +++ /dev/null @@ -1,31 +0,0 @@ - -github issue: [143](https://github.com/zcutlip/pyonepassword/issues/143) - -- [x] `OP.item_edit_generate_password()` - - [x] implemented - - [x] tested -- [x] `OP.item_edit_set_password()` - - [x] implemented - - [x] tested -- [x] `OP.item_edit_set_title()` - - [x] implemented - - [x] tested -- [x] `OP.item_edit_set_favorite()` - - [x] implemented - - [x] tested -- [x] `OP.item_edit_set_tags()` - - [x] implemented - - [x] tested -- [x] `OP.item_edit_set_url()` - - [x] implemented - - [x] tested -- [x] Set field types: - - [x] password - - [x] implemented - - [x] tested - - [ ] text - - [ ] implemented - - [ ] tested - - [ ] url - - [ ] implemented - - [ ] tested diff --git a/pyonepassword/__about__.py b/pyonepassword/__about__.py index c866395a..92db4692 100644 --- a/pyonepassword/__about__.py +++ b/pyonepassword/__about__.py @@ -1,5 +1,5 @@ __title__ = "pyonepassword" -__version__ = "4.0.1" +__version__ = "4.1.0" __summary__ = "A python API to query a 1Password account using the 'op' command-line tool" """ diff --git a/pyonepassword/_py_op_cli.py b/pyonepassword/_op_cli.py similarity index 88% rename from pyonepassword/_py_op_cli.py rename to pyonepassword/_op_cli.py index ed33ad05..d378fb56 100644 --- a/pyonepassword/_py_op_cli.py +++ b/pyonepassword/_op_cli.py @@ -44,14 +44,14 @@ def _should_log_op_errors(cls) -> bool: return should_log @classmethod - def _run_raw(cls, argv, input_string=None, capture_stdout=False, ignore_error=False, env=environ): + def _run_raw(cls, argv, input=None, capture_stdout=False, ignore_error=False, env=environ): stdout = subprocess.PIPE if capture_stdout else None - if input_string: - if isinstance(input_string, str): - input_string = input_string.encode("utf-8") + if input: + if isinstance(input, str): + input = input.encode("utf-8") _ran = subprocess.run( - argv, input=input_string, stderr=subprocess.PIPE, stdout=stdout, env=env) + argv, input=input, stderr=subprocess.PIPE, stdout=stdout, env=env) stdout = _ran.stdout stderr = _ran.stderr @@ -90,12 +90,12 @@ def _run_raw(cls, argv, input_string=None, capture_stdout=False, ignore_error=Fa return (stdout, stderr, returncode) @classmethod - def _run(cls, argv, capture_stdout=False, input_string=None, decode=None, env=environ): + def _run(cls, argv, capture_stdout=False, input=None, decode=None, env=environ): cls.logger.debug(f"Running: {argv.cmd_str()}") output = None try: output, _, _ = cls._run_raw( - argv, input_string=input_string, capture_stdout=capture_stdout, env=env) + argv, input=input, capture_stdout=capture_stdout, env=env) if decode and output is not None: output = output.decode(decode) except FileNotFoundError as err: diff --git a/pyonepassword/_op_cli_argv.py b/pyonepassword/_op_cli_argv.py index 033ef934..6f9016f7 100644 --- a/pyonepassword/_op_cli_argv.py +++ b/pyonepassword/_op_cli_argv.py @@ -77,6 +77,52 @@ def svc_account_supported(self) -> OPSvcAcctSupportCode: supported = reg.command_supported(self) return supported + @classmethod + def document_get_argv(cls, op_exe, document_name_or_id, vault=None, include_archive=False): + + sub_cmd_args = [document_name_or_id] + if vault: + sub_cmd_args.extend(["--vault", vault]) + if include_archive: + sub_cmd_args.append("--include-archive") + argv = cls._document_generic_argv(op_exe, "get", sub_cmd_args) + return argv + + @classmethod + def document_edit_argv(cls, + op_exe, + document_identifier, + file_name=None, + new_title=None, + vault=None): + + sub_cmd_args = [document_identifier] + + if file_name: + sub_cmd_args.extend(["--file-name", file_name]) + if new_title: + sub_cmd_args.extend(["--title", new_title]) + if vault: + sub_cmd_args.extend(["--vault", vault]) + argv = cls._document_generic_argv(op_exe, "edit", sub_cmd_args) + return argv + + @classmethod + def document_delete_argv(cls, + op_exe: str, + document_name_or_id: str, + vault: Optional[str] = None, + archive: bool = False): + sub_cmd_args = [document_name_or_id] + if archive: + sub_cmd_args.append("--archive") + if vault: + sub_cmd_args.extend(["--vault", vault]) + delete_argv = cls._document_generic_argv( + op_exe, "delete", sub_cmd_args) + + return delete_argv + @classmethod def item_generic_argv(cls, op_exe: str, @@ -119,33 +165,6 @@ def item_get_totp_argv(cls, op_exe, item_name_or_id, vault=None): op_exe, item_name_or_id, vault=vault, fields=field_arg) return argv - @classmethod - def document_generic_argv(cls, - op_exe: str, - subcommands: Optional[Union[str, List[str]]], - sub_cmd_args: Optional[List[str]]): - args = [] - global_args = ["--format", "json"] - if sub_cmd_args: - args.extend(sub_cmd_args) - argv = cls(op_exe, - "document", - args, - subcommands=subcommands, - global_args=global_args) - return argv - - @classmethod - def document_get_argv(cls, op_exe, document_name_or_id, vault=None, include_archive=False): - - sub_cmd_args = [document_name_or_id] - if vault: - sub_cmd_args.extend(["--vault", vault]) - if include_archive: - sub_cmd_args.append("--include-archive") - argv = cls.document_generic_argv(op_exe, "get", sub_cmd_args) - return argv - @classmethod def vault_generic_argv(cls, op_exe: str, @@ -493,16 +512,17 @@ def item_delete_argv(cls, return delete_argv @classmethod - def document_delete_argv(cls, - op_exe: str, - document_name_or_id: str, - vault: Optional[str] = None, - archive: bool = False): - sub_cmd_args = [document_name_or_id] - if archive: - sub_cmd_args.append("--archive") - if vault: - sub_cmd_args.extend(["--vault", vault]) - delete_argv = cls.document_generic_argv(op_exe, "delete", sub_cmd_args) - - return delete_argv + def _document_generic_argv(cls, + op_exe: str, + subcommands: Optional[Union[str, List[str]]], + sub_cmd_args: Optional[List[str]]): + args = [] + global_args = ["--format", "json"] + if sub_cmd_args: + args.extend(sub_cmd_args) + argv = cls(op_exe, + "document", + args, + subcommands=subcommands, + global_args=global_args) + return argv diff --git a/pyonepassword/_py_op_commands.py b/pyonepassword/_op_commands.py similarity index 95% rename from pyonepassword/_py_op_commands.py rename to pyonepassword/_op_commands.py index 587f4442..dda407f1 100644 --- a/pyonepassword/_py_op_commands.py +++ b/pyonepassword/_op_commands.py @@ -11,9 +11,9 @@ if TYPE_CHECKING: # pragma: no coverage from pyonepassword._field_assignment import OPFieldTypeEnum +from ._op_cli import _OPCLIExecute from ._op_cli_argv import _OPArgv from ._op_cli_config import OPCLIConfig -from ._py_op_cli import _OPCLIExecute from ._svc_account import ( SVC_ACCT_CMD_NOT_SUPPORTED, SVC_ACCT_INCOMPAT_OPTIONS, @@ -32,6 +32,7 @@ OPCmdFailedException, OPCmdMalformedSvcAcctTokenException, OPDocumentDeleteException, + OPDocumentEditException, OPDocumentGetException, OPGroupGetException, OPGroupListException, @@ -372,7 +373,7 @@ def _get_existing_token(self, account: OPAccount): def _run_signin(self, argv, password=None): try: output = self._run(argv, capture_stdout=True, - input_string=password, decode="utf-8") + input=password, decode="utf-8") except OPCmdFailedException as ocfe: raise OPSigninException.from_opexception(ocfe) from ocfe @@ -384,7 +385,7 @@ def _run_with_auth_check(cls, account: str, argv: _OPArgv, capture_stdout: bool = False, - input_string: str = None, + input: Union[str, bytes] = None, decode: str = None, env: Mapping = environ): # this somewhat of a hack to detect if authentication has expired @@ -418,87 +419,10 @@ def _run_with_auth_check(cls, return cls._run(argv, capture_stdout=capture_stdout, - input_string=input_string, + input=input, decode=decode, env=env) - @classmethod - def _account_list_argv(cls, op_path="op", encoding="utf-8"): - argv = _OPArgv.account_list_argv(op_path, encoding=encoding) - return argv - - def _item_get_argv(self, item_name_or_id, vault=None, fields=None, include_archive=False): - vault_arg = vault if vault else self.vault - - lookup_argv = _OPArgv.item_get_argv( - self.op_path, item_name_or_id, vault=vault_arg, fields=fields, include_archive=include_archive) - return lookup_argv - - def _item_delete_argv(self, item_name_or_id, vault=None, archive=False): - vault_arg = vault if vault else self.vault - - delete_argv = _OPArgv.item_delete_argv( - self.op_path, item_name_or_id, vault=vault_arg, archive=archive) - return delete_argv - - def _item_get_totp_argv(self, item_name_or_id, vault=None): - vault_arg = vault if vault else self.vault - - lookup_argv = _OPArgv.item_get_totp_argv( - self.op_path, item_name_or_id, vault=vault_arg) - return lookup_argv - - def _document_get_argv(self, - document_name_or_id: str, - vault: Optional[str] = None, - include_archive: Optional[bool] = False): - vault_arg = vault if vault else self.vault - document_get_argv = _OPArgv.document_get_argv(self.op_path, - document_name_or_id, - vault=vault_arg, - include_archive=include_archive) - print(document_get_argv) - - return document_get_argv - - def _document_delete_argv(self, document_name_or_id: str, vault: Optional[str] = None, archive=False): - vault_arg = vault if vault else self.vault - - document_delete_argv = _OPArgv.document_delete_argv( - self.op_path, document_name_or_id, vault=vault_arg, archive=archive) - - return document_delete_argv - - def _user_get_argv(self, user_name_or_id: str): - get_user_argv = _OPArgv.user_get_argv(self.op_path, user_name_or_id) - return get_user_argv - - def _user_list_argv(self, group_name_or_id=None, vault=None): - user_list_argv = _OPArgv.user_list_argv( - self.op_path, group_name_or_id=group_name_or_id, vault=vault) - return user_list_argv - - def _group_get_argv(self, group_name_or_id: str): - group_get_argv = _OPArgv.group_get_argv( - self.op_path, group_name_or_id) - return group_get_argv - - def _group_list_argv(self, user_name_or_id=None, vault=None): - group_list_argv = _OPArgv.group_list_argv( - self.op_path, user_name_or_id=user_name_or_id, vault=vault) - return group_list_argv - - def _vault_get_argv(self, vault_name_or_id: str): - - get_vault_argv = _OPArgv.vault_get_argv( - self.op_path, vault_name_or_id) - return get_vault_argv - - def _vault_list_argv(self, group_name_or_id=None, user_name_or_id=None): - vault_list_argv = _OPArgv.vault_list_argv( - self.op_path, group_name_or_id=group_name_or_id, user_name_or_id=user_name_or_id) - return vault_list_argv - @classmethod def _item_template_list_special(cls, op_path, env: Dict[str, str] = None): if not env: @@ -572,57 +496,6 @@ def _whoami(cls, op_path, env: Dict[str, str] = None, account: str = None) -> OP account_obj = OPAccount(account_json) return account_obj - def _item_get(self, item_name_or_id, vault=None, fields=None, include_archive=False, decode="utf-8"): - item_get_argv = self._item_get_argv( - item_name_or_id, vault=vault, fields=fields, include_archive=include_archive) - try: - output = self._run_with_auth_check( - self.op_path, self._account_identifier, item_get_argv, capture_stdout=True, decode=decode) - except OPCmdFailedException as ocfe: - raise OPItemGetException.from_opexception(ocfe) from ocfe - - return output - - def _item_delete(self, item_name_or_id, vault=None, archive=False, decode="utf-8"): - item_delete_argv = self._item_delete_argv( - item_name_or_id, vault=vault, archive=archive) - try: - # 'op item delete' doesn't have any output if successful - # if it fails, stderr will be in the exception object - self._run_with_auth_check( - self.op_path, self._account_identifier, item_delete_argv, decode=decode) - except OPCmdFailedException as ocfe: - raise OPItemDeleteException.from_opexception(ocfe) - - return - - def _item_delete_multiple(self, batch_json, vault, archive=False): - # op item delete takes '-' for the item to delete if objects are - # provided over stdin - item_id = "-" - item_delete_argv = self._item_delete_argv( - item_id, vault=vault, archive=archive) - try: - # 'op item delete' doesn't have any output if successful - # if it fails, stderr will be in the exception object - self._run_with_auth_check( - self.op_path, self._account_identifier, item_delete_argv, input_string=batch_json) - except OPCmdFailedException as ocfe: - raise OPItemDeleteException.from_opexception(ocfe) - - return - - def _item_get_totp(self, item_name_or_id, vault=None, decode="utf-8"): - item_get_totp_argv = self._item_get_totp_argv( - item_name_or_id, vault=vault) - try: - output = self._run_with_auth_check(self.op_path, self._account_identifier, - item_get_totp_argv, capture_stdout=True, decode=decode) - except OPCmdFailedException as ocfe: - raise OPItemGetException.from_opexception(ocfe) from ocfe - - return output - def _document_get(self, document_name_or_id: str, vault: Optional[str] = None, @@ -659,6 +532,25 @@ def _document_get(self, return document_bytes + def _document_edit(self, + document_identifier: str, + document_bytes: bytes, + file_name: Optional[str] = None, + new_title: Optional[str] = None, + vault: Optional[str] = None): + + document_edit_argv = self._document_edit_argv( + document_identifier, file_name=file_name, new_title=new_title, vault=vault) + try: + # 'op document edit' doesn't have any output if successful + # if it fails, stderr will be in the exception object + self._run_with_auth_check( + self.op_path, self._account_identifier, document_edit_argv, input=document_bytes) + except OPCmdFailedException as ocfe: + raise OPDocumentEditException.from_opexception(ocfe) + + return + def _document_delete(self, document_name_or_id: str, vault: Optional[str] = None, archive=False): document_delete_argv = self._document_delete_argv( @@ -673,6 +565,60 @@ def _document_delete(self, document_name_or_id: str, vault: Optional[str] = None return + def _item_get(self, item_name_or_id, vault=None, fields=None, include_archive=False, decode="utf-8"): + item_get_argv = self._item_get_argv( + item_name_or_id, vault=vault, fields=fields, include_archive=include_archive) + try: + output = self._run_with_auth_check( + self.op_path, self._account_identifier, item_get_argv, capture_stdout=True, decode=decode) + except OPCmdFailedException as ocfe: + raise OPItemGetException.from_opexception(ocfe) from ocfe + + return output + + def _item_delete(self, item_name_or_id, vault=None, archive=False, decode="utf-8"): + item_delete_argv = self._item_delete_argv( + item_name_or_id, vault=vault, archive=archive) + try: + # 'op item delete' doesn't have any output if successful + # if it fails, stderr will be in the exception object + self._run_with_auth_check( + self.op_path, self._account_identifier, item_delete_argv, decode=decode) + except OPCmdFailedException as ocfe: + raise OPItemDeleteException.from_opexception(ocfe) + + return + + def _item_delete_multiple(self, batch_json, vault, archive=False): + # op item delete takes '-' for the item to delete if objects are + # provided over stdin + item_id = "-" + item_delete_argv = self._item_delete_argv( + item_id, vault=vault, archive=archive) + try: + # 'op item delete' doesn't have any output if successful + # if it fails, stderr will be in the exception object + self._run_with_auth_check( + self.op_path, self._account_identifier, item_delete_argv, input=batch_json) + except OPCmdFailedException as ocfe: + # OPItemDeleteException will get turned into + # OPItemDeleteMultipleException by the caller, so + # any sucessfully deleted items can be included in the exception object + raise OPItemDeleteException.from_opexception(ocfe) + + return + + def _item_get_totp(self, item_name_or_id, vault=None, decode="utf-8"): + item_get_totp_argv = self._item_get_totp_argv( + item_name_or_id, vault=vault) + try: + output = self._run_with_auth_check(self.op_path, self._account_identifier, + item_get_totp_argv, capture_stdout=True, decode=decode) + except OPCmdFailedException as ocfe: + raise OPItemGetException.from_opexception(ocfe) from ocfe + + return output + @classmethod def _signed_in_accounts(cls, op_path, decode="utf-8"): account_list_argv = cls._account_list_argv(op_path, encoding=decode) @@ -752,20 +698,6 @@ def _account_forget(cls, account: str, op_path=None): # pragma: no cover argv = _OPArgv.account_forget_argv(op_path, account) cls._run(argv) - def _item_list_argv(self, categories=[], include_archive=False, tags=[], vault=None): - # default lists to the categories & list kwargs - # get initialized at module load - # so its the same list object on every call to this funciton - # This really isn't what we want, so the easiest - # mitigation is to just make a copy of whatever list was passed in - # or of the default kwarg if nothing was passed in - categories = list(categories) - tags = list(tags) - vault_arg = vault if vault else self.vault - list_items_argv = _OPArgv.item_list_argv(self.op_path, - categories=categories, include_archive=include_archive, tags=tags, vault=vault_arg) - return list_items_argv - def _item_list(self, categories=[], include_archive=False, tags=[], vault=None, decode="utf-8"): # default lists to the categories & list kwargs # get initialized at module load @@ -784,91 +716,6 @@ def _item_list(self, categories=[], include_archive=False, tags=[], vault=None, raise OPItemListException.from_opexception(e) return output - def _item_create_argv(self, item, password_recipe, vault): - vault_arg = vault if vault else self.vault - item_create_argv = _OPArgv.item_create_argv( - self.op_path, item, password_recipe=password_recipe, vault=vault_arg - ) - return item_create_argv - - def _item_edit_set_field_value_argv(self, - item_identifier: str, - field_type: OPFieldTypeEnum, - value: str, - field_label: str, - section_label: Optional[str], - vault: Optional[str]): - vault_arg = vault if vault else self.vault - item_edit_argv = _OPArgv.item_edit_set_field_value(self.op_path, - item_identifier, - field_type, - value, - field_label=field_label, - section_label=section_label, - vault=vault_arg) - return item_edit_argv - - def _item_edit_favorite_argv(self, - item_identifier: str, - favorite: bool, - vault: Optional[str]): - vault_arg = vault if vault else self.vault - - item_edit_argv = _OPArgv.item_edit_favorite(self.op_path, - item_identifier, - favorite, - vault=vault_arg) - return item_edit_argv - - def _item_edit_generate_password_argv(self, - item_identifier: str, - password_recipe: OPPasswordRecipe, - vault: Optional[str]): - - vault_arg = vault if vault else self.vault - item_edit_argv = _OPArgv.item_edit_generate_password_argv(self.op_path, - item_identifier, - password_recipe, - vault=vault_arg) - return item_edit_argv - - def _item_edit_tags_argv(self, - item_identifier: str, - tags: List[str], - vault: Optional[str]): - vault_arg = vault if vault else self.vault - - item_edit_argv = _OPArgv.item_edit_tags(self.op_path, - item_identifier, - tags, - vault=vault_arg) - - return item_edit_argv - - def _item_edit_title_argv(self, - item_identifier: str, - item_title: str, - vault: Optional[str]): - vault_arg = vault if vault else self.vault - - item_edit_argv = _OPArgv.item_edit_title(self.op_path, - item_identifier, - item_title, - vault=vault_arg) - return item_edit_argv - - def _item_edit_url_argv(self, - item_identifier: str, - url: str, - vault: Optional[str]): - vault_arg = vault if vault else self.vault - - item_edit_argv = _OPArgv.item_edit_url(self.op_path, - item_identifier, - url, - vault=vault_arg) - return item_edit_argv - def _item_create(self, item, vault, password_recipe, decode="utf-8"): argv = self._item_create_argv(item, password_recipe, vault) try: @@ -959,3 +806,192 @@ def _item_edit_url(self, output = self._item_edit_run(argv, decode) return output + + @classmethod + def _account_list_argv(cls, op_path="op", encoding="utf-8"): + argv = _OPArgv.account_list_argv(op_path, encoding=encoding) + return argv + + def _document_get_argv(self, + document_name_or_id: str, + vault: Optional[str] = None, + include_archive: Optional[bool] = False): + vault_arg = vault if vault else self.vault + document_get_argv = _OPArgv.document_get_argv(self.op_path, + document_name_or_id, + vault=vault_arg, + include_archive=include_archive) + + return document_get_argv + + def _document_edit_argv(self, + document_identifier: str, + file_name: Optional[str] = None, + new_title: Optional[str] = None, + vault: Optional[str] = None): + vault_arg = vault if vault else self.vault + document_edit_argv = _OPArgv.document_edit_argv(self.op_path, + document_identifier, + file_name=file_name, + new_title=new_title, + vault=vault_arg) + + return document_edit_argv + + def _document_delete_argv(self, document_name_or_id: str, vault: Optional[str] = None, archive=False): + vault_arg = vault if vault else self.vault + + document_delete_argv = _OPArgv.document_delete_argv( + self.op_path, document_name_or_id, vault=vault_arg, archive=archive) + + return document_delete_argv + + def _item_get_argv(self, item_name_or_id, vault=None, fields=None, include_archive=False): + vault_arg = vault if vault else self.vault + + lookup_argv = _OPArgv.item_get_argv( + self.op_path, item_name_or_id, vault=vault_arg, fields=fields, include_archive=include_archive) + return lookup_argv + + def _item_delete_argv(self, item_name_or_id, vault=None, archive=False): + vault_arg = vault if vault else self.vault + + delete_argv = _OPArgv.item_delete_argv( + self.op_path, item_name_or_id, vault=vault_arg, archive=archive) + return delete_argv + + def _item_get_totp_argv(self, item_name_or_id, vault=None): + vault_arg = vault if vault else self.vault + + lookup_argv = _OPArgv.item_get_totp_argv( + self.op_path, item_name_or_id, vault=vault_arg) + return lookup_argv + + def _user_get_argv(self, user_name_or_id: str): + get_user_argv = _OPArgv.user_get_argv(self.op_path, user_name_or_id) + return get_user_argv + + def _user_list_argv(self, group_name_or_id=None, vault=None): + user_list_argv = _OPArgv.user_list_argv( + self.op_path, group_name_or_id=group_name_or_id, vault=vault) + return user_list_argv + + def _group_get_argv(self, group_name_or_id: str): + group_get_argv = _OPArgv.group_get_argv( + self.op_path, group_name_or_id) + return group_get_argv + + def _group_list_argv(self, user_name_or_id=None, vault=None): + group_list_argv = _OPArgv.group_list_argv( + self.op_path, user_name_or_id=user_name_or_id, vault=vault) + return group_list_argv + + def _vault_get_argv(self, vault_name_or_id: str): + + get_vault_argv = _OPArgv.vault_get_argv( + self.op_path, vault_name_or_id) + return get_vault_argv + + def _vault_list_argv(self, group_name_or_id=None, user_name_or_id=None): + vault_list_argv = _OPArgv.vault_list_argv( + self.op_path, group_name_or_id=group_name_or_id, user_name_or_id=user_name_or_id) + return vault_list_argv + + def _item_create_argv(self, item, password_recipe, vault): + vault_arg = vault if vault else self.vault + item_create_argv = _OPArgv.item_create_argv( + self.op_path, item, password_recipe=password_recipe, vault=vault_arg + ) + return item_create_argv + + def _item_edit_set_field_value_argv(self, + item_identifier: str, + field_type: OPFieldTypeEnum, + value: str, + field_label: str, + section_label: Optional[str], + vault: Optional[str]): + vault_arg = vault if vault else self.vault + item_edit_argv = _OPArgv.item_edit_set_field_value(self.op_path, + item_identifier, + field_type, + value, + field_label=field_label, + section_label=section_label, + vault=vault_arg) + return item_edit_argv + + def _item_edit_favorite_argv(self, + item_identifier: str, + favorite: bool, + vault: Optional[str]): + vault_arg = vault if vault else self.vault + + item_edit_argv = _OPArgv.item_edit_favorite(self.op_path, + item_identifier, + favorite, + vault=vault_arg) + return item_edit_argv + + def _item_edit_generate_password_argv(self, + item_identifier: str, + password_recipe: OPPasswordRecipe, + vault: Optional[str]): + + vault_arg = vault if vault else self.vault + item_edit_argv = _OPArgv.item_edit_generate_password_argv(self.op_path, + item_identifier, + password_recipe, + vault=vault_arg) + return item_edit_argv + + def _item_edit_tags_argv(self, + item_identifier: str, + tags: List[str], + vault: Optional[str]): + vault_arg = vault if vault else self.vault + + item_edit_argv = _OPArgv.item_edit_tags(self.op_path, + item_identifier, + tags, + vault=vault_arg) + + return item_edit_argv + + def _item_edit_title_argv(self, + item_identifier: str, + item_title: str, + vault: Optional[str]): + vault_arg = vault if vault else self.vault + + item_edit_argv = _OPArgv.item_edit_title(self.op_path, + item_identifier, + item_title, + vault=vault_arg) + return item_edit_argv + + def _item_edit_url_argv(self, + item_identifier: str, + url: str, + vault: Optional[str]): + vault_arg = vault if vault else self.vault + + item_edit_argv = _OPArgv.item_edit_url(self.op_path, + item_identifier, + url, + vault=vault_arg) + return item_edit_argv + + def _item_list_argv(self, categories=[], include_archive=False, tags=[], vault=None): + # default lists to the categories & list kwargs + # get initialized at module load + # so its the same list object on every call to this funciton + # This really isn't what we want, so the easiest + # mitigation is to just make a copy of whatever list was passed in + # or of the default kwarg if nothing was passed in + categories = list(categories) + tags = list(tags) + vault_arg = vault if vault else self.vault + list_items_argv = _OPArgv.item_list_argv(self.op_path, + categories=categories, include_archive=include_archive, tags=tags, vault=vault_arg) + return list_items_argv diff --git a/pyonepassword/api/authentication.py b/pyonepassword/api/authentication.py index a44bdbdd..f3eefc90 100644 --- a/pyonepassword/api/authentication.py +++ b/pyonepassword/api/authentication.py @@ -1,4 +1,4 @@ -from .._py_op_commands import ( +from .._op_commands import ( EXISTING_AUTH_AVAIL, EXISTING_AUTH_IGNORE, EXISTING_AUTH_REQD, diff --git a/pyonepassword/api/exceptions.py b/pyonepassword/api/exceptions.py index 9ac2fecc..cd8fd612 100644 --- a/pyonepassword/api/exceptions.py +++ b/pyonepassword/api/exceptions.py @@ -29,6 +29,7 @@ OPCmdMalformedSvcAcctTokenException, OPConfigNotFoundException, OPDocumentDeleteException, + OPDocumentEditException, OPDocumentGetException, OPForgetException, OPGroupGetException, @@ -61,6 +62,7 @@ "OPConfigNotFoundException", "OPDocumentDeleteException", "OPDocumentGetException", + "OPDocumentEditException", "OPFieldNotFoundException", "OPForgetException", "OPGroupGetException", diff --git a/pyonepassword/data/svc_acct_commands/document.json b/pyonepassword/data/svc_acct_commands/document.json index f022c0e2..11ecf05f 100644 --- a/pyonepassword/data/svc_acct_commands/document.json +++ b/pyonepassword/data/svc_acct_commands/document.json @@ -17,6 +17,13 @@ "--vault" ], "prohibited_options": [] + }, + "edit": { + "has_arg": true, + "required_options": [ + "--vault" + ], + "prohibited_options": [] } } } diff --git a/pyonepassword/py_op_exceptions.py b/pyonepassword/py_op_exceptions.py index 60e3e1ef..2f6be0e3 100644 --- a/pyonepassword/py_op_exceptions.py +++ b/pyonepassword/py_op_exceptions.py @@ -161,6 +161,13 @@ def __init__(self, stderr_out, returncode): super().__init__(stderr_out, returncode) +class OPDocumentEditException(OPCmdFailedException): + MSG = "1Password 'document edit' failed." + + def __init__(self, stderr_out, returncode): + super().__init__(stderr_out, returncode) + + class OPUserGetException(OPCmdFailedException): MSG = "1Password 'get user' failed." diff --git a/pyonepassword/pyonepassword.py b/pyonepassword/pyonepassword.py index 07be3e47..3c598a1d 100644 --- a/pyonepassword/pyonepassword.py +++ b/pyonepassword/pyonepassword.py @@ -3,13 +3,14 @@ import fnmatch import logging from os import environ as env +from pathlib import Path from typing import TYPE_CHECKING, List, Optional, Type, Union if TYPE_CHECKING: # pragma: no coverage from .op_items.fields_sections.item_field import OPItemField from ._field_assignment import OPFieldTypeEnum -from ._py_op_commands import ( +from ._op_commands import ( EXISTING_AUTH_IGNORE, ExistingAuthEnum, _OPCommandInterface @@ -45,6 +46,7 @@ from .py_op_exceptions import ( OPCmdFailedException, OPDocumentDeleteException, + OPDocumentEditException, OPDocumentGetException, OPFieldExistsException, OPForgetException, @@ -172,6 +174,81 @@ def document_get(self, document_name_or_id, vault=None, include_archive=False, r return (file_name, document_bytes) + def document_edit(self, + document_identifier: str, + file_path_or_document_bytes: Union[str, Path, bytes], + file_name: Optional[str] = None, + new_title: Optional[str] = None, + vault: Optional[str] = None, + relaxed_validation: bool = False) -> str: + """ + Edit a document object based on document name or unique identifier + + Parameters + ---------- + document_identifier : str + Name or identifier of the document to edit + file_path_or_document_bytes: Union[str, Path, bytes], + Either the path to the file to replace the current document with, + or the actual bytes representation of the replacement document + file_name: str, optional + Optionally set the document's fileName attribute to this value + new_title: str, optional + Optionally update the title of the document to this value + vault : str, optional + The name or ID of a vault to override the default vault, by default None + relaxed_validation: bool, optional + Whether to enable relaxed item validation for this query, in order to parse non-conformant data + by default False + Returns + ------- + document_id: str + Unique identifier of the item edited + + Raises + ------ + OPDocumentEditException + - If the document to be edit is not found + - If there is more than one item matching 'document_identifier' + - If the edit operation fails for any other reason + + Service Account Support + ----------------------- + Supported + required keyword arguments: vault + """ + + if isinstance(file_path_or_document_bytes, bytes): + document_bytes = file_path_or_document_bytes + else: + file_path_or_document_bytes = Path(file_path_or_document_bytes) + document_bytes = file_path_or_document_bytes.read_bytes() + + # to satisfy mypy + generic_item_class: Type[_OPGenericItem] + if relaxed_validation: + generic_item_class = _OPGenericItemRelaxedValidation + else: + generic_item_class = _OPGenericItem + + try: + output = self._item_get(document_identifier, vault=vault) + item = generic_item_class(output) + except OPItemGetException as e: + raise OPDocumentEditException.from_opexception(e) + # we want to return the explicit ID even if we were + # given an document name or other identifier + # that way the caller knows exactly what got edited + # can match it up with what they expected to be edited, if desired + document_id = item.unique_id + + # 'op document edit' doesn't have any stdout, so we're not + # capturing any here + self._document_edit(document_id, document_bytes, + file_name=file_name, new_title=new_title, vault=vault) + + return document_id + def document_delete(self, document_identifier: str, vault: Optional[str] = None, archive: bool = False, relaxed_validation: bool = False) -> str: """ Delete a document object based on document name or unique identifier @@ -1466,6 +1543,9 @@ def item_delete_multiple(self, try: self._item_delete_multiple(batch_json, vault, archive=archive) except OPCmdFailedException as ope: # pragma: no coverage + # we have to raise OPItemDeleteMultipleException from here + # so we can give it the list of successully deleted items + # that isn't known from inside _item_delete_multiple() raise OPItemDeleteMultipleException.from_opexception( ope, deleted_items) deleted_items.extend(batch) diff --git a/scripts/generate-pngs.sh b/scripts/generate-pngs.sh new file mode 100755 index 00000000..bc953155 --- /dev/null +++ b/scripts/generate-pngs.sh @@ -0,0 +1,49 @@ +#!/bin/sh -e + + +image_num="$1" +count="$2" +output_path="$3" + +if [ -z "$image_num" ] || [ -z "$count" ] || [ -z "$output_path" ]; +then + echo "USAGE: $0 " + exit 1 +fi + +_gen_filename(){ + printf "image_%02d.png" "$1" +} + +_gen_image_text(){ + printf "image %02d" "$1" +} + +_gen_replacement_filename(){ + printf "replacement_image_%02d.png" "$1" +} + +_gen_replacement_image_text(){ + printf "replacement image %02d" "$1" +} + + +max=$((image_num + count)) + +while [ "$image_num" -lt $max ]; +do + _image_filename_1="$(_gen_filename "$image_num")" + _image_path_1="$output_path/$_image_filename_1" + _image_text_1="$(_gen_image_text "$image_num")" + + _image_filename_2="$(_gen_replacement_filename "$image_num")" + _image_path_2="$output_path/$_image_filename_2" + _image_text_2="$(_gen_replacement_image_text "$image_num")" + + echo "Creating $_image_path_1" + convert -size 400x100 -background "red" -fill "white" -pointsize 24 -gravity center label:"$_image_text_1" "$_image_path_1" + echo "Creating $_image_path_2" + convert -size 400x100 -background "blue" -fill "white" -pointsize 24 -gravity center label:"$_image_text_2" "$_image_path_2" + + image_num=$((image_num + 1)) +done diff --git a/tests/config/mock-op/response-generation/document-edit/response-generation-document-edit-1.cfg b/tests/config/mock-op/response-generation/document-edit/response-generation-document-edit-1.cfg new file mode 100644 index 00000000..c9f122fd --- /dev/null +++ b/tests/config/mock-op/response-generation/document-edit/response-generation-document-edit-1.cfg @@ -0,0 +1,66 @@ +[MAIN] +config-path = ./tests/config/mock-op/responses-document-edit +response-path = responses-1 +input-path = input +response-dir-file = response-directory-1.json +state-iteration = 0 +state-config = ./tests/config/mock-op/responses-document-edit/document-edit-state-config.json +# Be sure to set RESP_GEN_DOT_ENV_FILE=path/to/.env_corrupt_svc_account +# to have response-generator load the service account token +existing-auth = required + +[cli-version] +type = cli-version +enabled = true + +[list-signed-in-accounts] +type = account-list +enabled = false + +[whoami] +type = whoami +enabled = false + +[document-get-example-document-01] +type = document-get +vault=Test Data 2 +item-identifier = example document 01 +enabled = false + +[document-edit-example-document-01] +type = document-edit +vault = Test Data 2 +document_identifier = example document 01 +new-document-path = working_data/images/replacement_image_01.png +changes-state = true +enabled = false + +[document-get-example-document-02] +type = document-get +vault=Test Data 2 +item-identifier = example document 02 +enabled = false + +[document-edit-example-document-02] +type = document-edit +vault = Test Data 2 +document_identifier = example document 02 +new-title = example document 02 - updated +new-document-path = working_data/images/replacement_image_02.png +changes-state = true +enabled = false + +[document-get-example-document-03] +type = document-get +vault=Test Data 2 +item-identifier = example document 03 +enabled = false + +[document-edit-example-document-03] +type = document-edit +vault = Test Data 2 +document_identifier = example document 03 +file-name = replacement_image_03.png +new-document-path = working_data/images/replacement_image_03.png +changes-state = true +enabled = false diff --git a/tests/config/mock-op/response-generation/document-edit/response-generation-document-edit-2.cfg b/tests/config/mock-op/response-generation/document-edit/response-generation-document-edit-2.cfg new file mode 100644 index 00000000..cb2b7f0d --- /dev/null +++ b/tests/config/mock-op/response-generation/document-edit/response-generation-document-edit-2.cfg @@ -0,0 +1,32 @@ +[MAIN] +config-path = ./tests/config/mock-op/responses-document-edit +response-path = responses-2 +input-path = input +response-dir-file = response-directory-2.json +state-iteration = 1 +state-config = ./tests/config/mock-op/responses-document-edit/document-edit-state-config.json +# Be sure to set RESP_GEN_DOT_ENV_FILE=path/to/.env_corrupt_svc_account +# to have response-generator load the service account token +existing-auth = required + +[whoami] +type = whoami +enabled = false + +[document-get-example-document-01] +type = document-get +vault=Test Data 2 +item-identifier = example document 01 +enabled = false + +[document-get-example-document-02-updated] +type = document-get +vault=Test Data 2 +item-identifier = example document 02 - updated +enabled = false + +[document-get-example-document-03] +type = document-get +vault=Test Data 2 +item-identifier = example document 03 +enabled = false diff --git a/tests/config/mock-op/responses-document-edit/document-edit-state-config.json b/tests/config/mock-op/responses-document-edit/document-edit-state-config.json new file mode 100644 index 00000000..413eb2ae --- /dev/null +++ b/tests/config/mock-op/responses-document-edit/document-edit-state-config.json @@ -0,0 +1,20 @@ +{ + "iteration": 0, + "max-iterations": 2, + "state-list": [ + { + "response-directory": "tests/config/mock-op/responses-document-edit/response-directory-1.json", + "env-vars": { + "set": {}, + "pop": [] + } + }, + { + "response-directory": "tests/config/mock-op/responses-document-edit/response-directory-2.json", + "env-vars": { + "set": {}, + "pop": [] + } + } + ] +} \ No newline at end of file diff --git a/tests/config/mock-op/responses-document-edit/response-directory-1.json b/tests/config/mock-op/responses-document-edit/response-directory-1.json new file mode 100644 index 00000000..4a65bb2f --- /dev/null +++ b/tests/config/mock-op/responses-document-edit/response-directory-1.json @@ -0,0 +1,107 @@ +{ + "meta": { + "response_dir": "tests/config/mock-op/responses-document-edit/responses-1", + "input_dir": "input" + }, + "commands": { + "--account|5GHHPJK5HZC5BAT7WDUXW57G44|--format|json|whoami": { + "exit_status": 0, + "stdout": "output", + "stderr": "error_output", + "name": "whoami-account-uuid", + "changes_state": false + }, + "--version": { + "exit_status": 0, + "stdout": "output", + "stderr": "error_output", + "name": "cli-version", + "changes_state": false + }, + "--format|json|account|list": { + "exit_status": 0, + "stdout": "output", + "stderr": "error_output", + "name": "list-signed-in-accounts", + "changes_state": false + }, + "--format|json|whoami": { + "exit_status": 0, + "stdout": "output", + "stderr": "error_output", + "name": "whoami", + "changes_state": false + }, + "--format|json|document|get|example document 01|--vault|Test Data 2": { + "exit_status": 0, + "stdout": "output", + "stderr": "error_output", + "name": "document-get-example-document-01", + "changes_state": false + }, + "--format|json|item|get|example document 01|--vault|Test Data 2": { + "exit_status": 0, + "stdout": "output", + "stderr": "error_output", + "name": "document-get-example-document-01-filename", + "changes_state": false + }, + "--format|json|document|get|example document 02|--vault|Test Data 2": { + "exit_status": 0, + "stdout": "output", + "stderr": "error_output", + "name": "document-get-example-document-02", + "changes_state": false + }, + "--format|json|item|get|example document 02|--vault|Test Data 2": { + "exit_status": 0, + "stdout": "output", + "stderr": "error_output", + "name": "document-get-example-document-02-filename", + "changes_state": false + }, + "--format|json|document|get|example document 03|--vault|Test Data 2": { + "exit_status": 0, + "stdout": "output", + "stderr": "error_output", + "name": "document-get-example-document-03", + "changes_state": false + }, + "--format|json|item|get|example document 03|--vault|Test Data 2": { + "exit_status": 0, + "stdout": "output", + "stderr": "error_output", + "name": "document-get-example-document-03-filename", + "changes_state": false + } + }, + "commands_with_input": { + "910404ace404544ed7606e75f15b3753": { + "--format|json|document|edit|p7rtertk6746yb6tm5fc2zf66i|--vault|Test Data 2": { + "exit_status": 0, + "stdout": "output", + "stderr": "error_output", + "name": "document-edit-example-document-01", + "changes_state": true + } + }, + "d1857eefc7b8dcf80c5137ad54b96215": { + "--format|json|document|edit|okeubqaxp4bdjywf7xbvqov2ou|--title|example document 02 - updated|--vault|Test Data 2": { + "exit_status": 0, + "stdout": "output", + "stderr": "error_output", + "name": "document-edit-example-document-02", + "changes_state": true + } + }, + "cfaff0eafdc4c29a71dd009b4c996cb0": { + "--format|json|document|edit|znj7rkttvpz7femlp2wzflizgq|--file-name|replacement_image_03.png|--vault|Test Data 2": { + "exit_status": 0, + "stdout": "output", + "stderr": "error_output", + "name": "document-edit-example-document-03", + "changes_state": true + } + } + } +} \ No newline at end of file diff --git a/tests/config/mock-op/responses-document-edit/response-directory-2.json b/tests/config/mock-op/responses-document-edit/response-directory-2.json new file mode 100644 index 00000000..2efbb0db --- /dev/null +++ b/tests/config/mock-op/responses-document-edit/response-directory-2.json @@ -0,0 +1,58 @@ +{ + "meta": { + "response_dir": "tests/config/mock-op/responses-document-edit/responses-2", + "input_dir": "input" + }, + "commands": { + "--format|json|document|get|example document 01|--vault|Test Data 2": { + "exit_status": 0, + "stdout": "output", + "stderr": "error_output", + "name": "document-get-example-document-01", + "changes_state": false + }, + "--format|json|item|get|example document 01|--vault|Test Data 2": { + "exit_status": 0, + "stdout": "output", + "stderr": "error_output", + "name": "document-get-example-document-01-filename", + "changes_state": false + }, + "--account|5GHHPJK5HZC5BAT7WDUXW57G44|--format|json|whoami": { + "exit_status": 0, + "stdout": "output", + "stderr": "error_output", + "name": "whoami-account-uuid", + "changes_state": false + }, + "--format|json|document|get|example document 02 - updated|--vault|Test Data 2": { + "exit_status": 0, + "stdout": "output", + "stderr": "error_output", + "name": "document-get-example-document-02-updated", + "changes_state": false + }, + "--format|json|item|get|example document 02 - updated|--vault|Test Data 2": { + "exit_status": 0, + "stdout": "output", + "stderr": "error_output", + "name": "document-get-example-document-02-updated-filename", + "changes_state": false + }, + "--format|json|document|get|example document 03|--vault|Test Data 2": { + "exit_status": 0, + "stdout": "output", + "stderr": "error_output", + "name": "document-get-example-document-03", + "changes_state": false + }, + "--format|json|item|get|example document 03|--vault|Test Data 2": { + "exit_status": 0, + "stdout": "output", + "stderr": "error_output", + "name": "document-get-example-document-03-filename", + "changes_state": false + } + }, + "commands_with_input": {} +} \ No newline at end of file diff --git a/tests/config/mock-op/responses-document-edit/responses-1/cli-version/error_output b/tests/config/mock-op/responses-document-edit/responses-1/cli-version/error_output new file mode 100644 index 00000000..e69de29b diff --git a/tests/config/mock-op/responses-document-edit/responses-1/cli-version/output b/tests/config/mock-op/responses-document-edit/responses-1/cli-version/output new file mode 100644 index 00000000..e9763f6b --- /dev/null +++ b/tests/config/mock-op/responses-document-edit/responses-1/cli-version/output @@ -0,0 +1 @@ +2.23.0 diff --git a/tests/config/mock-op/responses-document-edit/responses-1/document-edit-example-document-01/error_output b/tests/config/mock-op/responses-document-edit/responses-1/document-edit-example-document-01/error_output new file mode 100644 index 00000000..e69de29b diff --git a/tests/config/mock-op/responses-document-edit/responses-1/document-edit-example-document-01/output b/tests/config/mock-op/responses-document-edit/responses-1/document-edit-example-document-01/output new file mode 100644 index 00000000..e69de29b diff --git a/tests/config/mock-op/responses-document-edit/responses-1/document-edit-example-document-02/error_output b/tests/config/mock-op/responses-document-edit/responses-1/document-edit-example-document-02/error_output new file mode 100644 index 00000000..e69de29b diff --git a/tests/config/mock-op/responses-document-edit/responses-1/document-edit-example-document-02/output b/tests/config/mock-op/responses-document-edit/responses-1/document-edit-example-document-02/output new file mode 100644 index 00000000..e69de29b diff --git a/tests/config/mock-op/responses-document-edit/responses-1/document-edit-example-document-03/error_output b/tests/config/mock-op/responses-document-edit/responses-1/document-edit-example-document-03/error_output new file mode 100644 index 00000000..e69de29b diff --git a/tests/config/mock-op/responses-document-edit/responses-1/document-edit-example-document-03/output b/tests/config/mock-op/responses-document-edit/responses-1/document-edit-example-document-03/output new file mode 100644 index 00000000..e69de29b diff --git a/tests/config/mock-op/responses-document-edit/responses-1/document-get-example-document-01-filename/error_output b/tests/config/mock-op/responses-document-edit/responses-1/document-get-example-document-01-filename/error_output new file mode 100644 index 00000000..e69de29b diff --git a/tests/config/mock-op/responses-document-edit/responses-1/document-get-example-document-01-filename/output b/tests/config/mock-op/responses-document-edit/responses-1/document-get-example-document-01-filename/output new file mode 100644 index 00000000..ffbb2618 --- /dev/null +++ b/tests/config/mock-op/responses-document-edit/responses-1/document-get-example-document-01-filename/output @@ -0,0 +1,30 @@ +{ + "id": "p7rtertk6746yb6tm5fc2zf66i", + "title": "example document 01", + "version": 7, + "vault": { + "id": "vkjyqsbkf63zycqigbg3yxd7ce", + "name": "Test Data 2" + }, + "category": "DOCUMENT", + "last_edited_by": "FZTDCNIJAOEBZZ2VQFNDVHC3K6", + "created_at": "2023-11-15T03:26:35Z", + "updated_at": "2023-11-15T06:08:40Z", + "fields": [ + { + "id": "notesPlain", + "type": "STRING", + "purpose": "NOTES", + "label": "notesPlain", + "reference": "op://Test Data 2/example document 01/notesPlain" + } + ], + "files": [ + { + "id": "bdwjqbnbmeaehyz7g6tbmgqbze", + "name": "image_01.png", + "size": 1830, + "content_path": "/v1/vaults/vkjyqsbkf63zycqigbg3yxd7ce/items/p7rtertk6746yb6tm5fc2zf66i/files/bdwjqbnbmeaehyz7g6tbmgqbze/content" + } + ] +} diff --git a/tests/config/mock-op/responses-document-edit/responses-1/document-get-example-document-01/error_output b/tests/config/mock-op/responses-document-edit/responses-1/document-get-example-document-01/error_output new file mode 100644 index 00000000..e69de29b diff --git a/tests/config/mock-op/responses-document-edit/responses-1/document-get-example-document-01/output b/tests/config/mock-op/responses-document-edit/responses-1/document-get-example-document-01/output new file mode 100644 index 00000000..fca5a576 Binary files /dev/null and b/tests/config/mock-op/responses-document-edit/responses-1/document-get-example-document-01/output differ diff --git a/tests/config/mock-op/responses-document-edit/responses-1/document-get-example-document-02-filename/error_output b/tests/config/mock-op/responses-document-edit/responses-1/document-get-example-document-02-filename/error_output new file mode 100644 index 00000000..e69de29b diff --git a/tests/config/mock-op/responses-document-edit/responses-1/document-get-example-document-02-filename/output b/tests/config/mock-op/responses-document-edit/responses-1/document-get-example-document-02-filename/output new file mode 100644 index 00000000..b5150cd7 --- /dev/null +++ b/tests/config/mock-op/responses-document-edit/responses-1/document-get-example-document-02-filename/output @@ -0,0 +1,30 @@ +{ + "id": "okeubqaxp4bdjywf7xbvqov2ou", + "title": "example document 02", + "version": 1, + "vault": { + "id": "vkjyqsbkf63zycqigbg3yxd7ce", + "name": "Test Data 2" + }, + "category": "DOCUMENT", + "last_edited_by": "FZTDCNIJAOEBZZ2VQFNDVHC3K6", + "created_at": "2023-11-15T03:26:36Z", + "updated_at": "2023-11-15T03:26:36Z", + "fields": [ + { + "id": "notesPlain", + "type": "STRING", + "purpose": "NOTES", + "label": "notesPlain", + "reference": "op://Test Data 2/example document 02/notesPlain" + } + ], + "files": [ + { + "id": "qpca6u4b4ikwfnw23m3mk7z63u", + "name": "image_02.png", + "size": 1959, + "content_path": "/v1/vaults/vkjyqsbkf63zycqigbg3yxd7ce/items/okeubqaxp4bdjywf7xbvqov2ou/files/qpca6u4b4ikwfnw23m3mk7z63u/content" + } + ] +} diff --git a/tests/config/mock-op/responses-document-edit/responses-1/document-get-example-document-02/error_output b/tests/config/mock-op/responses-document-edit/responses-1/document-get-example-document-02/error_output new file mode 100644 index 00000000..e69de29b diff --git a/tests/config/mock-op/responses-document-edit/responses-1/document-get-example-document-02/output b/tests/config/mock-op/responses-document-edit/responses-1/document-get-example-document-02/output new file mode 100644 index 00000000..9bb30960 Binary files /dev/null and b/tests/config/mock-op/responses-document-edit/responses-1/document-get-example-document-02/output differ diff --git a/tests/config/mock-op/responses-document-edit/responses-1/document-get-example-document-03-filename/error_output b/tests/config/mock-op/responses-document-edit/responses-1/document-get-example-document-03-filename/error_output new file mode 100644 index 00000000..e69de29b diff --git a/tests/config/mock-op/responses-document-edit/responses-1/document-get-example-document-03-filename/output b/tests/config/mock-op/responses-document-edit/responses-1/document-get-example-document-03-filename/output new file mode 100644 index 00000000..c3b7bcde --- /dev/null +++ b/tests/config/mock-op/responses-document-edit/responses-1/document-get-example-document-03-filename/output @@ -0,0 +1,31 @@ +{ + "id": "znj7rkttvpz7femlp2wzflizgq", + "title": "example document 03", + "version": 5, + "vault": { + "id": "vkjyqsbkf63zycqigbg3yxd7ce", + "name": "Test Data 2" + }, + "category": "DOCUMENT", + "last_edited_by": "4J4NLDK7GFAXJFOR7RF2KDUAMI", + "created_at": "2023-11-15T03:26:37Z", + "updated_at": "2023-11-20T03:36:55Z", + "additional_information": "1 KB", + "fields": [ + { + "id": "notesPlain", + "type": "STRING", + "purpose": "NOTES", + "label": "notesPlain", + "reference": "op://Test Data 2/example document 03/notesPlain" + } + ], + "files": [ + { + "id": "lazeuf52yj2y6rwsv7zgw5mf4i", + "name": "image_03.png", + "size": 1982, + "content_path": "/v1/vaults/vkjyqsbkf63zycqigbg3yxd7ce/items/znj7rkttvpz7femlp2wzflizgq/files/lazeuf52yj2y6rwsv7zgw5mf4i/content" + } + ] +} diff --git a/tests/config/mock-op/responses-document-edit/responses-1/document-get-example-document-03/error_output b/tests/config/mock-op/responses-document-edit/responses-1/document-get-example-document-03/error_output new file mode 100644 index 00000000..e69de29b diff --git a/tests/config/mock-op/responses-document-edit/responses-1/document-get-example-document-03/output b/tests/config/mock-op/responses-document-edit/responses-1/document-get-example-document-03/output new file mode 100644 index 00000000..ff701af8 Binary files /dev/null and b/tests/config/mock-op/responses-document-edit/responses-1/document-get-example-document-03/output differ diff --git a/tests/config/mock-op/responses-document-edit/responses-1/list-signed-in-accounts/error_output b/tests/config/mock-op/responses-document-edit/responses-1/list-signed-in-accounts/error_output new file mode 100644 index 00000000..e69de29b diff --git a/tests/config/mock-op/responses-document-edit/responses-1/list-signed-in-accounts/output b/tests/config/mock-op/responses-document-edit/responses-1/list-signed-in-accounts/output new file mode 100644 index 00000000..0f19f028 --- /dev/null +++ b/tests/config/mock-op/responses-document-edit/responses-1/list-signed-in-accounts/output @@ -0,0 +1,20 @@ +[ + { + "url": "pyonepassword.1password.com", + "email": "uid000@gmail.com", + "user_uuid": "4J4NLDK7GFAXJFOR7RF2KDUAMI", + "account_uuid": "M5C4BT3KMQ7HROISKYLWDUXW57" + }, + { + "url": "example-account.1password.com", + "email": "example_user@example.email", + "user_uuid": "5GHHPJK5HZC5BAT7WDUXW57G44", + "account_uuid": "GRXJAN4BY5DPROISKYL55IRCPY" + }, + { + "url": "my.1password.com", + "email": "guest_user@example.email", + "user_uuid": "DJGTGRFRM5C4BHNUXQLJXQJAOE", + "account_uuid": "6J7RFGQWONBVJHIHJUVICO2C3Q" + } +] diff --git a/tests/config/mock-op/responses-document-edit/responses-1/whoami-account-uuid/error_output b/tests/config/mock-op/responses-document-edit/responses-1/whoami-account-uuid/error_output new file mode 100644 index 00000000..e69de29b diff --git a/tests/config/mock-op/responses-document-edit/responses-1/whoami-account-uuid/output b/tests/config/mock-op/responses-document-edit/responses-1/whoami-account-uuid/output new file mode 100644 index 00000000..edc59bd5 --- /dev/null +++ b/tests/config/mock-op/responses-document-edit/responses-1/whoami-account-uuid/output @@ -0,0 +1,8 @@ +{ + "url": "https://pyonepassword.1password.com", + "URL": "https://pyonepassword.1password.com", + "user_uuid": "FZTDCNIJAOEBZZ2VQFNDVHC3K6", + "account_uuid": "M5C4BT3KMQ7HROISKYLWDUXW57", + "user_type": "SERVICE_ACCOUNT", + "ServiceAccountType": "SERVICE_ACCOUNT" +} diff --git a/tests/config/mock-op/responses-document-edit/responses-1/whoami/error_output b/tests/config/mock-op/responses-document-edit/responses-1/whoami/error_output new file mode 100644 index 00000000..e69de29b diff --git a/tests/config/mock-op/responses-document-edit/responses-1/whoami/output b/tests/config/mock-op/responses-document-edit/responses-1/whoami/output new file mode 100644 index 00000000..edc59bd5 --- /dev/null +++ b/tests/config/mock-op/responses-document-edit/responses-1/whoami/output @@ -0,0 +1,8 @@ +{ + "url": "https://pyonepassword.1password.com", + "URL": "https://pyonepassword.1password.com", + "user_uuid": "FZTDCNIJAOEBZZ2VQFNDVHC3K6", + "account_uuid": "M5C4BT3KMQ7HROISKYLWDUXW57", + "user_type": "SERVICE_ACCOUNT", + "ServiceAccountType": "SERVICE_ACCOUNT" +} diff --git a/tests/config/mock-op/responses-document-edit/responses-2/document-get-example-document-01-filename/error_output b/tests/config/mock-op/responses-document-edit/responses-2/document-get-example-document-01-filename/error_output new file mode 100644 index 00000000..e69de29b diff --git a/tests/config/mock-op/responses-document-edit/responses-2/document-get-example-document-01-filename/output b/tests/config/mock-op/responses-document-edit/responses-2/document-get-example-document-01-filename/output new file mode 100644 index 00000000..563eae77 --- /dev/null +++ b/tests/config/mock-op/responses-document-edit/responses-2/document-get-example-document-01-filename/output @@ -0,0 +1,30 @@ +{ + "id": "p7rtertk6746yb6tm5fc2zf66i", + "title": "example document 01", + "version": 8, + "vault": { + "id": "vkjyqsbkf63zycqigbg3yxd7ce", + "name": "Test Data 2" + }, + "category": "DOCUMENT", + "last_edited_by": "FZTDCNIJAOEBZZ2VQFNDVHC3K6", + "created_at": "2023-11-15T03:26:35Z", + "updated_at": "2023-11-15T06:09:25Z", + "fields": [ + { + "id": "notesPlain", + "type": "STRING", + "purpose": "NOTES", + "label": "notesPlain", + "reference": "op://Test Data 2/example document 01/notesPlain" + } + ], + "files": [ + { + "id": "ngmyp2kkocvft3wvvk5zfrzl3u", + "name": "image_01.png", + "size": 2462, + "content_path": "/v1/vaults/vkjyqsbkf63zycqigbg3yxd7ce/items/p7rtertk6746yb6tm5fc2zf66i/files/ngmyp2kkocvft3wvvk5zfrzl3u/content" + } + ] +} diff --git a/tests/config/mock-op/responses-document-edit/responses-2/document-get-example-document-01/error_output b/tests/config/mock-op/responses-document-edit/responses-2/document-get-example-document-01/error_output new file mode 100644 index 00000000..e69de29b diff --git a/tests/config/mock-op/responses-document-edit/responses-2/document-get-example-document-01/output b/tests/config/mock-op/responses-document-edit/responses-2/document-get-example-document-01/output new file mode 100644 index 00000000..f83a90b2 Binary files /dev/null and b/tests/config/mock-op/responses-document-edit/responses-2/document-get-example-document-01/output differ diff --git a/tests/config/mock-op/responses-document-edit/responses-2/document-get-example-document-02-updated-filename/error_output b/tests/config/mock-op/responses-document-edit/responses-2/document-get-example-document-02-updated-filename/error_output new file mode 100644 index 00000000..e69de29b diff --git a/tests/config/mock-op/responses-document-edit/responses-2/document-get-example-document-02-updated-filename/output b/tests/config/mock-op/responses-document-edit/responses-2/document-get-example-document-02-updated-filename/output new file mode 100644 index 00000000..974d4060 --- /dev/null +++ b/tests/config/mock-op/responses-document-edit/responses-2/document-get-example-document-02-updated-filename/output @@ -0,0 +1,30 @@ +{ + "id": "okeubqaxp4bdjywf7xbvqov2ou", + "title": "example document 02 - updated", + "version": 2, + "vault": { + "id": "vkjyqsbkf63zycqigbg3yxd7ce", + "name": "Test Data 2" + }, + "category": "DOCUMENT", + "last_edited_by": "FZTDCNIJAOEBZZ2VQFNDVHC3K6", + "created_at": "2023-11-15T03:26:36Z", + "updated_at": "2023-11-19T03:20:20Z", + "fields": [ + { + "id": "notesPlain", + "type": "STRING", + "purpose": "NOTES", + "label": "notesPlain", + "reference": "op://Test Data 2/example document 02 - updated/notesPlain" + } + ], + "files": [ + { + "id": "z2u4b47rl4m423cob7uqtu5n4q", + "name": "image_02.png", + "size": 2602, + "content_path": "/v1/vaults/vkjyqsbkf63zycqigbg3yxd7ce/items/okeubqaxp4bdjywf7xbvqov2ou/files/z2u4b47rl4m423cob7uqtu5n4q/content" + } + ] +} diff --git a/tests/config/mock-op/responses-document-edit/responses-2/document-get-example-document-02-updated/error_output b/tests/config/mock-op/responses-document-edit/responses-2/document-get-example-document-02-updated/error_output new file mode 100644 index 00000000..e69de29b diff --git a/tests/config/mock-op/responses-document-edit/responses-2/document-get-example-document-02-updated/output b/tests/config/mock-op/responses-document-edit/responses-2/document-get-example-document-02-updated/output new file mode 100644 index 00000000..7dc55556 Binary files /dev/null and b/tests/config/mock-op/responses-document-edit/responses-2/document-get-example-document-02-updated/output differ diff --git a/tests/config/mock-op/responses-document-edit/responses-2/document-get-example-document-03-filename/error_output b/tests/config/mock-op/responses-document-edit/responses-2/document-get-example-document-03-filename/error_output new file mode 100644 index 00000000..e69de29b diff --git a/tests/config/mock-op/responses-document-edit/responses-2/document-get-example-document-03-filename/output b/tests/config/mock-op/responses-document-edit/responses-2/document-get-example-document-03-filename/output new file mode 100644 index 00000000..c7ead150 --- /dev/null +++ b/tests/config/mock-op/responses-document-edit/responses-2/document-get-example-document-03-filename/output @@ -0,0 +1,30 @@ +{ + "id": "znj7rkttvpz7femlp2wzflizgq", + "title": "example document 03", + "version": 6, + "vault": { + "id": "vkjyqsbkf63zycqigbg3yxd7ce", + "name": "Test Data 2" + }, + "category": "DOCUMENT", + "last_edited_by": "FZTDCNIJAOEBZZ2VQFNDVHC3K6", + "created_at": "2023-11-15T03:26:37Z", + "updated_at": "2023-11-20T03:37:05Z", + "fields": [ + { + "id": "notesPlain", + "type": "STRING", + "purpose": "NOTES", + "label": "notesPlain", + "reference": "op://Test Data 2/example document 03/notesPlain" + } + ], + "files": [ + { + "id": "7bzfthc5o2t25gkmnygjukj24i", + "name": "replacement_image_03.png", + "size": 2626, + "content_path": "/v1/vaults/vkjyqsbkf63zycqigbg3yxd7ce/items/znj7rkttvpz7femlp2wzflizgq/files/7bzfthc5o2t25gkmnygjukj24i/content" + } + ] +} diff --git a/tests/config/mock-op/responses-document-edit/responses-2/document-get-example-document-03/error_output b/tests/config/mock-op/responses-document-edit/responses-2/document-get-example-document-03/error_output new file mode 100644 index 00000000..e69de29b diff --git a/tests/config/mock-op/responses-document-edit/responses-2/document-get-example-document-03/output b/tests/config/mock-op/responses-document-edit/responses-2/document-get-example-document-03/output new file mode 100644 index 00000000..614e2d02 Binary files /dev/null and b/tests/config/mock-op/responses-document-edit/responses-2/document-get-example-document-03/output differ diff --git a/tests/config/mock-op/responses-document-edit/responses-2/whoami-account-uuid/error_output b/tests/config/mock-op/responses-document-edit/responses-2/whoami-account-uuid/error_output new file mode 100644 index 00000000..e69de29b diff --git a/tests/config/mock-op/responses-document-edit/responses-2/whoami-account-uuid/output b/tests/config/mock-op/responses-document-edit/responses-2/whoami-account-uuid/output new file mode 100644 index 00000000..edc59bd5 --- /dev/null +++ b/tests/config/mock-op/responses-document-edit/responses-2/whoami-account-uuid/output @@ -0,0 +1,8 @@ +{ + "url": "https://pyonepassword.1password.com", + "URL": "https://pyonepassword.1password.com", + "user_uuid": "FZTDCNIJAOEBZZ2VQFNDVHC3K6", + "account_uuid": "M5C4BT3KMQ7HROISKYLWDUXW57", + "user_type": "SERVICE_ACCOUNT", + "ServiceAccountType": "SERVICE_ACCOUNT" +} diff --git a/tests/data/test-input-data/binary-data-registry.json b/tests/data/test-input-data/binary-data-registry.json new file mode 100644 index 00000000..57235eaf --- /dev/null +++ b/tests/data/test-input-data/binary-data-registry.json @@ -0,0 +1,6 @@ +{ + "binary-image-data": { + "name": "images", + "type": "registry" + } +} diff --git a/tests/data/test-input-data/binary-data/images/image_01.png b/tests/data/test-input-data/binary-data/images/image_01.png new file mode 100644 index 00000000..fca5a576 Binary files /dev/null and b/tests/data/test-input-data/binary-data/images/image_01.png differ diff --git a/tests/data/test-input-data/binary-data/images/image_02.png b/tests/data/test-input-data/binary-data/images/image_02.png new file mode 100644 index 00000000..9bb30960 Binary files /dev/null and b/tests/data/test-input-data/binary-data/images/image_02.png differ diff --git a/tests/data/test-input-data/binary-data/images/image_03.png b/tests/data/test-input-data/binary-data/images/image_03.png new file mode 100644 index 00000000..ff701af8 Binary files /dev/null and b/tests/data/test-input-data/binary-data/images/image_03.png differ diff --git a/tests/data/test-input-data/binary-data/images/image_04.png b/tests/data/test-input-data/binary-data/images/image_04.png new file mode 100644 index 00000000..6e33a2d1 Binary files /dev/null and b/tests/data/test-input-data/binary-data/images/image_04.png differ diff --git a/tests/data/test-input-data/binary-data/images/image_05.png b/tests/data/test-input-data/binary-data/images/image_05.png new file mode 100644 index 00000000..8dd54eae Binary files /dev/null and b/tests/data/test-input-data/binary-data/images/image_05.png differ diff --git a/tests/data/test-input-data/binary-data/images/image_06.png b/tests/data/test-input-data/binary-data/images/image_06.png new file mode 100644 index 00000000..0cf048d2 Binary files /dev/null and b/tests/data/test-input-data/binary-data/images/image_06.png differ diff --git a/tests/data/test-input-data/binary-data/images/image_07.png b/tests/data/test-input-data/binary-data/images/image_07.png new file mode 100644 index 00000000..4060379e Binary files /dev/null and b/tests/data/test-input-data/binary-data/images/image_07.png differ diff --git a/tests/data/test-input-data/binary-data/images/image_08.png b/tests/data/test-input-data/binary-data/images/image_08.png new file mode 100644 index 00000000..5e235c1e Binary files /dev/null and b/tests/data/test-input-data/binary-data/images/image_08.png differ diff --git a/tests/data/test-input-data/binary-data/images/image_09.png b/tests/data/test-input-data/binary-data/images/image_09.png new file mode 100644 index 00000000..77b5e4cd Binary files /dev/null and b/tests/data/test-input-data/binary-data/images/image_09.png differ diff --git a/tests/data/test-input-data/binary-data/images/image_10.png b/tests/data/test-input-data/binary-data/images/image_10.png new file mode 100644 index 00000000..6a499a49 Binary files /dev/null and b/tests/data/test-input-data/binary-data/images/image_10.png differ diff --git a/tests/data/test-input-data/binary-data/images/registry.json b/tests/data/test-input-data/binary-data/images/registry.json new file mode 100644 index 00000000..9058d26d --- /dev/null +++ b/tests/data/test-input-data/binary-data/images/registry.json @@ -0,0 +1,14 @@ +{ + "replacement-image-01": { + "name": "replacement_image_01.png", + "type": "binary" + }, + "replacement-image-02": { + "name": "replacement_image_02.png", + "type": "binary" + }, + "replacement-image-03": { + "name": "replacement_image_03.png", + "type": "binary" + } +} diff --git a/tests/data/test-input-data/binary-data/images/replacement_image_01.png b/tests/data/test-input-data/binary-data/images/replacement_image_01.png new file mode 100644 index 00000000..f83a90b2 Binary files /dev/null and b/tests/data/test-input-data/binary-data/images/replacement_image_01.png differ diff --git a/tests/data/test-input-data/binary-data/images/replacement_image_02.png b/tests/data/test-input-data/binary-data/images/replacement_image_02.png new file mode 100644 index 00000000..7dc55556 Binary files /dev/null and b/tests/data/test-input-data/binary-data/images/replacement_image_02.png differ diff --git a/tests/data/test-input-data/binary-data/images/replacement_image_03.png b/tests/data/test-input-data/binary-data/images/replacement_image_03.png new file mode 100644 index 00000000..614e2d02 Binary files /dev/null and b/tests/data/test-input-data/binary-data/images/replacement_image_03.png differ diff --git a/tests/data/test-input-data/binary-data/images/replacement_image_04.png b/tests/data/test-input-data/binary-data/images/replacement_image_04.png new file mode 100644 index 00000000..4162f83b Binary files /dev/null and b/tests/data/test-input-data/binary-data/images/replacement_image_04.png differ diff --git a/tests/data/test-input-data/binary-data/images/replacement_image_05.png b/tests/data/test-input-data/binary-data/images/replacement_image_05.png new file mode 100644 index 00000000..bbd79f47 Binary files /dev/null and b/tests/data/test-input-data/binary-data/images/replacement_image_05.png differ diff --git a/tests/data/test-input-data/binary-data/images/replacement_image_06.png b/tests/data/test-input-data/binary-data/images/replacement_image_06.png new file mode 100644 index 00000000..f553031e Binary files /dev/null and b/tests/data/test-input-data/binary-data/images/replacement_image_06.png differ diff --git a/tests/data/test-input-data/binary-data/images/replacement_image_07.png b/tests/data/test-input-data/binary-data/images/replacement_image_07.png new file mode 100644 index 00000000..4c8938b5 Binary files /dev/null and b/tests/data/test-input-data/binary-data/images/replacement_image_07.png differ diff --git a/tests/data/test-input-data/binary-data/images/replacement_image_08.png b/tests/data/test-input-data/binary-data/images/replacement_image_08.png new file mode 100644 index 00000000..03191b46 Binary files /dev/null and b/tests/data/test-input-data/binary-data/images/replacement_image_08.png differ diff --git a/tests/data/test-input-data/binary-data/images/replacement_image_09.png b/tests/data/test-input-data/binary-data/images/replacement_image_09.png new file mode 100644 index 00000000..d50eecaf Binary files /dev/null and b/tests/data/test-input-data/binary-data/images/replacement_image_09.png differ diff --git a/tests/data/test-input-data/binary-data/images/replacement_image_10.png b/tests/data/test-input-data/binary-data/images/replacement_image_10.png new file mode 100644 index 00000000..98d663d8 Binary files /dev/null and b/tests/data/test-input-data/binary-data/images/replacement_image_10.png differ diff --git a/tests/fixtures/binary_input_data.py b/tests/fixtures/binary_input_data.py new file mode 100644 index 00000000..b0654084 --- /dev/null +++ b/tests/fixtures/binary_input_data.py @@ -0,0 +1,29 @@ +from typing import Dict + +from .paths import BINARY_DATA_PATH, BINARY_DATA_REGISTRY_PATH +from .valid_data import ValidData + + +class BinaryData(ValidData): + REGISTRY_PATH = BINARY_DATA_REGISTRY_PATH + DATA_PATH = BINARY_DATA_PATH + + @property + def binary_image_data(self) -> Dict[str, Dict]: + item_data_registry = self.data_for_name("binary-image-data") + return item_data_registry + + +class BinaryImageData(ValidData): + + def __init__(self): + binary_data = BinaryData() + binary_image_data_registry: Dict = binary_data.binary_image_data + registry = binary_image_data_registry["registry"] + super().__init__(registry=registry) + self._data_path = binary_image_data_registry["data_path"] + self._registry_path = binary_image_data_registry["registry_path"] + + def data_for_name(self, entry_name) -> bytes: + data = super().data_for_name(entry_name) + return data diff --git a/tests/fixtures/op_fixtures.py b/tests/fixtures/op_fixtures.py index fdb6a879..8c3d9de9 100644 --- a/tests/fixtures/op_fixtures.py +++ b/tests/fixtures/op_fixtures.py @@ -8,6 +8,7 @@ from pyonepassword import OP, logging from pyonepassword.api.exceptions import OPCmdFailedException +from .binary_input_data import BinaryImageData from .expected_account_data import ExpectedAccountData from .expected_api_credential_data import ExpectedAPICredentialData from .expected_credit_card import ExpectedCreditCardData @@ -40,6 +41,7 @@ from .non_comformant_data import NonConformantData from .paths import ( ALT_RESP_DIRECTORY_PATH, + DOCUMENT_EDIT_STATE_CONFIG_PATH, ITEM_DELETE_MULTIPLE_STATE_CONFIG_PATH, ITEM_DELETE_MULTIPLE_TITLE_GLOB_STATE_CONFIG_PATH, ITEM_EDIT_STATE_CONFIG_PATH, @@ -235,6 +237,33 @@ def setup_stateful_item_edit(): # temp_dir will get cleaned up once we return +@fixture +def setup_stateful_document_edit(): + + # set up a temporary directory to copy the state config to, since it gets modified + # during state iteration + config_file_name = DOCUMENT_EDIT_STATE_CONFIG_PATH.name + temp_dir = tempfile.TemporaryDirectory() + state_config_dir = temp_dir.name + state_config_path = Path(state_config_dir, config_file_name) + shutil.copyfile( + DOCUMENT_EDIT_STATE_CONFIG_PATH, state_config_path) + + # now pop MOCK_OP_RESPONSE_DIRECTORY to ensure it doesn't conflict with with + # the stateful config + old_mock_op_resp_dir = os.environ.pop("MOCK_OP_RESPONSE_DIRECTORY", None) + os.environ["MOCK_OP_STATE_DIR"] = str(state_config_path) + yield # pytest will return us here after the test runs + # get rid of MOCK_OP_STATE_DIR + os.environ.pop("MOCK_OP_STATE_DIR") + + # restore MOCK_OP_RESPONSE_DIRECTORY if it was previously set + if old_mock_op_resp_dir is not None: + os.environ["MOCK_OP_RESPONSE_DIRECTORY"] = old_mock_op_resp_dir + + # temp_dir will get cleaned up once we return + + @fixture def setup_stateful_svc_acct_auth(): config_file_name = SVC_ACCT_NOT_YET_AUTH_STATE_CONFIG_PATH.name @@ -528,6 +557,12 @@ def non_conformant_data(): return data +@fixture +def binary_image_data(): + data = BinaryImageData() + return data + + @fixture def valid_op_cli_config_homedir(): config_obj = ValidOPCLIConfig() diff --git a/tests/fixtures/paths.py b/tests/fixtures/paths.py index eb198359..1db44a32 100644 --- a/tests/fixtures/paths.py +++ b/tests/fixtures/paths.py @@ -23,6 +23,13 @@ ITEM_EDIT_RESP_PATH, "item-edit-state-config.json" ) +DOCUMENT_EDIT_RESP_PATH = Path( + MOCK_OP_CONFIG_PATH, "responses-document-edit" +) +DOCUMENT_EDIT_STATE_CONFIG_PATH = Path( + DOCUMENT_EDIT_RESP_PATH, "document-edit-state-config.json" +) + UNAUTH_RESP_DIRECTORY_PATH = Path( MOCK_OP_CONFIG_PATH, "unauth-response-directory.json") @@ -46,12 +53,20 @@ VALID_DATA_REGISTRY_PATH = Path( TEST_INPUT_DATA_PATH, "valid-data-registry.json") VALID_DATA_PATH = Path(TEST_INPUT_DATA_PATH, "valid-data") + INVALID_DATA_REGISTRY_PATH = Path( TEST_INPUT_DATA_PATH, "invalid-data-registry.json") INVALID_DATA_PATH = Path(TEST_INPUT_DATA_PATH, "invalid-data") + NON_CONFORMANT_REGISTRY_PATH = Path( TEST_INPUT_DATA_PATH, "non-conformant-data-registry.json") NON_CONFORMANT_DATA_PATH = Path(TEST_INPUT_DATA_PATH, "non-conformant-data") + +BINARY_DATA_REGISTRY_PATH = Path( + TEST_INPUT_DATA_PATH, "binary-data-registry.json") +BINARY_DATA_PATH = Path(TEST_INPUT_DATA_PATH, "binary-data") + + EXPECTED_DATA_REGISTRY_PATH = Path( TEST_DATA_PATH, "expected-data-registry.json") EXPECTED_DATA_PATH = Path(TEST_DATA_PATH, "expected-data") diff --git a/tests/fixtures/valid_data.py b/tests/fixtures/valid_data.py index 7cbc5a88..96601851 100644 --- a/tests/fixtures/valid_data.py +++ b/tests/fixtures/valid_data.py @@ -28,10 +28,11 @@ def _load_registry_dict(self, item_path): "registry": reg_dict, "registry_path": registry_path} return registry - def _load_text_or_json(self, item_path, item_type, strip): + def _load_data_from_file(self, item_path, item_type, strip): if item_type in ["text", "json"]: mode = "r" else: + strip = False mode = "rb" data = open(item_path, mode).read() if item_type == "json": @@ -41,6 +42,16 @@ def _load_text_or_json(self, item_path, item_type, strip): data = data.rstrip() return data + def data_path_for_name(self, entry_name, version=0): + entry: Dict = self._registry[entry_name] + if "versions" in entry: + entry = entry["versions"][version] + + item_filename = entry["name"] + + item_path = Path(self._data_path, item_filename) + return item_path + def data_for_name(self, entry_name, version=0): entry: Dict = self._registry[entry_name] if "versions" in entry: @@ -54,7 +65,8 @@ def data_for_name(self, entry_name, version=0): if item_type == "registry": data = self._load_registry_dict(item_path) else: - data = self._load_text_or_json(item_path, item_type, strip) + # text, json, binary + data = self._load_data_from_file(item_path, item_type, strip) return data diff --git a/tests/test_op_api/__init__.py b/tests/test_op_api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_account_list.py b/tests/test_op_api/account/list/test_account_list.py similarity index 96% rename from tests/test_account_list.py rename to tests/test_op_api/account/list/test_account_list.py index 4f49505a..5755064a 100644 --- a/tests/test_account_list.py +++ b/tests/test_op_api/account/list/test_account_list.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from pyonepassword import OP - from .fixtures.expected_account_data import ExpectedAccountData + from ....fixtures.expected_account_data import ExpectedAccountData pytestmark = pytest.mark.usefixtures("valid_op_cli_config_homedir") diff --git a/tests/test_op_api/document/__init__.py b/tests/test_op_api/document/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_op_api/document/delete/__init__.py b/tests/test_op_api/document/delete/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_document_delete.py b/tests/test_op_api/document/delete/test_document_delete.py similarity index 96% rename from tests/test_document_delete.py rename to tests/test_op_api/document/delete/test_document_delete.py index 7aa6dfb9..5bdcaa31 100644 --- a/tests/test_document_delete.py +++ b/tests/test_op_api/document/delete/test_document_delete.py @@ -14,9 +14,9 @@ if TYPE_CHECKING: from pyonepassword import OP - from .fixtures.expected_document_data import ExpectedDocumentData + from ....fixtures.expected_document_data import ExpectedDocumentData -from .test_support.util import digest +from ....test_support.util import digest # ensure HOME env variable is set, and there's a valid op config present pytestmark = pytest.mark.usefixtures("valid_op_cli_config_homedir") diff --git a/tests/test_op_api/document/edit/__init__.py b/tests/test_op_api/document/edit/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_op_api/document/edit/test_document_edit.py b/tests/test_op_api/document/edit/test_document_edit.py new file mode 100644 index 00000000..9815f485 --- /dev/null +++ b/tests/test_op_api/document/edit/test_document_edit.py @@ -0,0 +1,153 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from pyonepassword import OP + from pyonepassword.api.object_types import OPDocumentItem + + from ....fixtures.binary_input_data import BinaryImageData + +import pytest + +from ....test_support.util import digest + +pytestmark = pytest.mark.usefixtures("valid_op_cli_config_homedir") + + +@pytest.mark.usefixtures("setup_stateful_document_edit") +def test_document_edit_01(signed_in_op: OP, binary_image_data: BinaryImageData): + """ + Test: OP.document_edit() with path to a replacement file + - Retrieve document bytes and filename via OP.document_get() + - Call document_edit(), providing the path to a new file to replace the document + - Retreive the same item a second time + Verify: + - The digest of the original document bytes does not match the digest of the replacement document bytes + - The the original document filename remains unchanged after the edit + - The edited document's digest matches the digest of the replacement document bytes + """ + item_name = "example document 01" + vault = "Test Data 2" + input_data_path = binary_image_data.data_path_for_name( + "replacement-image-01") + input_data = binary_image_data.data_for_name("replacement-image-01") + input_data_digest = digest(input_data) + + filename_1, data_1 = signed_in_op.document_get(item_name, vault=vault) + digest_1 = digest(data_1) + + # these should be different, else we've messed up + # and the document has already been edited + assert digest_1 != input_data_digest + + # Provide path to the input file + # we'll test providing input bytes separately + signed_in_op.document_edit(item_name, input_data_path, vault=vault) + + filename_2, data_2 = signed_in_op.document_get(item_name, vault=vault) + digest_2 = digest(data_2) + + # filename shouldn't change because we didn't explicitly set it + assert filename_2 == filename_1 + # The updated document digest should now match the digest of the input file + assert digest_2 == input_data_digest + + +@pytest.mark.usefixtures("setup_stateful_document_edit") +def test_document_edit_02(signed_in_op: OP, binary_image_data: BinaryImageData): + """ + Test: OP.document_edit() with the actual bytes of a replacement file + - Retrieve document bytes and filename via OP.document_get() + - Call document_edit(), providing the actual bytes of a new file to replace the document + - Retreive the same item a second time + Verify: + - The digest of the original document bytes does not match the digest of the replacement document bytes + - The the original document filename remains unchanged after the edit + - The edited document's digest matches the digest of the replacement document bytes + """ + item_name = "example document 01" + vault = "Test Data 2" + + input_data = binary_image_data.data_for_name("replacement-image-01") + input_data_digest = digest(input_data) + + filename_1, data_1 = signed_in_op.document_get(item_name, vault=vault) + digest_1 = digest(data_1) + + # these should be different, else we've messed up + # and the document has already been edited + assert digest_1 != input_data_digest + + # Provide the literal bytes of the input file + # we'll test providingthe path to the input file separately + signed_in_op.document_edit(item_name, input_data, vault=vault) + + filename_2, data_2 = signed_in_op.document_get(item_name, vault=vault) + digest_2 = digest(data_2) + + # filename shouldn't change because we didn't explicitly set it + assert filename_2 == filename_1 + # The updated document digest should now match the digest of the input file + assert digest_2 == input_data_digest + + +@pytest.mark.usefixtures("setup_stateful_document_edit") +def test_document_edit_03(signed_in_op: OP, binary_image_data: BinaryImageData): + """ + Test: OP.document_edit() while setting a new item title + - Retrieve document item via OP.item_get() + - Call document_edit(), providing the new file path along with a new title + - Retreive the same item a second time + Verify: + - The title of the original document item does not match the new document title + - The title of the second retrieved item matches the the new document title + """ + item_name = "example document 02" + new_item_title = "example document 02 - updated" + vault = "Test Data 2" + + input_data_path = binary_image_data.data_path_for_name( + "replacement-image-02") + document_item_1: OPDocumentItem = signed_in_op.item_get( + item_name, vault=vault) + assert document_item_1.title != new_item_title + # Provide path to the input file + # we'll test providing input bytes separately + signed_in_op.document_edit( + item_name, input_data_path, new_title=new_item_title, vault=vault) + + document_item_2: OPDocumentItem = signed_in_op.item_get( + new_item_title, vault=vault) + assert document_item_2.title == new_item_title + + +@pytest.mark.usefixtures("setup_stateful_document_edit") +def test_document_edit_04(signed_in_op: OP, binary_image_data: BinaryImageData): + """ + Test: OP.document_edit() while setting a new filename + - Retrieve document item via OP.item_get() + - Call document_edit(), providing the new file path along with a new filename + - Retreive the same item a second time + Verify: + - The filename of the original document item does not match the new filename + - The filename of the second retrieved item matches the the new document filename + """ + item_name = "example document 03" + vault = "Test Data 2" + + input_data_path = binary_image_data.data_path_for_name( + "replacement-image-03") + new_file_name = input_data_path.name + + document_item_1: OPDocumentItem = signed_in_op.item_get( + item_name, vault=vault) + assert document_item_1.file_name != new_file_name + # Provide path to the input file + # we'll test providing input bytes separately + signed_in_op.document_edit( + item_name, input_data_path, file_name=new_file_name, vault=vault) + + document_item_2: OPDocumentItem = signed_in_op.item_get( + item_name, vault=vault) + assert document_item_2.file_name == new_file_name diff --git a/tests/test_op_api/document/edit/test_document_edit_errors.py b/tests/test_op_api/document/edit/test_document_edit_errors.py new file mode 100644 index 00000000..e846b8ed --- /dev/null +++ b/tests/test_op_api/document/edit/test_document_edit_errors.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from pyonepassword import OP + from ....fixtures.binary_input_data import BinaryImageData + +import pytest + +from pyonepassword.api.exceptions import OPDocumentEditException + + +@pytest.mark.usefixtures("valid_op_cli_config_homedir") +def test_document_edit_invalid_document_01(signed_in_op: OP, + binary_image_data: BinaryImageData): + document_name = "Invalid Document" + input_data_path = binary_image_data.data_path_for_name( + "replacement-image-01") + with pytest.raises(OPDocumentEditException): + signed_in_op.document_edit(document_name, input_data_path) diff --git a/tests/test_op_api/document/get/__init__.py b/tests/test_op_api/document/get/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/test_document_get.py b/tests/test_op_api/document/get/test_document_get.py similarity index 98% rename from tests/test_document_get.py rename to tests/test_op_api/document/get/test_document_get.py index 3222c23d..3b0d0c29 100644 --- a/tests/test_document_get.py +++ b/tests/test_op_api/document/get/test_document_get.py @@ -13,7 +13,7 @@ ) from pyonepassword.api.object_types import OPDocumentFile, OPDocumentItem -from .test_support.util import digest +from ....test_support.util import digest pytestmark = pytest.mark.usefixtures("valid_op_cli_config_homedir") diff --git a/tests/test_op_api/test_group_get.py b/tests/test_op_api/group/get/test_group_get.py similarity index 100% rename from tests/test_op_api/test_group_get.py rename to tests/test_op_api/group/get/test_group_get.py diff --git a/tests/test_group_list/test_group_list_all.py b/tests/test_op_api/group/list/test_group_list_all.py similarity index 100% rename from tests/test_group_list/test_group_list_all.py rename to tests/test_op_api/group/list/test_group_list_all.py diff --git a/tests/test_group_list/test_group_list_user.py b/tests/test_op_api/group/list/test_group_list_user.py similarity index 100% rename from tests/test_group_list/test_group_list_user.py rename to tests/test_op_api/group/list/test_group_list_user.py diff --git a/tests/test_group_list/test_group_list_vault.py b/tests/test_op_api/group/list/test_group_list_vault.py similarity index 100% rename from tests/test_group_list/test_group_list_vault.py rename to tests/test_op_api/group/list/test_group_list_vault.py diff --git a/tests/test_item_delete.py b/tests/test_op_api/item/delete/test_item_delete.py similarity index 100% rename from tests/test_item_delete.py rename to tests/test_op_api/item/delete/test_item_delete.py diff --git a/tests/test_item_delete_multiple.py b/tests/test_op_api/item/delete/test_item_delete_multiple.py similarity index 100% rename from tests/test_item_delete_multiple.py rename to tests/test_op_api/item/delete/test_item_delete_multiple.py diff --git a/tests/test_item_edit/test_field_assignment.py b/tests/test_op_api/item/edit/test_field_assignment.py similarity index 100% rename from tests/test_item_edit/test_field_assignment.py rename to tests/test_op_api/item/edit/test_field_assignment.py diff --git a/tests/test_item_edit/test_item_edit.py b/tests/test_op_api/item/edit/test_item_edit.py similarity index 100% rename from tests/test_item_edit/test_item_edit.py rename to tests/test_op_api/item/edit/test_item_edit.py diff --git a/tests/test_item_edit/test_item_edit_add_field.py b/tests/test_op_api/item/edit/test_item_edit_add_field.py similarity index 100% rename from tests/test_item_edit/test_item_edit_add_field.py rename to tests/test_op_api/item/edit/test_item_edit_add_field.py diff --git a/tests/test_item_edit/test_item_edit_delete_field.py b/tests/test_op_api/item/edit/test_item_edit_delete_field.py similarity index 100% rename from tests/test_item_edit/test_item_edit_delete_field.py rename to tests/test_op_api/item/edit/test_item_edit_delete_field.py diff --git a/tests/test_item_edit/test_item_edit_errors.py b/tests/test_op_api/item/edit/test_item_edit_errors.py similarity index 100% rename from tests/test_item_edit/test_item_edit_errors.py rename to tests/test_op_api/item/edit/test_item_edit_errors.py diff --git a/tests/test_item_edit/test_item_edit_set_field_value.py b/tests/test_op_api/item/edit/test_item_edit_set_field_value.py similarity index 100% rename from tests/test_item_edit/test_item_edit_set_field_value.py rename to tests/test_op_api/item/edit/test_item_edit_set_field_value.py diff --git a/tests/test_op_api/test_item_get_login.py b/tests/test_op_api/item/get/test_item_get_login.py similarity index 96% rename from tests/test_op_api/test_item_get_login.py rename to tests/test_op_api/item/get/test_item_get_login.py index 6a93be09..4bb92926 100644 --- a/tests/test_op_api/test_item_get_login.py +++ b/tests/test_op_api/item/get/test_item_get_login.py @@ -7,8 +7,8 @@ if TYPE_CHECKING: from pyonepassword import OP - from ..fixtures.expected_item import ExpectedItemData - from ..fixtures.expected_login import ExpectedLoginItemData + from ....fixtures.expected_item import ExpectedItemData + from ....fixtures.expected_login import ExpectedLoginItemData from pyonepassword.api.exceptions import OPItemGetException from pyonepassword.api.object_types import OPLoginItem diff --git a/tests/test_op_api/test_item_get_totp.py b/tests/test_op_api/item/get/test_item_get_totp.py similarity index 97% rename from tests/test_op_api/test_item_get_totp.py rename to tests/test_op_api/item/get/test_item_get_totp.py index 1218f6b2..51a751ee 100644 --- a/tests/test_op_api/test_item_get_totp.py +++ b/tests/test_op_api/item/get/test_item_get_totp.py @@ -10,7 +10,7 @@ # circular imports. # this also reduced exercising tested code simply by importing if TYPE_CHECKING: - from ..fixtures.expected_totp_data import ExpectedTOTP, ExpectedTOTPData + from ....fixtures.expected_totp_data import ExpectedTOTP, ExpectedTOTPData from pyonepassword import OP from pyonepassword.api.exceptions import OPItemGetException diff --git a/tests/test_item_list/test_item_list_error.py b/tests/test_op_api/item/list/test_item_list_error.py similarity index 100% rename from tests/test_item_list/test_item_list_error.py rename to tests/test_op_api/item/list/test_item_list_error.py diff --git a/tests/test_item_list/test_item_list_include_archive.py b/tests/test_op_api/item/list/test_item_list_include_archive.py similarity index 100% rename from tests/test_item_list/test_item_list_include_archive.py rename to tests/test_op_api/item/list/test_item_list_include_archive.py diff --git a/tests/test_item_list/test_item_list_include_categories.py b/tests/test_op_api/item/list/test_item_list_include_categories.py similarity index 100% rename from tests/test_item_list/test_item_list_include_categories.py rename to tests/test_op_api/item/list/test_item_list_include_categories.py diff --git a/tests/test_item_list/test_item_list_include_tags.py b/tests/test_op_api/item/list/test_item_list_include_tags.py similarity index 100% rename from tests/test_item_list/test_item_list_include_tags.py rename to tests/test_op_api/item/list/test_item_list_include_tags.py diff --git a/tests/test_item_list/test_item_list_misc.py b/tests/test_op_api/item/list/test_item_list_misc.py similarity index 100% rename from tests/test_item_list/test_item_list_misc.py rename to tests/test_op_api/item/list/test_item_list_misc.py diff --git a/tests/test_item_list/test_item_list_password.py b/tests/test_op_api/item/list/test_item_list_password.py similarity index 100% rename from tests/test_item_list/test_item_list_password.py rename to tests/test_op_api/item/list/test_item_list_password.py diff --git a/tests/test_item_list/test_item_list_ssh_key.py b/tests/test_op_api/item/list/test_item_list_ssh_key.py similarity index 100% rename from tests/test_item_list/test_item_list_ssh_key.py rename to tests/test_op_api/item/list/test_item_list_ssh_key.py diff --git a/tests/test_op_api/test_user_get.py b/tests/test_op_api/user/get/test_user_get.py similarity index 94% rename from tests/test_op_api/test_user_get.py rename to tests/test_op_api/user/get/test_user_get.py index a998d60a..8ea7b11e 100644 --- a/tests/test_op_api/test_user_get.py +++ b/tests/test_op_api/user/get/test_user_get.py @@ -6,7 +6,7 @@ if TYPE_CHECKING: from pyonepassword import OP - from ..fixtures.expected_user_data import ExpectedUserData + from ....fixtures.expected_user_data import ExpectedUserData from pyonepassword.api.exceptions import OPUserGetException from pyonepassword.api.object_types import OPUser diff --git a/tests/test_user_list/test_user_list_all.py b/tests/test_op_api/user/list/test_user_list_all.py similarity index 100% rename from tests/test_user_list/test_user_list_all.py rename to tests/test_op_api/user/list/test_user_list_all.py diff --git a/tests/test_user_list/test_user_list_group.py b/tests/test_op_api/user/list/test_user_list_group.py similarity index 100% rename from tests/test_user_list/test_user_list_group.py rename to tests/test_op_api/user/list/test_user_list_group.py diff --git a/tests/test_user_list/test_user_list_vault.py b/tests/test_op_api/user/list/test_user_list_vault.py similarity index 100% rename from tests/test_user_list/test_user_list_vault.py rename to tests/test_op_api/user/list/test_user_list_vault.py diff --git a/tests/test_vault_get.py b/tests/test_op_api/vault/get/test_vault_get.py similarity index 96% rename from tests/test_vault_get.py rename to tests/test_op_api/vault/get/test_vault_get.py index 6372fdd3..191a8aeb 100644 --- a/tests/test_vault_get.py +++ b/tests/test_op_api/vault/get/test_vault_get.py @@ -6,7 +6,7 @@ if TYPE_CHECKING: from pyonepassword import OP - from .fixtures.expected_vault_data import ( + from ....fixtures.expected_vault_data import ( ExpectedVaultData, ExpectedVault ) diff --git a/tests/test_vault_list/test_vault_list_all.py b/tests/test_op_api/vault/list/test_vault_list_all.py similarity index 100% rename from tests/test_vault_list/test_vault_list_all.py rename to tests/test_op_api/vault/list/test_vault_list_all.py diff --git a/tests/test_vault_list/test_vault_list_error.py b/tests/test_op_api/vault/list/test_vault_list_error.py similarity index 100% rename from tests/test_vault_list/test_vault_list_error.py rename to tests/test_op_api/vault/list/test_vault_list_error.py diff --git a/tests/test_vault_list/test_vault_list_group.py b/tests/test_op_api/vault/list/test_vault_list_group.py similarity index 97% rename from tests/test_vault_list/test_vault_list_group.py rename to tests/test_op_api/vault/list/test_vault_list_group.py index c205163f..7c861456 100644 --- a/tests/test_vault_list/test_vault_list_group.py +++ b/tests/test_op_api/vault/list/test_vault_list_group.py @@ -10,7 +10,7 @@ if TYPE_CHECKING: from pyonepassword import OP - from ..fixtures.expected_vault_data import ( + from ...fixtures.expected_vault_data import ( ExpectedVaultListData, ExpectedVaultListEntry ) diff --git a/tests/test_vault_list/test_vault_list_user.py b/tests/test_op_api/vault/list/test_vault_list_user.py similarity index 100% rename from tests/test_vault_list/test_vault_list_user.py rename to tests/test_op_api/vault/list/test_vault_list_user.py