From e87a6b2024b5d42bf410387f4c6957b32f097ecf Mon Sep 17 00:00:00 2001 From: Pablo Panero Date: Wed, 20 Sep 2023 10:49:30 +0200 Subject: [PATCH] migrator: add support for gh connect action --- .../tests/actions/oauth/test_oauth_actions.py | 211 ++++++++++++++---- .../oauth/test_oauth_actions_stream.py | 30 +-- .../testdata/linked_accounts/connect_gh.jsonl | 7 + .../linked_accounts/connect_github.jsonl | 0 .../actions/transform/oauth.py | 57 +++-- 5 files changed, 228 insertions(+), 77 deletions(-) create mode 100644 migrator/tests/actions/oauth/testdata/linked_accounts/connect_gh.jsonl delete mode 100644 migrator/tests/actions/oauth/testdata/linked_accounts/connect_github.jsonl diff --git a/migrator/tests/actions/oauth/test_oauth_actions.py b/migrator/tests/actions/oauth/test_oauth_actions.py index 5a7b3b90..286f9296 100644 --- a/migrator/tests/actions/oauth/test_oauth_actions.py +++ b/migrator/tests/actions/oauth/test_oauth_actions.py @@ -660,40 +660,87 @@ def connect_orcid_oauth_application_tx(): return {"tx_id": 1, "operations": ops} +@pytest.fixture() +def connect_gh_oauth_application_tx(): + """Transaction data to connect an OAuth ORCID account. + + As it would be after the extraction step. + """ + datafile = ( + Path(__file__).parent / "testdata" / "linked_accounts" / "connect_gh.jsonl" + ) + with open(datafile, "rb") as reader: + ops = [orjson.loads(line)["value"] for line in reader] + + return {"tx_id": 1, "operations": ops} + + class TestOAuthLinkedAccountConnectAction: """Connect an OAuth account action tests.""" def test_matches_with_valid_data(self): - assert ( - OAuthLinkedAccountConnectAction.matches_action( - Tx( - id=1, - operations=[ - { - "op": OperationType.INSERT, - "source": {"table": "oauthclient_remoteaccount"}, - "after": {}, - }, - { - "op": OperationType.INSERT, - "source": {"table": "oauthclient_remotetoken"}, - "after": {}, - }, - { - "op": OperationType.INSERT, - "source": {"table": "oauthclient_useridentity"}, - "after": {}, - }, - { - "op": OperationType.UPDATE, - "source": {"table": "oauthclient_remoteaccount"}, - "after": {}, - }, - ], + full = [ + { + "op": OperationType.INSERT, + "source": {"table": "oauthclient_remoteaccount"}, + "after": {}, + }, + { + "op": OperationType.INSERT, + "source": {"table": "oauthclient_remotetoken"}, + "after": {}, + }, + { + "op": OperationType.INSERT, + "source": {"table": "oauthclient_useridentity"}, + "after": {}, + }, + { + "op": OperationType.UPDATE, + "source": {"table": "oauthclient_remoteaccount"}, + "after": {}, + }, + { + "op": OperationType.INSERT, + "source": {"table": "oauth2server_client"}, + "after": {}, + }, + { + "op": OperationType.INSERT, + "source": {"table": "oauth2server_token"}, + "after": {}, + }, + { + "op": OperationType.UPDATE, + "source": {"table": "oauthclient_remoteaccount"}, + "after": {}, + }, + ] + minimal = [ + { + "op": OperationType.INSERT, + "source": {"table": "oauthclient_remoteaccount"}, + "after": {}, + }, + { + "op": OperationType.INSERT, + "source": {"table": "oauthclient_remotetoken"}, + "after": {}, + }, + { + "op": OperationType.INSERT, + "source": {"table": "oauthclient_useridentity"}, + "after": {}, + }, + ] + + for valid_ops in [minimal]: + assert ( + OAuthLinkedAccountConnectAction.matches_action( + Tx(id=1, operations=valid_ops) ) + is True ) - is True - ) def test_matches_with_invalid_data(self): empty = [] @@ -709,14 +756,35 @@ def test_matches_with_invalid_data(self): "source": {"table": "oauthclient_useridentity"}, "after": {}, }, + ] + + no_token = [ { - "op": OperationType.UPDATE, + "op": OperationType.INSERT, "source": {"table": "oauthclient_remoteaccount"}, "after": {}, }, + { + "op": OperationType.INSERT, + "source": {"table": "oauthclient_useridentity"}, + "after": {}, + }, ] - no_account_update = [ + no_user_identity = [ + { + "op": OperationType.INSERT, + "source": {"table": "oauthclient_remoteaccount"}, + "after": {}, + }, + { + "op": OperationType.INSERT, + "source": {"table": "oauthclient_remotetoken"}, + "after": {}, + }, + ] + + no_server_token = [ { "op": OperationType.INSERT, "source": {"table": "oauthclient_remoteaccount"}, @@ -732,9 +800,14 @@ def test_matches_with_invalid_data(self): "source": {"table": "oauthclient_useridentity"}, "after": {}, }, + { + "op": OperationType.INSERT, + "source": {"table": "oauth2server_client"}, + "after": {}, + }, ] - double_insert = [ + no_server_client = [ { "op": OperationType.INSERT, "source": {"table": "oauthclient_remoteaccount"}, @@ -752,14 +825,14 @@ def test_matches_with_invalid_data(self): }, { "op": OperationType.INSERT, - "source": {"table": "oauthclient_remoteaccount"}, + "source": {"table": "oauth2server_token"}, "after": {}, }, ] - double_update = [ + wrong_update_op = [ { - "op": OperationType.UPDATE, + "op": OperationType.INSERT, "source": {"table": "oauthclient_remoteaccount"}, "after": {}, }, @@ -778,14 +851,34 @@ def test_matches_with_invalid_data(self): "source": {"table": "oauthclient_remoteaccount"}, "after": {}, }, + { + "op": OperationType.INSERT, + "source": {"table": "oauth2server_client"}, + "after": {}, + }, + { + "op": OperationType.INSERT, + "source": {"table": "oauth2server_token"}, + "after": {}, + }, + { + "op": OperationType.UPDATE, # wrong + "source": {"table": "oauth2server_token"}, + "after": {}, + }, ] - no_token = [ + extra_update = [ { "op": OperationType.INSERT, "source": {"table": "oauthclient_remoteaccount"}, "after": {}, }, + { + "op": OperationType.INSERT, + "source": {"table": "oauthclient_remotetoken"}, + "after": {}, + }, { "op": OperationType.INSERT, "source": {"table": "oauthclient_useridentity"}, @@ -796,27 +889,24 @@ def test_matches_with_invalid_data(self): "source": {"table": "oauthclient_remoteaccount"}, "after": {}, }, - ] - - no_user_identity = [ { "op": OperationType.INSERT, - "source": {"table": "oauthclient_remoteaccount"}, + "source": {"table": "oauth2server_client"}, "after": {}, }, { "op": OperationType.INSERT, - "source": {"table": "oauthclient_remotetoken"}, + "source": {"table": "oauth2server_token"}, "after": {}, }, { "op": OperationType.UPDATE, - "source": {"table": "oauthclient_remoteaccount"}, + "source": {"table": "another"}, "after": {}, }, ] - wrong_op = [ + extra_insert = [ { "op": OperationType.INSERT, "source": {"table": "oauthclient_remoteaccount"}, @@ -828,7 +918,7 @@ def test_matches_with_invalid_data(self): "after": {}, }, { - "op": OperationType.UPDATE, + "op": OperationType.INSERT, "source": {"table": "oauthclient_useridentity"}, "after": {}, }, @@ -837,17 +927,33 @@ def test_matches_with_invalid_data(self): "source": {"table": "oauthclient_remoteaccount"}, "after": {}, }, + { + "op": OperationType.INSERT, + "source": {"table": "oauth2server_client"}, + "after": {}, + }, + { + "op": OperationType.INSERT, + "source": {"table": "oauth2server_token"}, + "after": {}, + }, + { + "op": OperationType.INSERT, + "source": {"table": "another"}, + "after": {}, + }, ] for invalid_ops in [ empty, no_account, - no_account_update, - double_insert, - double_update, no_token, no_user_identity, - wrong_op, + no_server_token, + no_server_client, + wrong_update_op, + extra_update, + extra_insert, ]: assert ( OAuthLinkedAccountConnectAction.matches_action( @@ -856,7 +962,7 @@ def test_matches_with_invalid_data(self): is False ) - def test_transform_with_valid_data(self, connect_orcid_oauth_application_tx): + def test_transform_with_valid_orcid_data(self, connect_orcid_oauth_application_tx): action = OAuthLinkedAccountConnectAction( Tx( id=connect_orcid_oauth_application_tx["tx_id"], @@ -865,6 +971,15 @@ def test_transform_with_valid_data(self, connect_orcid_oauth_application_tx): ) assert isinstance(action.transform(), load.OAuthLinkedAccountConnectAction) + def test_transform_with_valid_gh_data(self, connect_gh_oauth_application_tx): + action = OAuthLinkedAccountConnectAction( + Tx( + id=connect_gh_oauth_application_tx["tx_id"], + operations=connect_gh_oauth_application_tx["operations"], + ) + ) + assert isinstance(action.transform(), load.OAuthLinkedAccountConnectAction) + @pytest.fixture() def disconnect_orcid_oauth_application_tx(): diff --git a/migrator/tests/actions/oauth/test_oauth_actions_stream.py b/migrator/tests/actions/oauth/test_oauth_actions_stream.py index 980de80c..d6a0c455 100644 --- a/migrator/tests/actions/oauth/test_oauth_actions_stream.py +++ b/migrator/tests/actions/oauth/test_oauth_actions_stream.py @@ -250,20 +250,22 @@ def test_oauth_linked_app_disconnect_orcid_action_stream( # -# def test_oauth_linked_app_connect_gh_action_stream( -# db_client_server, pg_tx_load, test_extract_cls, tx_files_linked_accounts -# ): -# stream = Stream( -# name="action", -# extract=test_extract_cls(tx_files_linked_accounts["connect_gh"]), -# transform=ZenodoTxTransform(), -# load=pg_tx_load, -# ) -# stream.run() - -# assert db_client_server.scalars(sa.select(RemoteAccount)).one() -# assert db_client_server.scalars(sa.select(RemoteToken)).one() -# assert db_client_server.scalars(sa.select(UserIdentity)).one() +def test_oauth_linked_app_connect_gh_action_stream( + db_client_server, pg_tx_load, test_extract_cls, tx_files_linked_accounts +): + stream = Stream( + name="action", + extract=test_extract_cls(tx_files_linked_accounts["connect_gh"]), + transform=ZenodoTxTransform(), + load=pg_tx_load, + ) + stream.run() + + assert db_client_server.scalars(sa.select(RemoteAccount)).one() + assert db_client_server.scalars(sa.select(RemoteToken)).one() + assert db_client_server.scalars(sa.select(UserIdentity)).one() + assert db_client_server.scalars(sa.select(ServerClient)).one() + assert db_client_server.scalars(sa.select(ServerToken)).one() @pytest.fixture(scope="function") diff --git a/migrator/tests/actions/oauth/testdata/linked_accounts/connect_gh.jsonl b/migrator/tests/actions/oauth/testdata/linked_accounts/connect_gh.jsonl new file mode 100644 index 00000000..9b6be3d4 --- /dev/null +++ b/migrator/tests/actions/oauth/testdata/linked_accounts/connect_gh.jsonl @@ -0,0 +1,7 @@ +{"topic": "zenodo-qa.public", "partition": 0, "offset": 13224, "timestamp": 1695130336914, "timestamp_type": 0, "key": {"id": 8553, "__dbz__physicalTableIdentifier": "zenodo-qa.public.oauthclient_remoteaccount"}, "value": {"before": null, "after": {"id": 8553, "user_id": 86490, "client_id": "64a3663a0ac1183598ce", "extra_data": "{}", "created": 1695130329996553, "updated": 1695130329996567}, "source": {"version": "2.3.0.Final", "connector": "postgresql", "name": "zenodo-qa", "ts_ms": 1695130336322, "snapshot": "false", "db": "zenodo", "sequence": "[\"1472993974464\",\"1472993977728\"]", "schema": "public", "table": "oauthclient_remoteaccount", "txId": 563876034, "lsn": 1472993977728, "xmin": null}, "op": "c", "ts_ms": 1695130336533, "transaction": {"id": "563876034:1472993977728", "total_order": 1, "data_collection_order": 1}}, "headers": [], "checksum": null, "serialized_key_size": 89, "serialized_value_size": 566, "serialized_header_size": -1} +{"topic": "zenodo-qa.public", "partition": 0, "offset": 13225, "timestamp": 1695130336914, "timestamp_type": 0, "key": {"id_remote_account": 8553, "token_type": "", "__dbz__physicalTableIdentifier": "zenodo-qa.public.oauthclient_remotetoken"}, "value": {"before": null, "after": {"id_remote_account": 8553, "token_type": "", "access_token": "Z04wTFQvUnpjUzd5alg2V3krMzRVUE9sTEpidnVEZjNNSXFCNmxSamdUcVZXU3IvRm1HVGFwRi9jN0FaSW9ZWA==", "secret": "", "created": 1695130329999576, "updated": 1695130329999584}, "source": {"version": "2.3.0.Final", "connector": "postgresql", "name": "zenodo-qa", "ts_ms": 1695130336322, "snapshot": "false", "db": "zenodo", "sequence": "[\"1472993974464\",\"1472994003224\"]", "schema": "public", "table": "oauthclient_remotetoken", "txId": 563876034, "lsn": 1472994003224, "xmin": null}, "op": "c", "ts_ms": 1695130336534, "transaction": {"id": "563876034:1472994003224", "total_order": 2, "data_collection_order": 1}}, "headers": [], "checksum": null, "serialized_key_size": 118, "serialized_value_size": 644, "serialized_header_size": -1} +{"topic": "zenodo-qa.public", "partition": 0, "offset": 13226, "timestamp": 1695130336914, "timestamp_type": 0, "key": {"client_id": "rKmVKlRxnQJfyizWeVKRO26cZjLqd2yWhsBFkjv0", "__dbz__physicalTableIdentifier": "zenodo-qa.public.oauth2server_client"}, "value": {"before": null, "after": {"name": "github-webhook", "description": "", "website": "", "user_id": 86490, "client_id": "rKmVKlRxnQJfyizWeVKRO26cZjLqd2yWhsBFkjv0", "client_secret": "HEK4W0bk2Px8xBwVUnY1S4ze8TvuWLy9NeIrcoWAP2H177cJtm3GIvdpSHTi", "is_confidential": false, "is_internal": true, "_redirect_uris": null, "_default_scopes": "webhooks:event"}, "source": {"version": "2.3.0.Final", "connector": "postgresql", "name": "zenodo-qa", "ts_ms": 1695130336322, "snapshot": "false", "db": "zenodo", "sequence": "[\"1472993974464\",\"1472994016016\"]", "schema": "public", "table": "oauth2server_client", "txId": 563876034, "lsn": 1472994016016, "xmin": null}, "op": "c", "ts_ms": 1695130336534, "transaction": {"id": "563876034:1472994016016", "total_order": 3, "data_collection_order": 1}}, "headers": [], "checksum": null, "serialized_key_size": 128, "serialized_value_size": 731, "serialized_header_size": -1} +{"topic": "zenodo-qa.public", "partition": 0, "offset": 13227, "timestamp": 1695130336914, "timestamp_type": 0, "key": {"id": 157734, "__dbz__physicalTableIdentifier": "zenodo-qa.public.oauth2server_token"}, "value": {"before": null, "after": {"id": 157734, "client_id": "rKmVKlRxnQJfyizWeVKRO26cZjLqd2yWhsBFkjv0", "user_id": 86490, "token_type": "bearer", "access_token": "dHRMMTN5K3dUTCtBMVcyMFhaRHFJa1prYnJ1YkN0Zlo4cVFmU1d1NXlTUnAveDE3WVFKcFBUeUJHTmFnQTh2emdpcG1idG1ZbTJpeFgzeG5nVGsrTHc9PQ==", "refresh_token": null, "expires": null, "_scopes": "webhooks:event", "is_personal": true, "is_internal": true}, "source": {"version": "2.3.0.Final", "connector": "postgresql", "name": "zenodo-qa", "ts_ms": 1695130336322, "snapshot": "false", "db": "zenodo", "sequence": "[\"1472993974464\",\"1472994034632\"]", "schema": "public", "table": "oauth2server_token", "txId": 563876034, "lsn": 1472994034632, "xmin": null}, "op": "c", "ts_ms": 1695130336534, "transaction": {"id": "563876034:1472994034632", "total_order": 4, "data_collection_order": 1}}, "headers": [], "checksum": null, "serialized_key_size": 84, "serialized_value_size": 770, "serialized_header_size": -1} +{"topic": "zenodo-qa.public", "partition": 0, "offset": 13228, "timestamp": 1695130336914, "timestamp_type": 0, "key": {"id": 8553, "__dbz__physicalTableIdentifier": "zenodo-qa.public.oauthclient_remoteaccount"}, "value": {"before": {"id": 8553, "user_id": 86490, "client_id": "64a3663a0ac1183598ce", "extra_data": "{}", "created": 1695130329996553, "updated": 1695130329996567}, "after": {"id": 8553, "user_id": 86490, "client_id": "64a3663a0ac1183598ce", "extra_data": "{\"tokens\": {\"webhook\": 157734}, \"name\": \"Pablo Panero\", \"login\": \"ppanero\", \"repos\": {}, \"id\": 6756943, \"last_sync\": \"2023-09-19T13:32:10.312546+00:00\"}", "created": 1695130329996553, "updated": 1695130336277950}, "source": {"version": "2.3.0.Final", "connector": "postgresql", "name": "zenodo-qa", "ts_ms": 1695130336322, "snapshot": "false", "db": "zenodo", "sequence": "[\"1472993974464\",\"1472994060816\"]", "schema": "public", "table": "oauthclient_remoteaccount", "txId": 563876034, "lsn": 1472994060816, "xmin": null}, "op": "u", "ts_ms": 1695130336534, "transaction": {"id": "563876034:1472994060816", "total_order": 5, "data_collection_order": 2}}, "headers": [], "checksum": null, "serialized_key_size": 89, "serialized_value_size": 866, "serialized_header_size": -1} +{"topic": "zenodo-qa.public", "partition": 0, "offset": 13229, "timestamp": 1695130336914, "timestamp_type": 0, "key": {"id": 8553, "__dbz__physicalTableIdentifier": "zenodo-qa.public.oauthclient_remoteaccount"}, "value": {"before": {"id": 8553, "user_id": 86490, "client_id": "64a3663a0ac1183598ce", "extra_data": "{\"tokens\": {\"webhook\": 157734}, \"name\": \"Pablo Panero\", \"login\": \"ppanero\", \"repos\": {}, \"id\": 6756943, \"last_sync\": \"2023-09-19T13:32:10.312546+00:00\"}", "created": 1695130329996553, "updated": 1695130336277950}, "after": {"id": 8553, "user_id": 86490, "client_id": "64a3663a0ac1183598ce", "extra_data": "{\"tokens\": {\"webhook\": 157734}, \"name\": \"Pablo Panero\", \"login\": \"ppanero\", \"repos\": {\"252459525\": {\"default_branch\": \"master\", \"id\": 252459525, \"full_name\": \"ppanero/invenio-drafts\", \"description\": null}, \"83563583\": {\"default_branch\": \"master\", \"id\": 83563583, \"full_name\": \"ppanero/minidns\", \"description\": \"DNS library for Android and Java SE\"}, \"63950352\": {\"default_branch\": \"2.4\", \"id\": 63950352, \"full_name\": \"ppanero/MISP\", \"description\": \"MISP - Malware Information Sharing Platform & Threat Sharing\"}, \"262068245\": {\"default_branch\": \"master\", \"id\": 262068245, \"full_name\": \"ppanero/base32-lib\", \"description\": \"Library to generate, encode and decode random base32 strings.\"}, \"44679702\": {\"default_branch\": \"master\", \"id\": 44679702, \"full_name\": \"zenodo/zenodo-accessrequests\", \"description\": \"Zenodo access requests module.\"}, \"267876888\": {\"default_branch\": \"master\", \"id\": 267876888, \"full_name\": \"ppanero/invenio-theme\", \"description\": \"Invenio standard theme.\"}, \"317860377\": {\"default_branch\": \"master\", \"id\": 317860377, \"full_name\": \"ppanero/invenio-i18n\", \"description\": \"Invenio internationalization module.\"}, \"386573852\": {\"default_branch\": \"master\", \"id\": 386573852, \"full_name\": \"ppanero/elasticsearch-py\", \"description\": \"Official Python low-level client for Elasticsearch\"}, \"277326366\": {\"default_branch\": \"development\", \"id\": 277326366, \"full_name\": \"ppanero/ppanero.github.io\", \"description\": null}, \"614311967\": {\"default_branch\": \"master\", \"id\": 614311967, \"full_name\": \"ppanero/invenio-stats\", \"description\": \"Statistical data processing and querying for Invenio.\"}, \"219780130\": {\"default_branch\": \"master\", \"id\": 219780130, \"full_name\": \"ppanero/opensource\", \"description\": \"Repository for issues, documentation and other matters related to management of open source at Invenio Software.\"}, \"89223205\": {\"default_branch\": \"master\", \"id\": 89223205, \"full_name\": \"ppanero/puppet-misp\", \"description\": \"This module installs and configures MISP (Malware Information Sharing Platform)\"}, \"33995815\": {\"default_branch\": \"master\", \"id\": 33995815, \"full_name\": \"ppanero/J2EE_Training\", \"description\": \"Basic J2EE applications examples\"}, \"160211508\": {\"default_branch\": \"master\", \"id\": 160211508, \"full_name\": \"ppanero/invenio-indexer\", \"description\": \"Record indexer for Invenio.\"}, \"262090813\": {\"default_branch\": \"master\", \"id\": 262090813, \"full_name\": \"ppanero/pytest-invenio\", \"description\": \"Pytest fixtures for Invenio.\"}, \"259575871\": {\"default_branch\": \"master\", \"id\": 259575871, \"full_name\": \"ppanero/flask-resources\", \"description\": \"REST APIs for Flask\"}, \"161791042\": {\"default_branch\": \"master\", \"id\": 161791042, \"full_name\": \"ppanero/invenio-logging\", \"description\": \"Invenio logging module.\"}, \"490748487\": {\"default_branch\": \"master\", \"id\": 490748487, \"full_name\": \"ppanero/zenodo-docs-user\", \"description\": \"Zenodo User Documentation\"}, \"261774920\": {\"default_branch\": \"master\", \"id\": 261774920, \"full_name\": \"ppanero/invenio-config\", \"description\": \"Invenio configuration loading module.\"}, \"261176076\": {\"default_branch\": \"master\", \"id\": 261176076, \"full_name\": \"ppanero/invenio-oauth2server\", \"description\": \"Invenio modules that implements OAuth 2 server.\"}, \"69670989\": {\"default_branch\": \"master\", \"id\": 69670989, \"full_name\": \"ppanero/sdg_hunter\", \"description\": null}, \"247984718\": {\"default_branch\": \"master\", \"id\": 247984718, \"full_name\": \"zenodo/zenodo-rdm\", \"description\": \"Zenodo Invenio RDM instance\"}, \"79244368\": {\"default_branch\": \"master\", \"id\": 79244368, \"full_name\": \"zenodo/zenodo-docs-user\", \"description\": \"Zenodo User Documentation\"}, \"94411345\": {\"default_branch\": \"master\", \"id\": 94411345, \"full_name\": \"zenodo/zenodo-classifier\", \"description\": null}, \"263002194\": {\"default_branch\": \"master\", \"id\": 263002194, \"full_name\": \"ppanero/pywebpack\", \"description\": \"Webpack integration layer for Python.\"}, \"261972579\": {\"default_branch\": \"master\", \"id\": 261972579, \"full_name\": \"ppanero/dojson\", \"description\": \"Simple pythonic JSON to JSON converter.\"}, \"274697819\": {\"default_branch\": \"master\", \"id\": 274697819, \"full_name\": \"ppanero/invenio-records-agent\", \"description\": null}, \"49374300\": {\"default_branch\": \"master\", \"id\": 49374300, \"full_name\": \"ppanero/diseasemeter\", \"description\": null}, \"206281822\": {\"default_branch\": \"master\", \"id\": 206281822, \"full_name\": \"ppanero/invenio-rdm-records\", \"description\": \"DataCite-based data model for InvenioRDM flavour.\"}, \"263034466\": {\"default_branch\": \"master\", \"id\": 263034466, \"full_name\": \"ppanero/invenio-userprofiles\", \"description\": \"User profiles module for Invenio.\"}, \"246798947\": {\"default_branch\": \"master\", \"id\": 246798947, \"full_name\": \"ppanero/helm-chart-test\", \"description\": null}, \"121617508\": {\"default_branch\": \"master\", \"id\": 121617508, \"full_name\": \"ppanero/invenio-records-rest\", \"description\": \"Invenio records REST API module.\"}, \"444398708\": {\"default_branch\": \"main\", \"id\": 444398708, \"full_name\": \"ppanero/admin-war\", \"description\": \"Administration raffle, with a Pokemon UX\"}, \"220490874\": {\"default_branch\": \"master\", \"id\": 220490874, \"full_name\": \"ppanero/cookiecutter\", \"description\": \"A command-line utility that creates projects from cookiecutters (project templates), e.g. Python package projects, jQuery plugin projects.\"}, \"255355516\": {\"default_branch\": \"master\", \"id\": 255355516, \"full_name\": \"ppanero/fosc\", \"description\": \"Field Of Study Classification (FOSC)\"}, \"138305154\": {\"default_branch\": \"master\", \"id\": 138305154, \"full_name\": \"ppanero/invenio-db\", \"description\": \"Database management for Invenio.\"}, \"234055814\": {\"default_branch\": \"demo\", \"id\": 234055814, \"full_name\": \"ppanero/invenio-rdm-extension-demo\", \"description\": \"Invenio module to showcase how to add an extension to InvenioRDM\"}, \"31359116\": {\"default_branch\": \"master\", \"id\": 31359116, \"full_name\": \"ppanero/reversi_algorithm_AI\", \"description\": \"This is the implementation of an algorithm to play reversi/othello game. \"}, \"261788817\": {\"default_branch\": \"master\", \"id\": 261788817, \"full_name\": \"ppanero/datacite\", \"description\": \"Python API wrapper for the DataCite Metadata Store API.\"}, \"317593235\": {\"default_branch\": \"master\", \"id\": 317593235, \"full_name\": \"ppanero/invenio-records-ui\", \"description\": \"Invenio records user interface module.\"}, \"244578965\": {\"default_branch\": \"master\", \"id\": 244578965, \"full_name\": \"ppanero/training\", \"description\": \"Invenio v3 Training Material\"}, \"261783192\": {\"default_branch\": \"master\", \"id\": 261783192, \"full_name\": \"ppanero/citeproc-py-styles\", \"description\": \"CSL styles for citeproc-py.\"}, \"337696623\": {\"default_branch\": \"master\", \"id\": 337696623, \"full_name\": \"ppanero/accordion-webpack-test\", \"description\": \"Test repository to showcase an issue\"}, \"299837084\": {\"default_branch\": \"master\", \"id\": 299837084, \"full_name\": \"ppanero/marshmallow-utils\", \"description\": \"Extras and utilities for Marshmallow\"}, \"212821857\": {\"default_branch\": \"master\", \"id\": 212821857, \"full_name\": \"ppanero/invenio-cli\", \"description\": \"Cache module for Invenio\"}, \"281715368\": {\"default_branch\": \"master\", \"id\": 281715368, \"full_name\": \"ppanero/invenio-rest\", \"description\": \"REST API support for Invenio.\"}, \"306261165\": {\"default_branch\": \"master\", \"id\": 306261165, \"full_name\": \"ppanero/zenodo-spam-classifier\", \"description\": \"Zenodo Spam Classifier\"}, \"240457390\": {\"default_branch\": \"master\", \"id\": 240457390, \"full_name\": \"ppanero/docs-invenio-rdm\", \"description\": null}, \"85076660\": {\"default_branch\": \"develop\", \"id\": 85076660, \"full_name\": \"ppanero/storehaus\", \"description\": \"Storehaus is a library that makes it easy to work with asynchronous key value stores\"}, \"45678263\": {\"default_branch\": \"master\", \"id\": 45678263, \"full_name\": \"zenodo/zenodo-migrator\", \"description\": \"Zenodo module for migrating data into Invenio 3.\"}, \"317466297\": {\"default_branch\": \"master\", \"id\": 317466297, \"full_name\": \"ppanero/invenio-search-ui\", \"description\": \"UI for Invenio Search.\"}, \"602099393\": {\"default_branch\": \"master\", \"id\": 602099393, \"full_name\": \"ppanero/invenio-banners\", \"description\": \"Create and show banners with useful messages to users.\"}, \"261752517\": {\"default_branch\": \"master\", \"id\": 261752517, \"full_name\": \"ppanero/invenio-previewer\", \"description\": \"Invenio module for previewing files.\"}, \"319332551\": {\"default_branch\": \"master\", \"id\": 319332551, \"full_name\": \"ppanero/invenio-pidrelations\", \"description\": \"**WORK IN PROGRESS** - warning: repo can be squashed and force pushed!\"}, \"248186658\": {\"default_branch\": \"master\", \"id\": 248186658, \"full_name\": \"ppanero/demo-inveniordm\", \"description\": \"Demosite for InvenioRDM.\"}, \"218804088\": {\"default_branch\": \"master\", \"id\": 218804088, \"full_name\": \"ppanero/cookiecutter-invenio-rdm\", \"description\": \"Cookiecutter template for a new Invenio RDM instance.\"}, \"94749400\": {\"default_branch\": \"master\", \"id\": 94749400, \"full_name\": \"ppanero/coursera-big-data-analysis-with-spark\", \"description\": \"Repository with the summaries and programming assigments of the Coursera courses I have completed\"}, \"662918362\": {\"default_branch\": \"main\", \"id\": 662918362, \"full_name\": \"ppanero/django-playground\", \"description\": \"Just playing with Django\"}, \"79111899\": {\"default_branch\": \"master\", \"id\": 79111899, \"full_name\": \"zenodo/about.zenodo.org\", \"description\": \"Zenodo About Site\"}, \"427018972\": {\"default_branch\": \"main\", \"id\": 427018972, \"full_name\": \"ppanero/zenodo-release-test\", \"description\": null}, \"235594976\": {\"default_branch\": \"master\", \"id\": 235594976, \"full_name\": \"ppanero/invenio-s3\", \"description\": \"S3 file storage support for Invenio.\"}, \"244679632\": {\"default_branch\": \"master\", \"id\": 244679632, \"full_name\": \"ppanero/invenio-oaiserver\", \"description\": \"Invenio module that adds more fun to the platform.\"}, \"568834279\": {\"default_branch\": \"master\", \"id\": 568834279, \"full_name\": \"ppanero/invenio-rdm-migrator\", \"description\": \"Migration module for InvenioRDM.\"}, \"262254313\": {\"default_branch\": \"master\", \"id\": 262254313, \"full_name\": \"ppanero/invenio-jsonschemas\", \"description\": \"Invenio Schema Registry\"}, \"160536815\": {\"default_branch\": \"master\", \"id\": 160536815, \"full_name\": \"ppanero/cookiecutter-invenio-datamodel\", \"description\": \"Cookiecutter template for an Invenio data model.\"}, \"156718325\": {\"default_branch\": \"master\", \"id\": 156718325, \"full_name\": \"ppanero/invenio-app\", \"description\": \"WSGI, Celery and CLI applications for Invenio flavours.\"}, \"347623159\": {\"default_branch\": \"main\", \"id\": 347623159, \"full_name\": \"ppanero/coding-challenges\", \"description\": \"Coding challenges, interview style!\"}, \"105632504\": {\"default_branch\": \"master\", \"id\": 105632504, \"full_name\": \"ppanero/Cortex-Analyzers\", \"description\": \"Cortex Analyzers Repository\"}, \"237186300\": {\"default_branch\": \"master\", \"id\": 237186300, \"full_name\": \"ppanero/invenio-files-rest\", \"description\": \"REST API for uploading/downloading files for Invenio.\"}, \"189557504\": {\"default_branch\": \"master\", \"id\": 189557504, \"full_name\": \"ppanero/docker-invenio\", \"description\": \"Docker base images for Invenio.\"}, \"243214088\": {\"default_branch\": \"master\", \"id\": 243214088, \"full_name\": \"ppanero/flask-limiter\", \"description\": \"rate limiting extension for flask applications\"}, \"179234572\": {\"default_branch\": \"master\", \"id\": 179234572, \"full_name\": \"ppanero/invenio-records\", \"description\": \"Invenio-Records is a metadata storage module.\"}, \"79111955\": {\"default_branch\": \"master\", \"id\": 79111955, \"full_name\": \"zenodo/developers.zenodo.org\", \"description\": \"Zenodo Developers Site\"}, \"261704983\": {\"default_branch\": \"master\", \"id\": 261704983, \"full_name\": \"ppanero/flask-kvsession\", \"description\": \"A drop-in replacement for Flask's session handling using server-side sessions.\"}, \"289911577\": {\"default_branch\": \"master\", \"id\": 289911577, \"full_name\": \"ppanero/docker-services-cli\", \"description\": \"Infrastruce services for local and CI tests.\"}, \"261974298\": {\"default_branch\": \"master\", \"id\": 261974298, \"full_name\": \"ppanero/flask-menu\", \"description\": \"Flask-Menu is a Flask extension that adds support for generating menus.\"}, \"246600478\": {\"default_branch\": \"master\", \"id\": 246600478, \"full_name\": \"ppanero/helm-invenio\", \"description\": \"PROTOTYPE\"}, \"309361953\": {\"default_branch\": \"master\", \"id\": 309361953, \"full_name\": \"ppanero/react-invenio-forms\", \"description\": \"React component library for Formik components.\"}, \"37220642\": {\"default_branch\": \"master\", \"id\": 37220642, \"full_name\": \"ppanero/Personal_accounting\", \"description\": \"This is a basic personal accounting (incomes, expenses managemente, etc.) RESTful API with a basic JavaScript client.\"}, \"8135462\": {\"default_branch\": \"master\", \"id\": 8135462, \"full_name\": \"zenodo/zenodo\", \"description\": \"Research. Shared.\"}, \"319348010\": {\"default_branch\": \"master\", \"id\": 319348010, \"full_name\": \"ppanero/invenio-celery\", \"description\": \"Integration layer between Celery and Invenio.\"}, \"132122929\": {\"default_branch\": \"master\", \"id\": 132122929, \"full_name\": \"ppanero/invenio-oauthclient\", \"description\": \"Invenio module that provides OAuth web authorization support.\"}, \"195800883\": {\"default_branch\": \"master\", \"id\": 195800883, \"full_name\": \"ppanero/zenodo\", \"description\": \"Research. Shared.\"}, \"278560566\": {\"default_branch\": \"master\", \"id\": 278560566, \"full_name\": \"ppanero/invenio-files-processor\", \"description\": \"PROTOTYPE!!! Invenio module for file processing tasks.\"}, \"279229752\": {\"default_branch\": \"master\", \"id\": 279229752, \"full_name\": \"ppanero/invenio-drafts-resources\", \"description\": null}, \"259586362\": {\"default_branch\": \"master\", \"id\": 259586362, \"full_name\": \"ppanero/invenio-records-resources\", \"description\": \"REST APIs for Invenio.\"}, \"457732924\": {\"default_branch\": \"master\", \"id\": 457732924, \"full_name\": \"ppanero/invenio-queues\", \"description\": \"PROTOTYPE!!! Do not use this module yet.\"}, \"332469567\": {\"default_branch\": \"master\", \"id\": 332469567, \"full_name\": \"ppanero/invenio\", \"description\": \"Invenio digital library framework\"}, \"32339268\": {\"default_branch\": \"master\", \"id\": 32339268, \"full_name\": \"ppanero/com.aware.plugin.io\", \"description\": \"Plugin for the AWARE framework for distinction between\\nindoor and outdoor placement\"}, \"483147589\": {\"default_branch\": \"master\", \"id\": 483147589, \"full_name\": \"ppanero/invenio-users-resources\", \"description\": \"Invenio module which provides a REST API for managing users and groups.\"}, \"170124615\": {\"default_branch\": \"master\", \"id\": 170124615, \"full_name\": \"ppanero/react-searchkit\", \"description\": \"React components to build your search UI application.\"}, \"161789773\": {\"default_branch\": \"master\", \"id\": 161789773, \"full_name\": \"ppanero/invenio-assets\", \"description\": \"Invenio media assets management module.\"}, \"111808334\": {\"default_branch\": \"trunk\", \"id\": 111808334, \"full_name\": \"ppanero/kafka\", \"description\": \"Mirror of Apache Kafka\"}, \"438953297\": {\"default_branch\": \"master\", \"id\": 438953297, \"full_name\": \"ppanero/invenio-requests\", \"description\": null}, \"127255896\": {\"default_branch\": \"master\", \"id\": 127255896, \"full_name\": \"ppanero/invenio-search\", \"description\": \"Invenio module for information retrieval.\"}, \"161787740\": {\"default_branch\": \"master\", \"id\": 161787740, \"full_name\": \"ppanero/invenio-admin\", \"description\": \"Invenio admin module.\"}, \"337784157\": {\"default_branch\": \"main\", \"id\": 337784157, \"full_name\": \"ppanero/coursera-programming-in-golang\", \"description\": \"Code produced as part of the Coursera Specialization [\\\"Programming with Google Go](https://www.coursera.org/specializations/google-golang).\"}, \"178234206\": {\"default_branch\": \"master\", \"id\": 178234206, \"full_name\": \"ppanero/udemy-modern-react-with-redux\", \"description\": \"Repo with the code of some tutorials I have/am followed/ing\"}, \"263368032\": {\"default_branch\": \"master\", \"id\": 263368032, \"full_name\": \"ppanero/invenio-accounts\", \"description\": \"Invenio module for managing user accounts.\"}, \"161527137\": {\"default_branch\": \"master\", \"id\": 161527137, \"full_name\": \"ppanero/uoc_projects\", \"description\": \"Repository for the projects carried out for my Masters in Data Science at UOC\"}, \"250563388\": {\"default_branch\": \"master\", \"id\": 250563388, \"full_name\": \"ppanero/mkdocs-versioning\", \"description\": \"A tool that allows for versioning sites built with mkdocs\"}, \"26401131\": {\"default_branch\": \"master\", \"id\": 26401131, \"full_name\": \"zenodo/zenodo-backup\", \"description\": \"Static backup site for Zenodo in case of maintenance and/or unexpected downtime.\"}, \"88861550\": {\"default_branch\": \"master\", \"id\": 88861550, \"full_name\": \"ppanero/randscripts\", \"description\": \"Random scripts/small programs needed for some task\"}, \"268475759\": {\"default_branch\": \"master\", \"id\": 268475759, \"full_name\": \"ppanero/invenio-communities\", \"description\": \"Invenio communities module.\"}, \"372728689\": {\"default_branch\": \"master\", \"id\": 372728689, \"full_name\": \"ppanero/flask-iiif\", \"description\": \"Flask-IIIF is permitting easy integration with the International Image Interoperability Framework (IIIF) API standards.\"}, \"262082451\": {\"default_branch\": \"master\", \"id\": 262082451, \"full_name\": \"ppanero/idutils\", \"description\": \"Small Python library to validate persistent identifiers used in scholarly communication.\"}, \"97226101\": {\"default_branch\": \"master\", \"id\": 97226101, \"full_name\": \"ppanero/tinyproxy\", \"description\": \"tinyproxy - a light-weight HTTP/HTTPS proxy daemon for POSIX operating systems\"}, \"28222840\": {\"default_branch\": \"lanesdev\", \"id\": 28222840, \"full_name\": \"ppanero/RhoPollard_Factorize\", \"description\": \"Java multithreading Rho Pollard factoring algorithm\"}, \"107264379\": {\"default_branch\": \"master\", \"id\": 107264379, \"full_name\": \"ppanero/TheHive\", \"description\": \"TheHive: a Scalable, Open Source and Free Security Incident Response Platform\"}, \"161607554\": {\"default_branch\": \"master\", \"id\": 161607554, \"full_name\": \"ppanero/cookiecutter-invenio-instance\", \"description\": \"Cookiecutter template for an Invenio instance.\"}, \"290481539\": {\"default_branch\": \"master\", \"id\": 290481539, \"full_name\": \"ppanero/cookiecutter-invenio-module\", \"description\": \"Cookiecutter template for an Invenio module.\"}, \"161790344\": {\"default_branch\": \"master\", \"id\": 161790344, \"full_name\": \"ppanero/invenio-base\", \"description\": \"Base package for building the Invenio application.\"}, \"200075657\": {\"default_branch\": \"master\", \"id\": 200075657, \"full_name\": \"ppanero/invenio-app-rdm\", \"description\": \"RDM flavour of Invenio\"}, \"34480522\": {\"default_branch\": \"master\", \"id\": 34480522, \"full_name\": \"ppanero/Distributed_4_Player_Chess\", \"description\": \"4 player chess (all-to-all mode)\"}, \"160364696\": {\"default_branch\": \"master\", \"id\": 160364696, \"full_name\": \"ppanero/inveniosoftware.org\", \"description\": \"Sources of the http://inveniosoftware.org web site.\"}, \"321325459\": {\"default_branch\": \"master\", \"id\": 321325459, \"full_name\": \"ppanero/invenio-vocabularies\", \"description\": \"Invenio module for managing vocabularies.\"}, \"200073967\": {\"default_branch\": \"master\", \"id\": 200073967, \"full_name\": \"ppanero/invenio-records-permissions\", \"description\": \"Permissions for Invenio's records REST API.\"}, \"244411291\": {\"default_branch\": \"master\", \"id\": 244411291, \"full_name\": \"ppanero/invenio-iiif\", \"description\": \"IIIF API for Invenio.\"}, \"88095652\": {\"default_branch\": \"master\", \"id\": 88095652, \"full_name\": \"ppanero/UNED_CSE_Scripts\", \"description\": \"Simple scripts used for assigments on my Computer Science Engineering degree at UNED\"}, \"244406257\": {\"default_branch\": \"master\", \"id\": 244406257, \"full_name\": \"ppanero/invenio-cache\", \"description\": \"Cache module for Invenio\"}, \"520954282\": {\"default_branch\": \"master\", \"id\": 520954282, \"full_name\": \"ppanero/invenio-webhooks\", \"description\": \"Invenio module for processing webhook events.\"}, \"261756743\": {\"default_branch\": \"master\", \"id\": 261756743, \"full_name\": \"ppanero/jsonresolver\", \"description\": \"JSON data resolver with support for plugins.\"}, \"570582445\": {\"default_branch\": \"main\", \"id\": 570582445, \"full_name\": \"ppanero/invenio-administration\", \"description\": \"Invenio administration module.\"}, \"8135480\": {\"default_branch\": \"zenodo-master\", \"id\": 8135480, \"full_name\": \"zenodo/invenio\", \"description\": \"Deprecated repository.\"}, \"435429296\": {\"default_branch\": \"master\", \"id\": 435429296, \"full_name\": \"ppanero/zenodo-rdm\", \"description\": \"Zenodo Invenio RDM instance\"}, \"261780041\": {\"default_branch\": \"master\", \"id\": 261780041, \"full_name\": \"ppanero/invenio-formatter\", \"description\": \"Invenio module for formatting the bibliographic records.\"}, \"35002810\": {\"default_branch\": \"master\", \"id\": 35002810, \"full_name\": \"ppanero/java-design-patterns\", \"description\": \"Design pattern samples implemented in Java\"}, \"248187845\": {\"default_branch\": \"master\", \"id\": 248187845, \"full_name\": \"ppanero/dotfiles\", \"description\": \"Humble dotfiles\"}, \"79334344\": {\"default_branch\": \"master\", \"id\": 79334344, \"full_name\": \"zenodo/blog.zenodo.org\", \"description\": \"Zenodo News Site\"}, \"485388745\": {\"default_branch\": \"master\", \"id\": 485388745, \"full_name\": \"ppanero/flask-security-invenio\", \"description\": \"Private fork of Flask-Security\"}, \"335100363\": {\"default_branch\": \"master\", \"id\": 335100363, \"full_name\": \"ppanero/react-webpack-boilerplate\", \"description\": \"This repository is a boiler plate to start React projects, using Webpack for assets management and SCSS for styling.\"}, \"212273102\": {\"default_branch\": \"master\", \"id\": 212273102, \"full_name\": \"ppanero/rfcs\", \"description\": \"RFCs for Invenio\"}, \"308595151\": {\"default_branch\": \"master\", \"id\": 308595151, \"full_name\": \"ppanero/.github\", \"description\": \"Default community health files for the inveniosoftware GitHub organization repositories.\"}, \"301656016\": {\"default_branch\": \"master\", \"id\": 301656016, \"full_name\": \"ppanero/react-invenio-deposit\", \"description\": \"React application for Invenio deposit forms.\"}, \"80704473\": {\"default_branch\": \"master\", \"id\": 80704473, \"full_name\": \"ppanero/dnspython\", \"description\": \"a powerful DNS toolkit for python\"}, \"281133536\": {\"default_branch\": \"master\", \"id\": 281133536, \"full_name\": \"ppanero/invenio-pidstore\", \"description\": \"Invenio module that stores and registers persistent identifiers.\"}, \"479993832\": {\"default_branch\": \"main\", \"id\": 479993832, \"full_name\": \"ppanero/PokeWars-Backend\", \"description\": \"PokeWars backend application\"}, \"79111932\": {\"default_branch\": \"master\", \"id\": 79111932, \"full_name\": \"zenodo/help.zenodo.org\", \"description\": \"Zenodo Help Site\"}, \"161791656\": {\"default_branch\": \"master\", \"id\": 161791656, \"full_name\": \"ppanero/invenio-access\", \"description\": \"Invenio module for common role based access control.\"}, \"160569336\": {\"default_branch\": \"master\", \"id\": 160569336, \"full_name\": \"ppanero/uoc_data_mining\", \"description\": \"Data Mining Master course project\"}}, \"id\": 6756943, \"last_sync\": \"2023-09-19T13:32:16.294105+00:00\"}", "created": 1695130329996553, "updated": 1695130336304024}, "source": {"version": "2.3.0.Final", "connector": "postgresql", "name": "zenodo-qa", "ts_ms": 1695130336322, "snapshot": "false", "db": "zenodo", "sequence": "[\"1472993974464\",\"1472994103272\"]", "schema": "public", "table": "oauthclient_remoteaccount", "txId": 563876034, "lsn": 1472994103272, "xmin": null}, "op": "u", "ts_ms": 1695130336534, "transaction": {"id": "563876034:1472994103272", "total_order": 6, "data_collection_order": 3}}, "headers": [], "checksum": null, "serialized_key_size": 89, "serialized_value_size": 25870, "serialized_header_size": -1} +{"topic": "zenodo-qa.public", "partition": 0, "offset": 13230, "timestamp": 1695130336914, "timestamp_type": 0, "key": {"id": "6756943", "method": "github", "__dbz__physicalTableIdentifier": "zenodo-qa.public.oauthclient_useridentity"}, "value": {"before": null, "after": {"id": "6756943", "method": "github", "id_user": 86490, "created": 1695130336318289, "updated": 1695130336318298}, "source": {"version": "2.3.0.Final", "connector": "postgresql", "name": "zenodo-qa", "ts_ms": 1695130336322, "snapshot": "false", "db": "zenodo", "sequence": "[\"1472993974464\",\"1472994103616\"]", "schema": "public", "table": "oauthclient_useridentity", "txId": 563876034, "lsn": 1472994103616, "xmin": null}, "op": "c", "ts_ms": 1695130336534, "transaction": {"id": "563876034:1472994103616", "total_order": 7, "data_collection_order": 1}}, "headers": [], "checksum": null, "serialized_key_size": 111, "serialized_value_size": 535, "serialized_header_size": -1} \ No newline at end of file diff --git a/migrator/tests/actions/oauth/testdata/linked_accounts/connect_github.jsonl b/migrator/tests/actions/oauth/testdata/linked_accounts/connect_github.jsonl deleted file mode 100644 index e69de29b..00000000 diff --git a/migrator/zenodo_rdm_migrator/actions/transform/oauth.py b/migrator/zenodo_rdm_migrator/actions/transform/oauth.py index dc116a4a..f8e01250 100644 --- a/migrator/zenodo_rdm_migrator/actions/transform/oauth.py +++ b/migrator/zenodo_rdm_migrator/actions/transform/oauth.py @@ -238,32 +238,47 @@ class OAuthLinkedAccountConnectAction(TransformAction): @classmethod def matches_action(cls, tx): """Checks if the data corresponds with that required by the action.""" - if len(tx.operations) != 4: + if 3 > len(tx.operations) or len(tx.operations) > 7: return False - rules = { - # OpType is not hashable (to use a dict), the list won't be long in any case - # so the worst case scenario is not too bad - "oauthclient_remoteaccount": [OperationType.INSERT, OperationType.UPDATE], - "oauthclient_remotetoken": [OperationType.INSERT], - "oauthclient_useridentity": [OperationType.INSERT], + mandatory = { + "oauthclient_remoteaccount", + "oauthclient_remotetoken", + "oauthclient_useridentity", + } + + optional = { + "oauth2server_client", + "oauth2server_token", } + allow_updates = {"oauthclient_remoteaccount"} + for op in tx.operations: - rule = rules.get(op["source"]["table"]) - if not rule: - return False - try: - rule.remove(op["op"]) # prevents double update/insert in sets - except ValueError: + if op["op"] == OperationType.UPDATE: + # nested to be able to have the final else clause by op type + if op["source"]["table"] not in allow_updates: + return False + elif op["op"] == OperationType.INSERT: + try: + mandatory.remove(op["source"]["table"]) + except KeyError: + try: + optional.remove(op["source"]["table"]) + except KeyError: + return False + else: return False - return True + # all mandatory were found and optionals are all or none + return len(mandatory) == 0 and (len(optional) == 2 or len(optional) == 0) def _transform_data(self): """Transforms the data and returns dictionary.""" remote_account = None remote_token = None + server_client = None + server_token = None user_identity = None for op in self.tx.operations: @@ -277,13 +292,25 @@ def _transform_data(self): remote_token = op["after"] elif op["source"]["table"] == "oauthclient_useridentity": user_identity = op["after"] + elif op["source"]["table"] == "oauth2server_client": + server_client = op["after"] + elif op["source"]["table"] == "oauth2server_token": + server_token = op["after"] - return { + result = { "tx_id": self.tx.id, "remote_account": IdentityTransform()._transform(remote_account), "remote_token": IdentityTransform()._transform(remote_token), "user_identity": IdentityTransform()._transform(user_identity), } + if server_client: + result["server_client"]: OAuthServerClientTransform()._transform( + server_client + ) + if server_token: + result["server_token"]: OAuthServerTokenTransform()._transform(server_token) + + return result class OAuthLinkedAccountDisconnectAction(TransformAction):