Repository: robocorp/rpaframework

Issue:
Fix Hubspot robot test's secrets value in CI
CI has [failing](https://github.com/robocorp/rpaframework/actions/runs/3688305681) robot tests due to `401 Unauthorized` given the either outdated or missing `HUBSPOT_TESTVARS` secret value.

<img width="652" alt="Screenshot 2022-12-14 at 12 24 57" src="https://user-images.githubusercontent.com/709053/207575064-c0d62160-1efd-4efd-a82f-729f7413ba92.png">


### ToDo
- [x] Update the secret in GitHub so it unblocks the flow in order to deploy docs and merge PRs nicely. (put some instance credentials in 1Password in case somebody else needs to generate new API keys)
- [x] **In order for this to not happen again, let's add a failback check and if the credentials are not good, the tests will be just skipped or kinda xfailed.**
- [x] Maybe it looks more RFish to have a _HubspotTestvars.py_ instead of _hubspot_testvars.py_.
  - [x] Ensure such template script already exists and given the fake creds inside, it will fail the login, thus skipping the tests. (rather than relying on the absence/existence of such file to decide if we skip or not the tests)
- [ ] Optional (if needed): Create a helper script inside of _main/tests/scripts_ which will take the content of an existing _HubspotTestvars.py_ file and output the content that should be placed in the GH secret value. (like base64 encoding if that's not already done by GitHub)

Internal Slack [thread](https://robocorptechnologies.slack.com/archives/CS71JM9T7/p1671013668245459)


Discussion:

Resolving patch:
diff --git a/packages/main/src/RPA/Hubspot.py b/packages/main/src/RPA/Hubspot.py
--- a/packages/main/src/RPA/Hubspot.py
+++ b/packages/main/src/RPA/Hubspot.py
@@ -1,49 +1,51 @@
 # pylint: disable=too-many-lines
-from enum import Enum
 import logging
 import math
-
 import traceback
-from typing import Any, List, Dict, Optional, Tuple, Union
-from tenacity import (
-    before_sleep_log,
-    retry,
-    retry_if_exception,
-    stop_after_attempt,
-    wait_exponential,
-)
-
-from robot.api.deco import keyword, library
+from enum import Enum
+from typing import Any, Dict, List, Optional, Tuple, Union
 
 import requests
 
 # pylint: disable=no-name-in-module
 from hubspot import HubSpot as HubSpotApi
+from hubspot.crm.associations.models import (
+    AssociatedId,
+    BatchInputPublicObjectId,
+    PublicObjectId,
+)
 from hubspot.crm.objects.models import (
-    PublicObjectSearchRequest as ObjectSearchRequest,
-    FilterGroup,
-    Filter,
-    SimplePublicObject,
-    SimplePublicObjectWithAssociations,
-    BatchReadInputSimplePublicObjectId,
-    SimplePublicObjectId,
-    SimplePublicObjectInput as CreateObjectInput,
-    SimplePublicObjectBatchInput as UpdateObjectInput,
-    BatchInputSimplePublicObjectInput as CreateBatchInput,
     BatchInputSimplePublicObjectBatchInput as UpdateBatchInput,
 )
-from hubspot.crm.schemas.models import ObjectSchema
-from hubspot.crm.pipelines.models import (
-    Pipeline,
+from hubspot.crm.objects.models import (
+    BatchInputSimplePublicObjectInput as CreateBatchInput,
 )
-from hubspot.crm.associations.models import (
-    BatchInputPublicObjectId,
-    PublicObjectId,
-    AssociatedId,
+from hubspot.crm.objects.models import (
+    BatchReadInputSimplePublicObjectId,
+    Filter,
+    FilterGroup,
 )
+from hubspot.crm.objects.models import PublicObjectSearchRequest as ObjectSearchRequest
+from hubspot.crm.objects.models import SimplePublicObject
+from hubspot.crm.objects.models import SimplePublicObjectBatchInput as UpdateObjectInput
+from hubspot.crm.objects.models import SimplePublicObjectId
+from hubspot.crm.objects.models import SimplePublicObjectInput as CreateObjectInput
+from hubspot.crm.objects.models import SimplePublicObjectWithAssociations
 from hubspot.crm.owners.models import PublicOwner
-from hubspot.crm.schemas.exceptions import ApiException as SchemaApiException
 from hubspot.crm.pipelines.exceptions import ApiException as PipelineApiException
+from hubspot.crm.pipelines.models import Pipeline
+from hubspot.crm.schemas.exceptions import ApiException as SchemaApiException
+from hubspot.crm.schemas.models import ObjectSchema
+from robot.api.deco import keyword, library
+from tenacity import (
+    before_sleep_log,
+    retry,
+    retry_if_exception,
+    stop_after_attempt,
+    wait_exponential,
+)
+
+from RPA.core.logger import deprecation
 
 
 class HubSpotAuthenticationError(Exception):
@@ -1024,9 +1026,11 @@ def auth_with_token(self, access_token: str) -> None:
         keyword verifies the provided credentials by retrieving the
         custom object schema from the API.
 
+        Learn more about Private Apps:
+        https://developers.hubspot.com/docs/api/private-apps
+
         :param access_token: The access token created for the Private App
             in your HubSpot account.
-
         """
         if self.hs is None or getattr(self.hs, "access_token", "") != access_token:
             self.hs = HubSpotApi(access_token=access_token)
@@ -1039,19 +1043,21 @@ def auth_with_token(self, access_token: str) -> None:
                     ) from e
                 else:
                     raise e
-            self.logger.info("Authentication to Hubspot CRM API with token successful.")
+            self.logger.info("Authentication to Hubspot CRM API with token succeeded.")
         else:
             self.logger.info("Already authenticated with access token.")
 
     @keyword
     def auth_with_api_key(self, api_key: str) -> None:
-        """Authorize to HubSpot with an account-wide API key. This
+        """Deprecated! Use ``Auth With Token`` instead.
+
+        Authorize to HubSpot with an account-wide API key. This
         keyword verifies the provided credentials by retrieving the
         custom object schema from the API.
 
-        :param api_key: The API key for the account to autheniticate to.
-
+        :param api_key: The API key for the account to authenticate to.
         """
+        deprecation("Use `Auth With Token` as HubSpot removed support for API keys.")
         if self.hs is None or getattr(self.hs, "api_key", "") != api_key:
             self.hs = HubSpotApi(api_key=api_key)
             try:


Test patch:
diff --git a/packages/main/tests/resources/config/HubspotTestvars.py b/packages/main/tests/resources/config/HubspotTestvars.py
new file mode 100644
--- /dev/null
+++ b/packages/main/tests/resources/config/HubspotTestvars.py
@@ -0,0 +1,51 @@
+import os
+
+
+# Token for access.
+ACCESS_TOKEN = os.getenv("HUBSPOT_TOKEN", "not-set")
+
+# Contact/object lookup tests.
+FIRST_NAME = "Cosmin"
+LAST_NAME = "Poieană"
+FIRST_NAME_2 = "Cosmin"
+CONTACT_EMAILS = ["cosmin@robocorp.com", "cmin764@gmail.com"]
+CONTACT_ID = "7397"
+
+# Get One Object test
+OBJECT_ID = 7397
+COMPANY_ID = 7946543029
+
+# Batch tests
+OBJECT_IDS = [7397, 10559]
+EXPECTED_ASSOCIATION_MAP = {"7397": "7946543029", "10559": "7946467247"}
+EXPECTED_EMAILS = ["cosmin@robocorp.com", "cmin764@gmail.com"]
+
+# Get Custom Object with Custom ID property test
+CUSTOM_OBJ_ID = "a736d282-8ef6-4af3-9c10-996c827a78f4"
+ID_PROPERTY = "organization_id"
+CUSTOM_OBJECT_TYPE = "Organization"
+
+# Pipeline tests.
+PIPELINE_LABEL = "Self-Service Pipeline"
+EXPECTED_STAGE_ORDER = (
+    "Starter",
+    "Flex",
+    "Pro",
+    "Closed lost",
+)
+TEST_DEAL = 8262422658
+EXPECTED_STAGE = "Contract Signed - Active"
+
+# User provisioning tests.
+USER_ID = "27198978"
+USER_EMAIL = "cosmin@robocorp.com"
+
+# Owner lookup tests.
+OWNER_ID = "129210975"
+OWNER_EMAIL = "chris@robocorp.com"
+COMPANY_ID_WITH_OWNER = "9848040731"
+EXPECTED_COMPANY_OWNER = "149929641"
+
+CUSTOM_OWNER_PROPERTY = "customer_success_contact"
+COMPANY_ID_WITH_CUSTOM_OWNER = "7946565950"
+EXPECTED_CUSTOM_OWNER = "149929641"
diff --git a/packages/main/tests/robot/test_hubspot.robot b/packages/main/tests/robot/test_hubspot.robot
--- a/packages/main/tests/robot/test_hubspot.robot
+++ b/packages/main/tests/robot/test_hubspot.robot
@@ -1,93 +1,65 @@
 *** Settings ***
-Documentation       API keys and variables must be provided in ``./testvars.py``. These should use a live or
-...                 sandbox Hubspot environment to test the API, there is no mocking function.
-...
-...                 The ``testvars.py`` should be built like so (example values provided, these will
-...                 need to be replaced with IDs and data from HubSpot):
-...
-...                 |    # API key for all tests.
-...                 |    API_KEY = "not-a-real-hubspot-api-key"
-...                 |    ACCESS_TOKEN = "pat-na1-not-a-real-hubspot-auth-token"
-...                 |
-...                 |    # Contact/object lookup tests.
-...                 |    FIRST_NAME = "John"
-...                 |    LAST_NAME = "Smith"
-...                 |    FIRST_NAME_2 = "Alice"
-...                 |    CONTACT_EMAILS = ["john@example.com", "alice@example.com"]
-...                 |    CONTACT_ID = "1234"
-...                 |
-...                 |    # Get One Object test
-...                 |    OBJECT_ID = 4567
-...                 |    COMPANY_ID = 123456789
-...                 |
-...                 |    # Batch tests
-...                 |    OBJECT_IDS = [4567, 987654]
-...                 |    EXPECTED_ASSOCIATION_MAP = {"4567": "123456789", "65478": "987654321"}
-...                 |    EXPECTED_EMAILS = ["john@example.com", "alice@example.com"]
-...                 |
-...                 |    # Get Custom Object with Custom ID property test
-...                 |    CUSTOM_OBJ_ID = "123456-8ef6-4af3-9c10-8798a532f"
-...                 |    ID_PROPERTY = "organization_id"
-...                 |    CUSTOM_OBJECT_TYPE = "Organization"
-...                 |
-...                 |    # Pipeline tests.
-...                 |    PIPELINE_LABEL = "Self-Service Pipeline"
-...                 |    EXPECTED_STAGE_ORDER = (
-...                 |    "Free",
-...                 |    "Pro",
-...                 |    "Closed lost",
-...                 |    )
-...                 |    TEST_DEAL = 123456789
-...                 |    EXPECTED_STAGE = "Contract Signed"
-...                 |
-...                 |    # User provisioning tests.
-...                 |    USER_ID = "2456789"
-...                 |    USER_EMAIL = "john@example.com"
-...                 |
-...                 |    # Owner lookup tests.
-...                 |    OWNER_ID = "123654987"
-...                 |    OWNER_EMAIL = "john@example.com"
-...                 |    COMPANY_WITH_OWNER_ID = "123456789"
-...                 |    EXPECTED_COMPANY_OWNER = "987456123"
-...                 |
-...                 |    CUSTOM_OWNER_PROPERTY = "customer_success_contact"
-...                 |    COMPANY_ID_WITH_CUSTOM_OWNER = "123456789"
-...                 |    EXPECTED_CUSTOM_OWNER = "123654987"
+Documentation       Run real tests in a live HubSpot sandbox as there's no mocking
+...     functionality available. Tests configuration is placed under the
+...     HubspotTestvars.py variables file, which relies on a 'HUBSPOT_TOKEN' env var to
+...     be set in order to get authorized into the API.
 
-Library             RPA.Hubspot
 Library             Collections
+Library             RPA.Hubspot
 Library             String
-Variables           ./hubspot_testvars.py
 
+Variables           ../resources/config/HubspotTestvars.py
+Task Setup          Token Auth
 Force Tags          hubspot
 
 
 *** Variables ***
 ${NOT_AUTHENTICATED_ERROR}      STARTS:HubSpotAuthenticationError:
-${AUTHENTICATION_FAILED}        HubSpotAuthenticationError: Authentication was not successful.
-${HUBSPOT_TYPE_ERROR}           STARTS:HubSpotObjectTypeError:
+
+
+*** Keywords ***
+Token Auth
+    IF   "${ACCESS_TOKEN}" == "not-set"
+        Skip    No token set, please provide it with 'HUBSPOT_TOKEN' env var.
+    END
+    ${status}   ${ret} =    Run Keyword And Ignore Error
+    ...     Auth With Token    ${ACCESS_TOKEN}
+    IF    "${status}" == "FAIL"
+        Skip    Can't authorize with the provided token.
+    END
+
+Generate random name and description
+    ${random_name}=    Generate random string    8    [LETTERS]
+    ${random_description}=    Generate random string    20    chars=\ [LETTERS]
+    [Return]    ${random_name}    ${random_description}
 
 
 *** Tasks ***
 Search for objects should fail without authentication
+    [Setup]     Log To Console    No auth required
+
     Run Keyword And Expect Error    ${NOT_AUTHENTICATED_ERROR}
     ...    Search for objects    object_type=CONTACTS
 
 List associations should fail without authentication
+    [Setup]     Log To Console    No auth required
+
     Run Keyword And Expect Error    ${NOT_AUTHENTICATED_ERROR}
     ...    List associations    object_type=contact    object_id=123    to_object_type=company
 
 Get object should fail without authentication
+    [Setup]     Log To Console    No auth required
+
     Run Keyword And Expect Error    ${NOT_AUTHENTICATED_ERROR}
     ...    Get object    object_type=contact    object_id=123
 
-Authentication fails with bad API key
-    Run Keyword And Expect Error    ${AUTHENTICATION_FAILED}
-    ...    Auth with API key    api_key=123
+Authentication fails with API key
+    [Setup]     Log To Console    No auth required
+
+    Run Keyword And Expect Error    ${NOT_AUTHENTICATED_ERROR}
+    ...     Auth With API Key    api_key=123
 
 Search for Contact by First Name Returns Contacts
-    Check If Variable File Exists
-    Auth with API key    ${API_KEY}
     ${search_object}=    Evaluate
     ...    [{'filters':[{'propertyName':'firstname','operator':'EQ','value':'${FIRST_NAME}'}]}]
     ${contacts}=    Search for objects    CONTACTS    search=${search_object}
@@ -95,16 +67,12 @@ Search for Contact by First Name Returns Contacts
     ...    case_insensitive=${True}
 
 Search for All Contacts Returns 1000 Contacts
-    Check If Variable File Exists
-    Auth with API key    ${API_KEY}
     ${search_object}=    Evaluate    [{'filters':[{'propertyName':'firstname','operator':'HAS_PROPERTY'}]}]
     ${contacts}=    Search for objects    CONTACTS    search=${search_object}
     ${length}=    Get Length    ${contacts}
     Should Be Equal As Integers    1000    ${length}
 
 Search for object with natural language returns object
-    Check If Variable File Exists
-    Auth with API key    ${API_KEY}
     ${contacts}=    Search for objects    CONTACTS    firstname    EQ    ${FIRST_NAME}    AND    lastname    EQ
     ...    ${LAST_NAME}
     Should Contain Match    ${{[c.properties["firstname"] for c in $contacts]}}    ${FIRST_NAME}
@@ -113,8 +81,6 @@ Search for object with natural language returns object
     ...    case_insensitive=${True}
 
 Seach for object using IN operator returns object
-    Check If Variable File Exists
-    Auth with API key    ${API_KEY}
     ${contacts}=    Search for objects    CONTACTS    email    IN    ${CONTACT_EMAILS}
     Should Contain Match    ${{[c.properties["firstname"] for c in $contacts]}}    ${FIRST_NAME}
     ...    case_insensitive=${True}
@@ -122,8 +88,6 @@ Seach for object using IN operator returns object
     ...    case_insensitive=${True}
 
 Search for object using BETWEEN operator returns object
-    Check If Variable File Exists
-    Auth with API key    ${API_KEY}
     ${contacts}=    Search for objects    CONTACTS    hs_object_id    BETWEEN    ${{[$CONTACT_ID,$CONTACT_ID]}}
     Should Contain Match    ${{[c.properties["firstname"] for c in $contacts]}}    ${FIRST_NAME}
     ...    case_insensitive=${True}
@@ -131,28 +95,20 @@ Search for object using BETWEEN operator returns object
     ...    case_insensitive=${True}
 
 Retrieve One Object Using ID Returns Object
-    Check If Variable File Exists
-    Auth with API key    ${API_KEY}
     ${contact}=    Get object    CONTACT    ${OBJECT_ID}
     Should Be Equal As Strings    ${OBJECT_ID}    ${contact.id}
 
 Retrieve Objects Using IDs Returns Objects
-    Check If Variable File Exists
-    Auth with API key    ${API_KEY}
     ${contacts}=    Get object    CONTACT    ${OBJECT_IDS}
     FOR    ${obj}    IN    @{contacts}
         List should contain value    ${EXPECTED_EMAILS}    ${obj.properties}[email]
     END
 
 List company associations for contact returns one company
-    Check If Variable File Exists
-    Auth with API key    ${API_KEY}
     ${associations}=    List associations    CONTACT    ${OBJECT_ID}    COMPANY
     Should Be Equal As Strings    ${COMPANY_ID}    ${{$associations[0].id}}
 
 List company associations for list of contacts returns companies
-    Check If Variable File Exists
-    Auth with API key    ${API_KEY}
     ${associations}=    List associations    CONTACT    ${OBJECT_IDS}    COMPANY
     FOR    ${id}    ${associated_objs}    IN    &{associations}
         ${associated_ids}=    Evaluate    [o.id for o in $associated_objs]
@@ -160,80 +116,56 @@ List company associations for list of contacts returns companies
     END
 
 Retrieve Custom Object Using Custom ID Returns Object
-    Check If Variable File Exists
-    Auth with API key    ${API_KEY}
     ${custom_object}=    Get object    ${CUSTOM_OBJECT_TYPE}    ${CUSTOM_OBJ_ID}
     ...    id_property=${ID_PROPERTY}    properties=${ID_PROPERTY}
     Should Be Equal as Strings    ${CUSTOM_OBJ_ID}    ${custom_object.properties["${ID_PROPERTY}"]}
 
 Search for custom object with natural language returns object
-    Check If Variable File Exists
-    Auth with API key    ${API_KEY}
     ${custom_objects}=    Search for objects    ${CUSTOM_OBJECT_TYPE}    ${ID_PROPERTY}    EQ    ${CUSTOM_OBJ_ID}
     ...    properties=${ID_PROPERTY}
     Should Be Equal as Strings    ${CUSTOM_OBJ_ID}    ${custom_objects[0].properties["${ID_PROPERTY}"]}
 
 List Deal Pipelines Should Return Default Pipeline
-    Check If Variable File Exists
-    Auth with API key    ${API_KEY}
     ${pipelines}=    List pipelines    DEALS
     ${pipeline_ids}=    Evaluate    [p.id for p in $pipelines]
     List should contain value    ${pipeline_ids}    default
 
 List Default Deal Pipeline Should Return Default Pipeline
-    Check If Variable File Exists
-    Auth with API key    ${API_KEY}
     ${default_pipeline}=    Get pipeline    DEALS    default
 
 List Deal Pipeline With Label Should Return Pipeline
-    Check If Variable File Exists
-    Auth with API key    ${API_KEY}
     ${default_pipeline}=    Get pipeline    DEALS    ${PIPELINE_LABEL}
 
 Get Pipeline Stages For Labeled Pipeline Returns Dictionary In Proper Order
-    Check If Variable File Exists
-    Auth with API key    ${API_KEY}
     &{stages}=    Get Pipeline Stages    DEALS    ${PIPELINE_LABEL}    use_cache=${False}
     @{stage_labels}=    Get dictionary keys    ${stages}    sort_keys=${False}
     Lists should be equal    ${EXPECTED_STAGE_ORDER}    ${stage_labels}
 
 Check Test Deal Is Currently In Expected Stage
-    Check If Variable File Exists
-    Auth with API key    ${API_KEY}
     ${current_stage}=    Get current stage of object    DEAL    ${TEST_DEAL}
     Should be equal as strings    ${EXPECTED_STAGE}    ${current_stage}[0]
 
 Get User Returns Expected User
-    Check If Variable File Exists
-    Auth with token    ${ACCESS_TOKEN}
     ${user}=    Get user    ${USER_ID}
     Should be equal as strings    ${USER_EMAIL}    ${user}[email]
 
 Get Owner by ID Returns Expected Owner
-    Check If Variable File Exists
-    Auth with API key    ${API_KEY}
     ${owner}=    Get owner by id    ${OWNER_ID}
     Should be equal as strings    ${OWNER_EMAIL}    ${owner.email}
 
 Get Owner of Company Returns Expected Owner
-    Check If Variable File Exists
-    Auth with API key    ${API_KEY}
     ${extra_properties}=    Create list    hubspot_owner_id
-    ${company}=    Get object    COMPANY    ${COMPANY_WITH_OWNER_ID}    properties=${extra_properties}
+    ${company}=    Get object    COMPANY    ${COMPANY_ID_WITH_OWNER}    properties=${extra_properties}
     ${owner}=    Get owner of object    ${company}
     Should be equal as strings    ${EXPECTED_COMPANY_OWNER}    ${owner.id}
 
 Get Custom Owner Property of Company Returns Expected Owner
-    Check If Variable File Exists
-    Auth with API key    ${API_KEY}
     ${extra_properties}=    Create list    hubspot_owner_id    ${CUSTOM_OWNER_PROPERTY}
     ${company}=    Get object    COMPANY    ${COMPANY_ID_WITH_CUSTOM_OWNER}    properties=${extra_properties}
     ${owner}=    Get owner of object    ${company}    owner_property=${CUSTOM_OWNER_PROPERTY}
     Should be equal as strings    ${EXPECTED_CUSTOM_OWNER}    ${owner.id}
 
 Induce Rate Limit Error and Ensure Good results
-    Check If Variable File Exists
-    Auth with API key    ${API_KEY}
     ${contacts}=    Search for objects    CONTACTS    firstname    HAS_PROPERTY    max_results=20
     FOR    ${contact}    IN    @{contacts}
         ${individual_contact}=    Search for objects    CONTACTS    hs_object_id    EQ    ${contact.id}
@@ -246,23 +178,17 @@ Induce Rate Limit Error and Ensure Good results
     ...    case_insensitive=${True}
 
 Create random company
-    Check If Variable File Exists
-    Auth with API key    ${API_KEY}
     ${random_name}    ${random_description}=    Generate random name and description
     ${new_object}=    Create object    COMPANY    name=${random_name}    description=${random_description}
     Should be equal as strings    ${random_name}    ${new_object.properties}[name]
     Should be equal as strings    ${random_description}    ${new_object.properties}[description]
 
 Set company number of employees to random number
-    Check If Variable File Exists
-    Auth with API key    ${API_KEY}
     ${random_number}=    Generate random string    3    [NUMBERS]
     ${updated_object}=    Update object    COMPANY    ${COMPANY_ID}    numberofemployees=${random_number}
     Should be equal as integers    ${random_number}    ${updated_object.properties}[numberofemployees]
 
 Create random companies by batch
-    Check If Variable File Exists
-    Auth with API key    ${API_KEY}
     Create new batch    COMPANY    CREATE
     ${random_name1}    ${random_description1}=    Generate random name and description
     Add input to batch    name=${random_name1}    description=${random_description1}
@@ -277,8 +203,6 @@ Create random companies by batch
     END
 
 Create two hundred random companies by batch
-    Check If Variable File Exists
-    Auth with API key    ${API_KEY}
     Create new batch    COMPANY    CREATE
     ${properties_for_batch}=    Create list
     FOR    ${counter}    IN RANGE    200
@@ -294,18 +218,3 @@ Create two hundred random companies by batch
         Should contain    ${all_names}    ${company.properties}[name]
         Should contain    ${all_descriptions}    ${company.properties}[description]
     END
-
-
-*** Keywords ***
-Check If Variable File Exists
-    ${result}    ${_}=    Run keyword and ignore error    Variable Should Exist    ${API_KEY}
-    IF    "${result}" == "FAIL"
-        ${message}=    Set variable    No variable file for tests to use, skipping this test.
-        Log    ${message}    level=WARN
-        Skip    ${message}
-    END
-
-Generate random name and description
-    ${random_name}=    Generate random string    8    [LETTERS]
-    ${random_description}=    Generate random string    20    chars=\ [LETTERS]
-    [Return]    ${random_name}    ${random_description}
diff --git a/packages/main/tests/scripts/prepare_files.py b/packages/main/tests/scripts/prepare_files.py
deleted file mode 100755
--- a/packages/main/tests/scripts/prepare_files.py
+++ /dev/null
@@ -1,59 +0,0 @@
-#!/usr/bin/env python3
-"""
-Create test data for FilesLibrary acceptance tests
-"""
-import argparse
-import os
-from pathlib import Path
-
-
-def touch(path):
-    Path(path).touch()
-
-
-def mkdir(path):
-    os.makedirs(path)
-
-
-def write(path, content):
-    if isinstance(content, str):
-        content = content.encode("utf-8")
-    with open(path, "wb") as fd:
-        fd.write(content)
-
-
-def prepare():
-    mkdir("subfolder/first")
-    mkdir("subfolder/second")
-    mkdir("another/first")
-    mkdir("another/second")
-    mkdir("empty")
-
-    touch("emptyfile")
-    touch("notemptyfile")
-    touch("sorted1.test")
-    touch("sorted2.test")
-    touch("sorted3.test")
-    touch("subfolder/first/stuff.ext")
-    touch("subfolder/second/stuff.ext")
-    touch("another/first/noext")
-    touch("another/second/one")
-    touch("another/second/two")
-    touch("another/second/three")
-    touch("another/second/.dotfile")
-
-    write("notemptyfile", "some content here")
-    write("somebytes", b"\x00\x66\x6f\x6f\x00")
-
-
-if __name__ == "__main__":
-    parser = argparse.ArgumentParser(description=__doc__)
-    parser.add_argument(
-        "root", default=os.getcwd(), help="Root directory for mock files"
-    )
-    args = parser.parse_args()
-
-    mkdir(args.root)
-    os.chdir(args.root)
-
-    prepare()