@phase1 @domain @repository @error_handling_coverage
Feature: Repository error handling coverage for major repository classes
  As a system operating under unstable database conditions
  I want all repository error-handling paths to be covered
  So that OperationalError, IntegrityError, and DatabaseError paths are exercised

  # ==========================================================================
  # NamespacedProjectRepository error handling
  # ==========================================================================

  # --- create: IntegrityError (UNIQUE) -> DatabaseError "already exists" ----

  @project_repo @transient_error
  Scenario: NamespacedProjectRepository create raises DatabaseError on UNIQUE IntegrityError
    Given a namespaced project repository with session raising UNIQUE IntegrityError on flush
    When a namespaced project is created and a repo error is expected
    Then a repo DatabaseError should be raised containing "already exists"

  # --- create: IntegrityError (non-UNIQUE) -> DatabaseError -----------------

  @project_repo @transient_error
  Scenario: NamespacedProjectRepository create raises DatabaseError on non-UNIQUE IntegrityError
    Given a namespaced project repository with session raising non-UNIQUE IntegrityError on flush
    When a namespaced project is created and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to create project"

  # --- create: OperationalError -> DatabaseError ----------------------------

  @project_repo @transient_error
  Scenario: NamespacedProjectRepository create raises DatabaseError on OperationalError
    Given a namespaced project repository with session raising OperationalError on flush
    When a namespaced project is created and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to create project"

  # --- get: OperationalError -> DatabaseError -------------------------------

  @project_repo @transient_error
  Scenario: NamespacedProjectRepository get raises DatabaseError on OperationalError
    Given a namespaced project repository with session raising OperationalError on query
    When a namespaced project is fetched by name and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to get project"

  # --- list_projects: OperationalError -> DatabaseError ---------------------

  @project_repo @transient_error
  Scenario: NamespacedProjectRepository list_projects raises DatabaseError on OperationalError
    Given a namespaced project repository with session raising OperationalError on query
    When namespaced projects are listed and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to list projects"

  # --- update: OperationalError -> DatabaseError ----------------------------

  @project_repo @transient_error
  Scenario: NamespacedProjectRepository update raises DatabaseError on OperationalError
    Given a namespaced project repository with session raising OperationalError on query
    When a namespaced project is updated and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to update project"

  # --- delete: OperationalError -> DatabaseError ----------------------------

  @project_repo @transient_error
  Scenario: NamespacedProjectRepository delete raises DatabaseError on OperationalError
    Given a namespaced project repository with session raising OperationalError on query
    When a namespaced project is deleted and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to delete project"

  # ==========================================================================
  # ToolRepository error handling
  # ==========================================================================

  # --- add: IntegrityError (UNIQUE) -> DuplicateToolError -------------------

  @tool_repo @transient_error
  Scenario: ToolRepository add raises DuplicateToolError on UNIQUE IntegrityError
    Given a tool repository with session raising UNIQUE IntegrityError on flush
    When a tool is added and a repo error is expected
    Then a repo DuplicateToolError should be raised

  # --- add: IntegrityError (non-UNIQUE) -> DatabaseError --------------------

  @tool_repo @transient_error
  Scenario: ToolRepository add raises DatabaseError on non-UNIQUE IntegrityError
    Given a tool repository with session raising non-UNIQUE IntegrityError on flush
    When a tool is added and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to create tool"

  # --- add: OperationalError -> DatabaseError -------------------------------

  @tool_repo @transient_error
  Scenario: ToolRepository add raises DatabaseError on OperationalError
    Given a tool repository with session raising OperationalError on flush
    When a tool is added and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to create tool"

  # --- get_by_name: OperationalError -> DatabaseError -----------------------

  @tool_repo @transient_error
  Scenario: ToolRepository get_by_name raises DatabaseError on OperationalError
    Given a tool repository with session raising OperationalError on query
    When a tool is fetched by name and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to get tool"

  # --- list_all: OperationalError -> DatabaseError --------------------------

  @tool_repo @transient_error
  Scenario: ToolRepository list_all raises DatabaseError on OperationalError
    Given a tool repository with session raising OperationalError on query
    When all tools are listed and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to list tools"

  # --- update: ToolNotFoundError (not found) --------------------------------

  @tool_repo @not_found
  Scenario: ToolRepository update raises ToolNotFoundError when tool not found
    Given a tool repository backed by a clean in-memory database
    When a non-existent tool is updated and a repo error is expected
    Then a repo ToolNotFoundError should be raised

  # --- update: OperationalError -> DatabaseError ----------------------------

  @tool_repo @transient_error
  Scenario: ToolRepository update raises DatabaseError on OperationalError
    Given a tool repository with session raising OperationalError on query
    When a non-existent tool is updated and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to update tool"

  # --- remove: ToolNotFoundError (not found) --------------------------------

  @tool_repo @not_found
  Scenario: ToolRepository remove raises ToolNotFoundError when tool not found
    Given a tool repository backed by a clean in-memory database
    When a non-existent tool is removed and a repo error is expected
    Then a repo ToolNotFoundError should be raised

  # --- remove: OperationalError -> DatabaseError ----------------------------

  @tool_repo @transient_error
  Scenario: ToolRepository remove raises DatabaseError on OperationalError
    Given a tool repository with session raising OperationalError on query
    When a non-existent tool is removed and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to delete tool"

  # ==========================================================================
  # AutomationProfileRepository error handling
  # ==========================================================================

  # --- get_by_name: OperationalError -> DatabaseError -----------------------

  @profile_repo @transient_error
  Scenario: AutomationProfileRepository get_by_name raises DatabaseError on OperationalError
    Given an automation profile repository with session raising OperationalError on query
    When an automation profile is fetched by name and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to get profile"

  # --- list_all: OperationalError -> DatabaseError --------------------------

  @profile_repo @transient_error
  Scenario: AutomationProfileRepository list_all raises DatabaseError on OperationalError
    Given an automation profile repository with session raising OperationalError on query
    When all automation profiles are listed and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to list profiles"

  # --- upsert: OperationalError -> DatabaseError ----------------------------

  @profile_repo @transient_error
  Scenario: AutomationProfileRepository upsert raises DatabaseError on OperationalError
    Given an automation profile repository with session raising OperationalError on query
    When an automation profile is upserted and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to upsert profile"

  # --- delete: OperationalError -> DatabaseError ----------------------------

  @profile_repo @transient_error
  Scenario: AutomationProfileRepository delete raises DatabaseError on OperationalError
    Given an automation profile repository with session raising OperationalError on query
    When an automation profile is deleted and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to delete profile"

  # ==========================================================================
  # ResourceTypeRepository error handling
  # ==========================================================================

  # --- create: IntegrityError (non-UNIQUE) -> DatabaseError -----------------

  @resource_type_repo @transient_error
  Scenario: ResourceTypeRepository create raises DatabaseError on non-UNIQUE IntegrityError
    Given a resource type repository with session that passes query but raises non-UNIQUE IntegrityError on flush
    When a resource type is created and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to create resource type"

  # --- create: OperationalError -> DatabaseError ----------------------------

  @resource_type_repo @transient_error
  Scenario: ResourceTypeRepository create raises DatabaseError on OperationalError
    Given a resource type repository with session raising OperationalError on flush
    When a resource type is created and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to create resource type"

  # --- get: OperationalError -> DatabaseError -------------------------------

  @resource_type_repo @transient_error
  Scenario: ResourceTypeRepository get raises DatabaseError on OperationalError
    Given a resource type repository with session raising OperationalError on query
    When a resource type is fetched by name and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to get resource type"

  # --- list_types: OperationalError -> DatabaseError ------------------------

  @resource_type_repo @transient_error
  Scenario: ResourceTypeRepository list_types raises DatabaseError on OperationalError
    Given a resource type repository with session raising OperationalError on query
    When resource types are listed and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to list resource types"

  # --- update: OperationalError -> DatabaseError ----------------------------

  @resource_type_repo @transient_error
  Scenario: ResourceTypeRepository update raises DatabaseError on OperationalError
    Given a resource type repository with session raising OperationalError on query
    When a resource type is updated and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to update resource type"

  # --- delete: OperationalError -> DatabaseError ----------------------------

  @resource_type_repo @transient_error
  Scenario: ResourceTypeRepository delete raises DatabaseError on OperationalError
    Given a resource type repository with session raising OperationalError on query
    When a resource type is deleted and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to delete resource type"

  # ==========================================================================
  # ResourceRepository error handling
  # ==========================================================================

  # --- create: IntegrityError (UNIQUE) -> DuplicateResourceError ------------

  @resource_repo @transient_error
  Scenario: ResourceRepository create raises DuplicateResourceError on UNIQUE IntegrityError
    Given a resource repository with session that passes initial queries but raises UNIQUE IntegrityError on flush
    When a resource is created and a repo error is expected
    Then a repo DuplicateResourceError should be raised

  # --- create: IntegrityError (non-UNIQUE) -> DatabaseError -----------------

  @resource_repo @transient_error
  Scenario: ResourceRepository create raises DatabaseError on non-UNIQUE IntegrityError
    Given a resource repository with session that passes initial queries but raises non-UNIQUE IntegrityError on flush
    When a resource is created and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to create resource"

  # --- create: OperationalError -> DatabaseError ----------------------------

  @resource_repo @transient_error
  Scenario: ResourceRepository create raises DatabaseError on OperationalError
    Given a resource repository with session raising OperationalError on flush
    When a resource is created and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to create resource"

  # --- get: OperationalError -> DatabaseError -------------------------------

  @resource_repo @transient_error
  Scenario: ResourceRepository get raises DatabaseError on OperationalError
    Given a resource repository with session raising OperationalError on query
    When a resource is fetched by ID and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to get resource"

  # --- get_by_name: OperationalError -> DatabaseError -----------------------

  @resource_repo @transient_error
  Scenario: ResourceRepository get_by_name raises DatabaseError on OperationalError
    Given a resource repository with session raising OperationalError on query
    When a resource is fetched by name and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to get resource by name"

  # --- list_resources: OperationalError -> DatabaseError --------------------

  @resource_repo @transient_error
  Scenario: ResourceRepository list_resources raises DatabaseError on OperationalError
    Given a resource repository with session raising OperationalError on query
    When resources are listed and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to list resources"

  # --- update: OperationalError -> DatabaseError ----------------------------

  @resource_repo @transient_error
  Scenario: ResourceRepository update raises DatabaseError on OperationalError
    Given a resource repository with session raising OperationalError on query
    When a resource is updated and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to update resource"

  # --- delete: OperationalError -> DatabaseError ----------------------------

  @resource_repo @transient_error
  Scenario: ResourceRepository delete raises DatabaseError on OperationalError
    Given a resource repository with session raising OperationalError on query
    When a resource is deleted and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to delete resource"

  # --- resolve_namespaced_name: OperationalError -> DatabaseError -----------

  @resource_repo @transient_error
  Scenario: ResourceRepository resolve_namespaced_name raises DatabaseError on OperationalError
    Given a resource repository with session raising OperationalError on query
    When a resource is resolved by name-or-id and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to resolve resource"

  # ==========================================================================
  # ProjectResourceLinkRepository error handling
  # ==========================================================================

  # --- create_link: IntegrityError -> DatabaseError -------------------------

  @link_repo @transient_error
  Scenario: ProjectResourceLinkRepository create_link raises DatabaseError on IntegrityError
    Given a project resource link repository with session that passes query but raises IntegrityError on flush
    When a project resource link is created and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to create link"

  # --- create_link: OperationalError -> DatabaseError -----------------------

  @link_repo @transient_error
  Scenario: ProjectResourceLinkRepository create_link raises DatabaseError on OperationalError
    Given a project resource link repository with session raising OperationalError on flush
    When a project resource link is created and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to create link"

  # --- list_links: OperationalError -> DatabaseError ------------------------

  @link_repo @transient_error
  Scenario: ProjectResourceLinkRepository list_links raises DatabaseError on OperationalError
    Given a project resource link repository with session raising OperationalError on query
    When project resource links are listed and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to list links"

  # --- get_link: OperationalError -> DatabaseError --------------------------

  @link_repo @transient_error
  Scenario: ProjectResourceLinkRepository get_link raises DatabaseError on OperationalError
    Given a project resource link repository with session raising OperationalError on query
    When a project resource link is fetched and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to get link"

  # --- remove_link: OperationalError -> DatabaseError -----------------------

  @link_repo @transient_error
  Scenario: ProjectResourceLinkRepository remove_link raises DatabaseError on OperationalError
    Given a project resource link repository with session raising OperationalError on query
    When a project resource link is removed and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to remove link"

  # ==========================================================================
  # ValidationAttachmentRepository error handling
  # ==========================================================================

  # --- attach: IntegrityError (UNIQUE) -> DuplicateValidationAttachmentError

  @validation_repo @transient_error
  Scenario: ValidationAttachmentRepository attach raises DuplicateValidationAttachmentError on UNIQUE IntegrityError
    Given a validation attachment repository with session that passes query but raises UNIQUE IntegrityError on flush
    When a validation is attached and a repo error is expected
    Then a repo DuplicateValidationAttachmentError should be raised

  # --- attach: IntegrityError (non-UNIQUE) -> DatabaseError -----------------

  @validation_repo @transient_error
  Scenario: ValidationAttachmentRepository attach raises DatabaseError on non-UNIQUE IntegrityError
    Given a validation attachment repository with session that passes query but raises non-UNIQUE IntegrityError on flush
    When a validation is attached and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to attach validation"

  # --- attach: OperationalError -> DatabaseError ----------------------------

  @validation_repo @transient_error
  Scenario: ValidationAttachmentRepository attach raises DatabaseError on OperationalError
    Given a validation attachment repository with session raising OperationalError on flush
    When a validation is attached and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to attach validation"

  # --- detach: OperationalError -> DatabaseError ----------------------------

  @validation_repo @transient_error
  Scenario: ValidationAttachmentRepository detach raises DatabaseError on OperationalError
    Given a validation attachment repository with session raising OperationalError on query
    When a validation is detached and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to detach validation"

  # --- list_for_resource: OperationalError -> DatabaseError -----------------

  @validation_repo @transient_error
  Scenario: ValidationAttachmentRepository list_for_resource raises DatabaseError on OperationalError
    Given a validation attachment repository with session raising OperationalError on query
    When validation attachments are listed and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to list attachments"

  # ==========================================================================
  # LifecyclePlanRepository error handling
  # ==========================================================================

  # --- create: IntegrityError (non-UNIQUE) -> DatabaseError -----------------

  @plan_repo @transient_error
  @tdd_issue @tdd_issue_4260 @tdd_expected_fail
  Scenario: LifecyclePlanRepository create raises DatabaseError on non-UNIQUE IntegrityError
    Given a lifecycle plan repository with session raising non-UNIQUE IntegrityError on flush
    When a lifecycle plan is created and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to create plan"

  # --- create: OperationalError -> DatabaseError ----------------------------

  @plan_repo @transient_error
  @tdd_issue @tdd_issue_4260 @tdd_expected_fail
  Scenario: LifecyclePlanRepository create raises DatabaseError on OperationalError
    Given a lifecycle plan repository with session raising OperationalError on flush
    When a lifecycle plan is created and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to create plan"

  # --- get: OperationalError -> DatabaseError -------------------------------

  @plan_repo @transient_error
  Scenario: LifecyclePlanRepository get raises DatabaseError on OperationalError
    Given a lifecycle plan repository with session raising OperationalError on query
    When a lifecycle plan is fetched by ID and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to get plan"

  # --- get_by_name: OperationalError -> DatabaseError -----------------------

  @plan_repo @transient_error
  Scenario: LifecyclePlanRepository get_by_name raises DatabaseError on OperationalError
    Given a lifecycle plan repository with session raising OperationalError on query
    When a lifecycle plan is fetched by name and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to get plan by name"

  # --- update: OperationalError -> DatabaseError ----------------------------

  @plan_repo @transient_error
  Scenario: LifecyclePlanRepository update raises DatabaseError on OperationalError
    Given a lifecycle plan repository with session raising OperationalError on query
    When a lifecycle plan is updated and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to update plan"

  # --- delete: OperationalError -> DatabaseError ----------------------------

  @plan_repo @transient_error
  Scenario: LifecyclePlanRepository delete raises DatabaseError on OperationalError
    Given a lifecycle plan repository with session raising OperationalError on query
    When a lifecycle plan is deleted and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to delete plan"

  # --- list_plans: OperationalError -> DatabaseError ------------------------

  @plan_repo @transient_error
  Scenario: LifecyclePlanRepository list_plans raises DatabaseError on OperationalError
    Given a lifecycle plan repository with session raising OperationalError on query
    When lifecycle plans are listed and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to list plans"

  # --- count: OperationalError -> DatabaseError -----------------------------

  @plan_repo @transient_error
  Scenario: LifecyclePlanRepository count raises DatabaseError on OperationalError
    Given a lifecycle plan repository with session raising OperationalError on query
    When lifecycle plans are counted and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to count plans"

  # ==========================================================================
  # ResourceRepository DAG operations error handling
  # ==========================================================================

  # --- link_child: IntegrityError -> DatabaseError --------------------------

  @resource_repo @dag @transient_error
  Scenario: ResourceRepository link_child raises DatabaseError on IntegrityError
    Given a resource repository with session that has resources but raises IntegrityError on link flush
    When a resource child is linked and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to link"

  # --- link_child: OperationalError -> DatabaseError ------------------------

  @resource_repo @dag @transient_error
  Scenario: ResourceRepository link_child raises DatabaseError on OperationalError during link
    Given a resource repository with session that has resources but raises OperationalError on link flush
    When a resource child is linked and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to link"

  # --- unlink_child: OperationalError -> DatabaseError ----------------------

  @resource_repo @dag @transient_error
  Scenario: ResourceRepository unlink_child raises DatabaseError on OperationalError
    Given a resource repository with session raising OperationalError on query
    When a resource child is unlinked and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to unlink"

  # --- get_children: OperationalError -> DatabaseError ----------------------

  @resource_repo @dag @transient_error
  Scenario: ResourceRepository get_children raises DatabaseError on OperationalError
    Given a resource repository with session raising OperationalError on query
    When resource children are fetched and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to get children"

  # --- get_parents: OperationalError -> DatabaseError -----------------------

  @resource_repo @dag @transient_error
  Scenario: ResourceRepository get_parents raises DatabaseError on OperationalError
    Given a resource repository with session raising OperationalError on query
    When resource parents are fetched and a repo error is expected
    Then a repo DatabaseError should be raised containing "Failed to get parents"
