Gherkin Akzeptanzkriterien — Cartly Phase 1

Status: Draft (QA)
Issue: CAR-14
Datum: 2026-07-03
Owner: QA / DEV
PRD-Reference: PRD-001 v0.7


Konventionen


F1 — User Registration & Authentication

F1.1 Email/Password Login

Feature: F1.1 Email/Password Login

  Background:
    Given the system is operating normally
    And no user is currently authenticated

  Scenario: AC-001 — Erfolgreicher Login mit gültigen Credentials
    Given a User with email "sarah@meinladen.de" and password "Sicher!23" exists
    And the user's account is active
    When the user submits login with email "sarah@meinladen.de" and password "Sicher!23"
    Then the response status is 200
    And the response contains a valid JWT access token
    And the response contains a valid JWT refresh token (HTTP-only cookie)
    And the user's role and company_id are encoded in the token claims

  Scenario: AC-002 — Login schlägt fehl bei falschem Passwort
    Given a User with email "sarah@meinladen.de" and password "Sicher!23" exists
    When the user submits login with email "sarah@meinladen.de" and password "Falsch!45"
    Then the response status is 401
    And the response contains no JWT tokens
    And the response contains error "Invalid credentials"

  Scenario: AC-003 — Login schlägt fehl bei nicht existierendem User
    Given no user with email "unbekannt@test.de" exists
    When the user submits login with email "unbekannt@test.de" and password "Irgendwas!"
    Then the response status is 401
    And the response contains error "Invalid credentials"

  Scenario: AC-004 — Login schlägt fehl bei leerem Email-Feld
    When the user submits login with email "" and password "Sicher!23"
    Then the response status is 400
    And the response contains validation error for field "email"

  Scenario: AC-005 — Login schlägt fehl bei ungültigem Email-Format
    When the user submits login with email "kein-email" and password "Sicher!23"
    Then the response status is 400
    And the response contains validation error for field "email"

  Scenario: AC-006 — Refresh Token wird akzeptiert und liefert neuen Access Token
    Given a User with email "sarah@meinladen.de" and password "Sicher!23" exists
    And the user has previously logged in and holds a valid refresh token
    When the user submits a refresh request with the refresh token
    Then the response status is 200
    And the response contains a new valid JWT access token
    And the previous access token is invalidated

  Scenario: AC-007 — Refresh Token läuft ab → 401
    Given a User with email "sarah@meinladen.de" and password "Sicher!23" exists
    And the user holds an expired refresh token
    When the user submits a refresh request with the expired refresh token
    Then the response status is 401
    And the response contains error "Token expired"

  Scenario: AC-008 — Geburtsland/Company-Binding bei Login
    Given a User with email "sarah@meinladen.de" and password "Sicher!23" belongs to Company "Mein Laden GmbH"
    When the user submits login with email "sarah@meinladen.de" and password "Sicher!23"
    Then the JWT access token contains the company_id of "Mein Laden GmbH"
    And the user can only access resources belonging to that company

F1.2 User Registration

Feature: F1.2 User Registration

  Background:
    Given the system is operating normally

  Scenario: AC-010 — Erfolgreiche Registration mit Email-Verifikation
    When a prospective user submits registration with:
      | email           | password    | company_name       |
      | sarah@test.de | Sicher!23  | Mein Laden GmbH   |
    Then the response status is 201
    And a User record is created with email "sarah@test.de"
    And a Company record is created with name "Mein Laden GmbH"
    And the user is assigned the role "admin" for that company
    And an verification email is sent to "sarah@test.de"
    And the user's account is in status "pending_verification"

  Scenario: AC-011 — Registration mit bereits existierender Email → Fehler
    Given a User with email "sarah@test.de" already exists
    When a prospective user submits registration with:
      | email          | password    | company_name      |
      | sarah@test.de | Sicher!23  | Mein Laden GmbH  |
    Then the response status is 409
    And the response contains error "Email already registered"

  Scenario: AC-012 — Registration mit schwachem Passwort → Fehler
    When a prospective user submits registration with:
      | email          | password | company_name      |
      | sarah@test.de | 12345   | Mein Laden GmbH  |
    Then the response status is 400
    And the response contains validation error for field "password"
    And the error message indicates password complexity requirements

  Scenario: AC-013 — Email-Verifikation erfolgreich
    Given a User with email "sarah@test.de" exists with status "pending_verification"
    And a valid verification token was sent to "sarah@test.de"
    When the user submits email verification with the valid token
    Then the response status is 200
    And the user's account status changes to "active"
    And the verification token is invalidated

  Scenario: AC-014 — Email-Verifikation mit ungültigem Token → Fehler
    Given a User with email "sarah@test.de" exists
    When the user submits email verification with token "invalid-token-xyz"
    Then the response status is 400
    And the response contains error "Invalid or expired verification token"

F1.3 Password Reset Flow

Feature: F1.3 Password Reset Flow

  Background:
    Given the system is operating normally

  Scenario: AC-020 — Password Reset Email angefordert
    Given a User with email "sarah@test.de" exists
    When the user requests a password reset for "sarah@test.de"
    Then the response status is 200
    And a password reset email is sent to "sarah@test.de"
    And a reset token is stored with 1-hour expiry

  Scenario: AC-021 — Password Reset für nicht-existierenden User gibt 200 zurück (Security)
    Given no user with email "unbekannt@test.de" exists
    When the user requests a password reset for "unbekannt@test.de"
    Then the response status is 200
    And no email is sent (preventing email enumeration)

  Scenario: AC-022 — Passwort mit gültigem Token zurücksetzen
    Given a User with email "sarah@test.de" exists
    And a valid password reset token was sent to "sarah@test.de"
    When the user submits new password "NeuesPasswort!99" with the valid token
    Then the response status is 200
    And the user's password is updated to match "NeuesPasswort!99"
    And all existing sessions are invalidated
    And the reset token is invalidated

  Scenario: AC-023 — Passwort-Reset mit abgelaufenem Token → Fehler
    Given a User with email "sarah@test.de" exists
    And an expired password reset token was sent to "sarah@test.de"
    When the user submits new password "NeuesPasswort!99" with the expired token
    Then the response status is 400
    And the response contains error "Token expired"
    And the password remains unchanged

F2 — Role-Based Access Control (RBAC)

F2.1 Role Definitions

Feature: F2.1 Role-Based Access Control — Roles & Permissions

  Background:
    Given Company "Testladen GmbH" exists
    And Store "Filiale Berlin" belongs to Company "Testladen GmbH"
    And the following users exist for Company "Testladen GmbH":
      | email                    | role    |
      | admin@testladen.de      | admin   |
      | manager@testladen.de    | manager |
      | sales@testladen.de      | sales   |

  Scenario: AC-030 — Admin kann alle Ressourcen verwalten
    Given the user "admin@testladen.de" is authenticated as "admin" for Company "Testladen GmbH"
    When the user attempts to create a new Store for Company "Testladen GmbH"
    Then the request is allowed
    And the user attempts to delete any User in the company
    Then the request is allowed
    And the user attempts to access the Billing/Settings section
    Then the request is allowed

  Scenario: AC-031 — Manager kann keine Admin-Operationen durchführen
    Given the user "manager@testladen.de" is authenticated as "manager" for Company "Testladen GmbH"
    When the user attempts to delete another User in the company
    Then the request is denied with 403 Forbidden
    And the user attempts to change company billing settings
    Then the request is denied with 403 Forbidden
    And the user attempts to delete a Store
    Then the request is denied with 403 Forbidden

  Scenario: AC-032 — Sales kann nur POS-Operationen durchführen
    Given the user "sales@testladen.de" is authenticated as "sales" for Company "Testladen GmbH"
    When the user attempts to process a sale at the POS
    Then the request is allowed
    And the user attempts to create a Product
    Then the request is denied with 403 Forbidden
    And the user attempts to view reporting dashboard
    Then the request is denied with 403 Forbidden

  Scenario: AC-033 — Unauthenticated User wird auf Login redirectet
    Given no user is authenticated
    When the user attempts to access any protected endpoint
    Then the response status is 401
    And the response contains error "Authentication required"

  Scenario: AC-034 — User aus anderem Company wird abgelehnt (Tenant Isolation)
    Given Company "Anderer Laden GmbH" exists
    And a User "other@andererladen.de" with role "admin" belongs to Company "Anderer Laden GmbH"
    And the user "other@andererladen.de" is authenticated
    When the user attempts to access resources of Company "Testladen GmbH"
    Then the request is denied with 403 Forbidden
    And the response contains error "Access denied"

F2.2 Role Assignment & Management

Feature: F2.2 Role Assignment & Management

  Background:
    Given Company "Testladen GmbH" exists
    And User "admin@testladen.de" with role "admin" exists in Company "Testladen GmbH"
    And User "manager@testladen.de" exists in Company "Testladen GmbH"

  Scenario: AC-040 — Admin kann Rolle eines Users ändern
    Given the user "admin@testladen.de" is authenticated as "admin"
    When the admin changes the role of "manager@testladen.de" from "none" to "manager"
    Then the request is allowed with 200
    And User "manager@testladen.de" now has role "manager"

  Scenario: AC-041 — Manager kann keine Rollen ändern
    Given the user "admin@testladen.de" is authenticated as "admin"
    And User "manager@testladen.de" has role "manager"
    And the user "manager@testladen.de" is authenticated as "manager"
    When the manager attempts to change the role of any user
    Then the request is denied with 403 Forbidden

  Scenario: AC-042 — Admin kann User aus der Company entfernen
    Given the user "admin@testladen.de" is authenticated as "admin"
    When the admin removes User "manager@testladen.de" from the company
    Then the request is allowed with 200
    And User "manager@testladen.de" no longer has access to Company "Testladen GmbH"

  Scenario: AC-043 — User kann eigene Rolle nicht ändern
    Given the user "admin@testladen.de" is authenticated as "admin"
    When the admin attempts to change their own role to "sales"
    Then the request is denied with 400
    And the response contains error "Cannot change own role"

F3 — Company & Store Setup

F3.1 Company Management

Feature: F3.1 Company Management

  Background:
    Given the system is operating normally

  Scenario: AC-050 — Company-Admin kann Company-Daten bearbeiten
    Given a Company "Mein Laden GmbH" exists
    And User "admin@meinladen.de" with role "admin" belongs to Company "Mein Laden GmbH"
    And the user "admin@meinladen.de" is authenticated
    When the admin updates company details:
      | field     | value               |
      | name      | Mein Laden GmbH II |
      | theme     | {"primary": "#000"} |
    Then the response status is 200
    And Company "Mein Laden GmbH" now has name "Mein Laden GmbH II"

  Scenario: AC-051 — Nicht-Admin kann Company-Daten nicht bearbeiten
    Given a Company "Mein Laden GmbH" exists
    And User "manager@meinladen.de" with role "manager" belongs to Company "Mein Laden GmbH"
    And the user "manager@meinladen.de" is authenticated
    When the user attempts to update company details
    Then the request is denied with 403 Forbidden

  Scenario: AC-052 — Company mit ungültigen Daten → Validation Error
    Given User "admin@meinladen.de" is authenticated as "admin"
    When the admin attempts to update company with name ""
    Then the response status is 400
    And the response contains validation error for field "name"

F3.2 Store Management

Feature: F3.2 Store Management

  Background:
    Given Company "Mein Laden GmbH" exists
    And User "admin@meinladen.de" with role "admin" belongs to Company "Mein Laden GmbH"
    And User "manager@meinladen.de" with role "manager" belongs to Company "Mein Laden GmbH"

  Scenario: AC-060 — Admin kann neuen Store anlegen
    Given the user "admin@meinladen.de" is authenticated as "admin"
    When the admin creates a new Store with:
      | name       | address                    | timezone      | currency |
      | Filiale 1  | Hauptstraße 1, 10115 Berlin | Europe/Berlin | EUR      |
    Then the response status is 201
    And a Store record exists with name "Filiale 1"
    And the Store is linked to Company "Mein Laden GmbH"

  Scenario: AC-061 — Manager kann keinen Store anlegen
    Given the user "manager@meinladen.de" is authenticated as "manager"
    When the manager attempts to create a new Store
    Then the request is denied with 403 Forbidden

  Scenario: AC-062 — Admin kann Store bearbeiten
    Given Store "Filiale 1" belongs to Company "Mein Laden GmbH"
    And the user "admin@meinladen.de" is authenticated as "admin"
    When the admin updates Store "Filiale 1" with name "Filiale Zentrum"
    Then the response status is 200
    And Store "Filiale 1" now has name "Filiale Zentrum"

  Scenario: AC-063 — Admin kann Store deaktivieren (Soft Delete)
    Given Store "Filiale 1" belongs to Company "Mein Laden GmbH"
    And the user "admin@meinladen.de" is authenticated as "admin"
    When the admin deactivates Store "Filiale 1"
    Then the response status is 200
    And Store "Filiale 1" is no longer accessible in active store lists
    But Store "Filiale 1" still exists in the database with deleted_at set

  Scenario: AC-064 — Store-Auswahl bei Multi-Store Company
    Given Company "Mein Laden GmbH" has 3 Stores
    And User "manager@meinladen.de" is authenticated as "manager"
    And User "manager@meinladen.de" is assigned to all 3 stores
    When the user requests the list of accessible stores
    Then the response contains exactly 3 stores
    And each store belongs to Company "Mein Laden GmbH"

F4 — User Management

F4.1 User CRUD

Feature: F4.1 User Management (CRUD)

  Background:
    Given Company "Mein Laden GmbH" exists
    And Store "Filiale 1" belongs to Company "Mein Laden GmbH"
    And User "admin@meinladen.de" with role "admin" belongs to Company "Mein Laden GmbH"
    And User "manager@meinladen.de" with role "manager" belongs to Company "Mein Laden GmbH"

  Scenario: AC-070 — Admin kann neuen User einladen
    Given the user "admin@meinladen.de" is authenticated as "admin"
    When the admin creates an invitation for "neuer@test.de" with role "sales" for Store "Filiale 1"
    Then the response status is 201
    And an invitation email is sent to "neuer@test.de"
    And a User record in status "pending" is created

  Scenario: AC-071 — Admin kann alle Users der Company auflisten
    Given the user "admin@meinladen.de" is authenticated as "admin"
    When the admin requests the list of all users in Company "Mein Laden GmbH"
    Then the response status is 200
    And the list contains exactly the 2 existing users
    And each user has their assigned role

  Scenario: AC-072 — Manager kann keine Users anderer Stores sehen
    Given Company "Mein Laden GmbH" has Store "Filiale 1" and Store "Filiale 2"
    And User "alice@meinladen.de" (role "manager") is assigned only to "Filiale 1"
    And User "bob@meinladen.de" (role "sales") is assigned only to "Filiale 2"
    And the user "alice@meinladen.de" is authenticated as "manager"
    When the manager requests the list of all users
    Then the response only contains users assigned to "Filiale 1"

  Scenario: AC-073 — Admin kann User bearbeiten (Rolle ändern)
    Given User "manager@meinladen.de" has role "manager"
    And the user "admin@meinladen.de" is authenticated as "admin"
    When the admin changes User "manager@meinladen.de" from role "manager" to role "sales"
    Then the response status is 200
    And User "manager@meinladen.de" now has role "sales"

  Scenario: AC-074 — Admin kann User deaktivieren
    Given User "manager@meinladen.de" belongs to Company "Mein Laden GmbH"
    And the user "admin@meinladen.de" is authenticated as "admin"
    When the admin deactivates User "manager@meinladen.de"
    Then the response status is 200
    And User "manager@meinladen.de" can no longer log in
    But the user record is not deleted from the database

  Scenario: AC-075 — Deaktivierter User erhält 401 bei Login
    Given User "deactivated@test.de" exists with status "deactivated"
    When the user submits login with email "deactivated@test.de" and password "Sicher!23"
    Then the response status is 401
    And the response contains error "Account deactivated"

F5 — Product Stammdaten

F5.1 Product Management

Feature: F5.1 Product Stammdaten — CRUD

  Background:
    Given Company "Mein Laden GmbH" exists
    And Category "Bekleidung" belongs to Company "Mein Laden GmbH"
    And User "admin@meinladen.de" with role "admin" belongs to Company "Mein Laden GmbH"

  Scenario: AC-080 — Admin kann Produkt mit allen Pflichtfeldern anlegen
    Given the user "admin@meinladen.de" is authenticated as "admin"
    When the admin creates a Product with:
      | name        | sku_code    | price   | category  | attributes |
      | T-Shirt Rot | TS-001-R    | 29.99   | Bekleidung| {"size": "M", "color": "red"} |
    Then the response status is 201
    And a Product record exists with name "T-Shirt Rot"
    And the product has status "active"
    And the product's tenant_id matches Company "Mein Laden GmbH"

  Scenario: AC-081 — Produktanlage ohne Pflichtfeld "name" → Fehler
    Given the user "admin@meinladen.de" is authenticated as "admin"
    When the admin creates a Product with:
      | sku_code | price |
      | TS-001   | 29.99 |
    Then the response status is 400
    And the response contains validation error for field "name"

  Scenario: AC-082 — Produkt mit Fashion-spezifischen Attributen (Size, Color, Season)
    Given the user "admin@meinladen.de" is authenticated as "admin"
    When the admin creates a Product with Fashion-Attributes:
      | name          | sku_code | price  | size  | color | season   |
      | Winterjacke M | WJ-001   | 149.99 | M     | navy  | Winter23 |
    Then the response status is 201
    And the Product's attributes contain size "M"
    And the Product's attributes contain color "navy"
    And the Product's attributes contain season "Winter23"

  Scenario: AC-083 — Admin kann Produkt bearbeiten
    Given Product "T-Shirt Rot" exists with price 29.99
    And the user "admin@meinladen.de" is authenticated as "admin"
    When the admin updates Product "T-Shirt Rot" with price 34.99
    Then the response status is 200
    And Product "T-Shirt Rot" now has price 34.99
    And updated_at timestamp is refreshed

  Scenario: AC-084 — Admin kann Produkt deaktivieren (Soft Delete)
    Given Product "T-Shirt Rot" exists
    And the user "admin@meinladen.de" is authenticated as "admin"
    When the admin deactivates Product "T-Shirt Rot"
    Then the response status is 200
    And Product "T-Shirt Rot" does not appear in the active product catalog
    But the record still exists with deleted_at set

  Scenario: AC-085 — Manager kann Produkte anlegen
    Given User "manager@meinladen.de" with role "manager" belongs to Company "Mein Laden GmbH"
    And the user "manager@meinladen.de" is authenticated as "manager"
    When the manager creates a Product with name "Hose Blau" and sku "HB-001"
    Then the request is allowed with 201

  Scenario: AC-086 — Sales kann keine Produkte anlegen
    Given User "sales@meinladen.de" with role "sales" belongs to Company "Mein Laden GmbH"
    And the user "sales@meinladen.de" is authenticated as "sales"
    When the sales user attempts to create a Product
    Then the request is denied with 403 Forbidden

  Scenario: AC-087 — User aus Company A sieht keine Produkte aus Company B
    Given Company "Anderer Laden GmbH" exists
    And Product "Exklusiv-Hose" belongs to Company "Anderer Laden GmbH"
    And User "admin@meinladen.de" is authenticated as "admin" for Company "Mein Laden GmbH"
    When the user searches for products
    Then Product "Exklusiv-Hose" is NOT in the results

F5.2 Category Management

Feature: F5.2 Category Management

  Background:
    Given Company "Mein Laden GmbH" exists
    And User "admin@meinladen.de" with role "admin" belongs to Company "Mein Laden GmbH"
    And Category "Oberteile" exists in Company "Mein Laden GmbH"

  Scenario: AC-090 — Admin kann Hauptkategorie anlegen
    Given the user "admin@meinladen.de" is authenticated as "admin"
    When the admin creates a Category "Hosen" with slug "hosen"
    Then the response status is 201
    And a Category record exists with name "Hosen" and slug "hosen"
    And the category's tenant_id matches Company "Mein Laden GmbH"

  Scenario: AC-091 — Admin kann Unterkategorie anlegen (Parent-ID)
    Given Category "Oberteile" exists with slug "oberteile"
    And the user "admin@meinladen.de" is authenticated as "admin"
    When the admin creates a Category "T-Shirts" with slug "t-shirts" and parent_id of "Oberteile"
    Then the response status is 201
    And Category "T-Shirts" has parent_id pointing to "Oberteile"
    And a product can be assigned to "T-Shirts" and appears under "Oberteile" in the category tree

  Scenario: AC-092 — Kategorie mit ungültigem Parent-ID → Fehler
    Given the user "admin@meinladen.de" is authenticated as "admin"
    When the admin attempts to create a Category with non-existent parent_id "999"
    Then the response status is 400
    And the response contains error "Parent category not found"

  Scenario: AC-093 — Manager kann Kategorien nicht löschen
    Given User "manager@meinladen.de" with role "manager" belongs to Company "Mein Laden GmbH"
    And the user "manager@meinladen.de" is authenticated as "manager"
    When the manager attempts to delete Category "Oberteile"
    Then the request is denied with 403 Forbidden

F5.3 SKU & Inventory

Feature: F5.3 SKU Management & Stock Quantity

  Background:
    Given Company "Mein Laden GmbH" exists
    And Store "Filiale 1" belongs to Company "Mein Laden GmbH"
    And Store "Filiale 2" belongs to Company "Mein Laden GmbH"
    And Product "T-Shirt" with SKU "TS-001" exists
    And User "admin@meinladen.de" with role "admin" belongs to Company "Mein Laden GmbH"

  Scenario: AC-100 — SKU-Code muss pro Company eindeutig sein
    Given another Product with SKU "TS-001" already exists in Company "Mein Laden GmbH"
    And the user "admin@meinladen.de" is authenticated as "admin"
    When the admin creates a Product with SKU "TS-001"
    Then the response status is 409
    And the response contains error "SKU already exists in this company"

  Scenario: AC-101 — Bestand kann pro Store unterschiedlich sein
    Given Product "T-Shirt" (SKU "TS-001") exists
    And the user "admin@meinladen.de" is authenticated as "admin"
    When the admin sets stock quantity for SKU "TS-001" at Store "Filiale 1" to 50
    And the admin sets stock quantity for SKU "TS-001" at Store "Filiale 2" to 0
    Then the response status is 200
    And Store "Filiale 1" shows stock_qty = 50 for SKU "TS-001"
    And Store "Filiale 2" shows stock_qty = 0 for SKU "TS-001"

  Scenario: AC-102 — Bestandsauskunft für nicht-existierenden SKU → leer
    Given the user "admin@meinladen.de" is authenticated as "admin"
    When the admin queries stock for SKU "UNBEKANNT-999"
    Then the response status is 200
    And the stock quantity is 0
    And no inventory record is created

F6 — Authentication Security & Edge Cases

Feature: F6 Authentication Security & Edge Cases

  Background:
    Given the system is operating normally

  Scenario: AC-110 — Mehr als 5 fehlgeschlagene Login-Versuche → Account temporär gesperrt (30 min)
    Given a User with email "sarah@test.de" and password "Sicher!23" exists
    When the user fails to log in 6 times consecutively with wrong password
    Then the account is locked for 30 minutes
    And the next login attempt with correct password returns 429
    And the response contains error "Account temporarily locked"

  Scenario: AC-111 — JWT-Token Manipulation → 401
    Given a User is authenticated with a valid JWT
    When the user modifies the JWT payload (changing role to "admin")
    And the user sends a request with the modified token
    Then the response status is 401
    And the response contains error "Invalid token signature"

  Scenario: AC-112 — Rate Limiting bei Login — mehr als 20 Versuche/Minute → 429
    When an anonymous user attempts to log in 21 times within 1 minute
    Then the 21st request returns 429 Too Many Requests
    And the response contains error "Rate limit exceeded"

  Scenario: AC-113 — XSS im Email-Feld bei Registration → escaped
    When a prospective user submits registration with email "<script>alert(1)</script>@test.de"
    Then the response status is 400
    And the system does not store the raw script in the database

  Scenario: AC-114 — SQL Injection in Login-Feld → Parameterized Query
    When an anonymous user submits login with email "' OR '1'='1" and password "anything"
    Then the request is handled safely via parameterized queries
    And the response status is 401 (no SQL error leaked)

  Scenario: AC-115 — Access Token läuft nach 15 Minuten ab
    Given a User has a valid access token issued 16 minutes ago
    When the user sends a request with that access token
    Then the response status is 401
    And the response contains error "Token expired"

F7 — Multi-Tenant Isolation

Feature: F7 Multi-Tenant Data Isolation

  Background:
    Given Company "Laden A GmbH" exists
    And Company "Laden B GmbH" exists

  Scenario: AC-120 — User aus Company A sieht keine Daten von Company B
    Given Product "Exklusiv-A" belongs to Company "Laden A GmbH"
    And Product "Exklusiv-B" belongs to Company "Laden B GmbH"
    And User "user-a@ladenia.de" is authenticated for Company "Laden A GmbH"
    When the user searches for products
    Then only Product "Exklusiv-A" appears in results
    And Product "Exklusiv-B" is not returned

  Scenario: AC-121 — User mit Zugriff auf mehrere Stores sieht nur eigene Stores
    Given Company "Laden A GmbH" has Store "Store-A1" and Store "Store-A2"
    And User "manager-a@ladenia.de" (role "manager") is assigned only to Store "Store-A1"
    When the user requests accessible stores
    Then only Store "Store-A1" is returned
    And Store "Store-A2" is not accessible

  Scenario: AC-122 — Row-Level Security — direkte DB-Abfrage ohne Tenant-ID → 0 Rows
    Given Company "Laden A GmbH" exists
    And Company "Laden B GmbH" exists
    When a DB query is executed without tenant_id filter (simulating misconfigured RLS)
    Then the database-level Row-Level Security policy blocks the query
    And no cross-tenant data is accessible at the database layer

  Scenario: AC-123 — API-Response enthält niemals Daten aus anderem Tenant
    Given User "attacker@malicious.de" attempts to access Company "Laden B" resources via IDOR
    When the user sends GET /api/companies/laden-b-uuid/stores
    Then the response is 403 Forbidden
    And no store data from "Laden B" is leaked in the response

Test Coverage Summary

Feature Must-Have Szenarien Edge-Case Szenarien Total
F1.1 Login 3 5 8
F1.2 Registration 1 3 4
F1.3 Password Reset 1 3 4
F2.1 RBAC Roles 3 1 4
F2.2 Role Assignment 2 2 4
F3.1 Company Mgmt 1 2 3
F3.2 Store Mgmt 3 1 4
F4.1 User CRUD 3 2 5
F5.1 Products 3 4 7
F5.2 Categories 2 2 4
F5.3 SKU/Stock 1 3 4
F6 Security 2 4 6
F7 Multi-Tenant 1 3 4
Total 25 35 60

Ziel: ≥ 80% Coverage = 48/60 Szenarien automatisiert. Alle Critical-Path-Szenarien (AC-001, AC-030, AC-050, AC-060, AC-070, AC-080, AC-087) müssen in CI.


Sprint 2 — Stammdaten: Products, Categories, Attributes (Gherkin AK)

Hinzugefügt: 2026-07-03 von PM (95ed75cd)
Issue: CAR-22 — Sprint-2 Stories + Gherkin AK


S2-F1 Product CRUD

Feature: S2-F1 Product CRUD

  Background:
    Given Company "Testladen GmbH" exists
    And Store "Hauptfiliale" belongs to Company "Testladen GmbH"
    And the authenticated user is an Admin of Company "Testladen GmbH"

  Scenario: SP-AC001 — Admin erstellt ein Product mit Pflichtfeldern
    When the Admin submits a create product request with:
      | name           | sku        | price   | category    |
      | Baumwoll-T-Shirt | TST-001   | 29.99   | Bekleidung  |
    Then the response status is 201
    And a Product record exists with name "Baumwoll-T-Shirt" and sku "TST-001"
    And the product belongs to Company "Testladen GmbH"

  Scenario: SP-AC002 — Product-Name ist Pflichtfeld
    When the Admin submits a create product request with:
      | sku    | price   |
      | TST-002 | 19.99   |
    Then the response status is 400
    And the response contains validation error for field "name"

  Scenario: SP-AC003 — SKU muss eindeutig sein pro Company
    Given a Product with sku "TST-001" belongs to Company "Testladen GmbH"
    When the Admin submits a create product request with:
      | name    | sku      | price   |
      | andere Ware | TST-001 | 9.99 |
    Then the response status is 409
    And the response contains error "SKU already exists in this company"

  Scenario: SP-AC004 — Admin liest alle Products einer Company
    Given Products "TST-001" and "TST-002" belong to Company "Testladen GmbH"
    When the Admin requests all products for Company "Testladen GmbH"
    Then the response status is 200
    And the response contains exactly 2 products

  Scenario: SP-AC005 — Admin updated ein Product
    Given a Product with name "Baumwoll-T-Shirt" and sku "TST-001" exists
    When the Admin submits an update request for product "TST-001" with:
      | name                    | price   |
      | Premium Baumwoll-T-Shirt | 34.99  |
    Then the response status is 200
    And the Product name is now "Premium Baumwoll-T-Shirt"
    And the Product price is now 34.99

  Scenario: SP-AC006 — Admin löscht ein Product (soft delete)
    Given a Product with sku "TST-001" exists
    When the Admin submits a delete request for product "TST-001"
    Then the response status is 200
    And the Product is marked as deleted (not returned in active product list)

  Scenario: SP-AC007 — Manager kann Products lesen aber nicht erstellen/löschen
    Given User "manager@test.de" has role "manager" at Store "Hauptfiliale"
    When the Manager requests all products for Company "Testladen GmbH"
    Then the response status is 200
    And the products are returned
    When the Manager submits a create product request with:
      | name    | sku      | price   |
      | Neue Ware | TST-999 | 9.99   |
    Then the response status is 403
    And the response contains error "Access denied"

  Scenario: SP-AC008 — Sales Rolle hat nur Leserechte für Products
    Given User "sales@test.de" has role "sales" at Store "Hauptfiliale"
    When the Sales user requests all products for Company "Testladen GmbH"
    Then the response status is 200
    And the products are returned
    When the Sales user submits a create product request with:
      | name    | sku      | price   |
      | Test    | TST-888 | 1.00    |
    Then the response status is 403
    And the response contains error "Access denied"

S2-F2 Category CRUD + Hierarchie

Feature: S2-F2 Category CRUD + Hierarchie

  Background:
    Given Company "Testladen GmbH" exists
    And the authenticated user is an Admin of Company "Testladen GmbH"

  Scenario: SP-AC010 — Admin erstellt eine Hauptkategorie
    When the Admin submits a create category request with:
      | name        | parent_id |
      | Bekleidung  | null      |
    Then the response status is 201
    And a Category record exists with name "Bekleidung"
    And the category has no parent (is a root category)

  Scenario: SP-AC011 — Admin erstellt eine Unterkategorie
    Given a Category "Bekleidung" exists
    When the Admin submits a create category request with:
      | name     | parent_id      |
      | T-Shirts | {category_id} |
    Then the response status is 201
    And a Category record exists with name "T-Shirts"
    And the category's parent is "Bekleidung"

  Scenario: SP-AC012 — Kategorie-Hierarchie wird korrekt zurückgegeben
    Given Categories "Bekleidung" → "T-Shirts" → "Basic Shirts" exist
    When the Admin requests the category tree
    Then the response status is 200
    And the category "Bekleidung" contains child "T-Shirts"
    And "T-Shirts" contains child "Basic Shirts"

  Scenario: SP-AC013 — Kategorie mit ungültigem parent_id → Fehler
    When the Admin submits a create category request with:
      | name        | parent_id            |
      | Orphan      | nonexistent-id-123  |
    Then the response status is 400
    And the response contains error "Parent category not found"

  Scenario: SP-AC014 — Kategorie wird gelöscht → Children werden zu Root
    Given Categories "Bekleidung" → "T-Shirts" exist
    When the Admin deletes category "Bekleidung"
    Then the response status is 200
    And category "T-Shirts" now has no parent (becomes a root category)

  Scenario: SP-AC015 — Category wird einem Product zugewiesen
    Given a Category "Bekleidung" exists
    And a Product "TST-001" exists
    When the Admin assigns category "Bekleidung" to product "TST-001"
    Then the response status is 200
    And the Product "TST-001" has category "Bekleidung"

S2-F3 Product Attributes (Size, Color, Season)

Feature: S2-F3 Product Attributes (Size, Color, Season)

  Background:
    Given Company "Testladen GmbH" exists
    And the authenticated user is an Admin of Company "Testladen GmbH"

  Scenario: SP-AC020 — Admin vergibt Size-Attribute (einzeln)
    Given a Product "TST-001" exists
    When the Admin adds attribute "size" with value "M" to product "TST-001"
    Then the response status is 200
    And the Product "TST-001" has attribute size = "M"

  Scenario: SP-AC021 — Admin vergibt mehrere Size-Attribute (Multi-Select)
    Given a Product "TST-001" exists
    When the Admin adds attribute "size" with values "S", "M", "L", "XL" to product "TST-001"
    Then the response status is 200
    And the Product "TST-001" has size variants: S, M, L, XL

  Scenario: SP-AC022 — Admin vergibt Color-Attribute
    Given a Product "TST-001" exists
    When the Admin adds attribute "color" with values "schwarz", "weiß" to product "TST-001"
    Then the response status is 200
    And the Product "TST-001" has color variants: schwarz, weiß

  Scenario: SP-AC023 — Admin vergibt Season-Attribute
    Given a Product "TST-001" exists
    When the Admin adds attribute "season" with value "Frühjahr/Sommer 2025" to product "TST-001"
    Then the response status is 200
    And the Product "TST-001" has season attribute = "Frühjahr/Sommer 2025"

  Scenario: SP-AC024 — Ungültiges Attribut → Fehler
    Given a Product "TST-001" exists
    When the Admin adds attribute "invalid_attribute" with value "test" to product "TST-001"
    Then the response status is 400
    And the response contains error "Unknown attribute type"

  Scenario: SP-AC025 — Season-Attribut ist optional (Product ohne Saison)
    Given a Product "TST-001" exists
    When the Admin creates a product without season attribute
    Then the response status is 201
    And the Product has no season constraint (ganzjährig verfügbar)

  Scenario: SP-AC026 — Product wird nach Size gefiltert
    Given Products "TST-S" (size=S) and "TST-M" (size=M) exist
    When the user requests products filtered by size "M"
    Then the response contains only the product with size "M"

S2-F4 SKU-Varianten (Größe × Farbe)

Feature: S2-F4 SKU-Varianten (Größe × Farbe)

  Background:
    Given Company "Testladen GmbH" exists
    And the authenticated user is an Admin of Company "Testladen GmbH"
    And a Category "Bekleidung" exists

  Scenario: SP-AC030 — Admin erstellt SKU-Varianten automatisch (Matrix)
    Given a Product "TST-001" exists with attributes:
      | size   | color    |
      | S, M   | schwarz  |
    When the Admin triggers SKU generation for product "TST-001"
    Then the response status is 200
    And the following SKU variants exist:
      | sku         | size | color   |
      | TST-001-S-S | S    | schwarz |
      | TST-001-M-S | M    | schwarz |

  Scenario: SP-AC031 — Admin erstellt vollständige Größe-Farbe-Matrix
    Given a Product "TST-001" exists with attributes:
      | size     | color       |
      | S, M, L  | schwarz, weiß |
    When the Admin triggers SKU generation for product "TST-001"
    Then the response status is 200
    And exactly 6 SKU variants are created (3 sizes × 2 colors)

  Scenario: SP-AC032 — SKU-Variante erhält eigenen Preis
    Given a Product "TST-001" exists with SKU variant "TST-001-S-S"
    When the Admin sets price 39.99 for SKU variant "TST-001-S-S"
    Then the response status is 200
    And the SKU variant "TST-001-S-S" has price 39.99

  Scenario: SP-AC033 — SKU-Variante erhält eigenen Bestand
    Given a Product "TST-001" exists with SKU variant "TST-001-S-S"
    When the Admin sets stock quantity 50 for SKU variant "TST-001-S-S"
    Then the response status is 200
    And the SKU variant "TST-001-S-S" has stock quantity 50

  Scenario: SP-AC034 — Bestand einer Variante wird reduziert ( Verkauf)
    Given SKU variant "TST-001-S-S" has stock quantity 50
    When the system reduces stock for SKU "TST-001-S-S" by 1 (sale)
    Then the SKU variant "TST-001-S-S" now has stock quantity 49

  Scenario: SP-AC035 — Bestand = 0 → Variante ist ausverkauft
    Given SKU variant "TST-001-S-S" has stock quantity 1
    When the system reduces stock for SKU "TST-001-S-S" by 1 (sale)
    Then the SKU variant "TST-001-S-S" has stock quantity 0
    And the variant is marked as out_of_stock

  Scenario: SP-AC036 — Verkauf mit unzureichendem Bestand → Fehler
    Given SKU variant "TST-001-S-S" has stock quantity 0
    When the system attempts to reduce stock for SKU "TST-001-S-S" by 1
    Then the response status is 409
    And the response contains error "Insufficient stock"

  Scenario: SP-AC037 — SKU-Variante wird aus dem Sortiment entfernt
    Given SKU variant "TST-001-S-S" exists
    When the Admin deactivates SKU variant "TST-001-S-S"
    Then the response status is 200
    And the SKU variant is not returned in active variant list

S2-F5 Product Images (S3 Upload)

Feature: S2-F5 Product Images (S3 Upload)

  Background:
    Given Company "Testladen GmbH" exists
    And the authenticated user is an Admin of Company "Testladen GmbH"
    And a Product "TST-001" exists

  Scenario: SP-AC040 — Admin läd Product-Bild hoch
    When the Admin uploads an image for product "TST-001" with:
      | file   | is_primary |
      | img.jpg | true       |
    Then the response status is 201
    And the image is stored in S3
    And the image URL is returned in the response
    And the image is marked as primary

  Scenario: SP-AC041 — Admin setzt mehrere Bilder (Galerie)
    Given a primary image exists for product "TST-001"
    When the Admin uploads a second image for product "TST-001" with is_primary=false
    Then the response status is 201
    And the product now has 2 images
    And the first image remains primary

  Scenario: SP-AC042 — Primäres Bild wird ausgetauscht
    Given product "TST-001" has a primary image
    When the Admin uploads a new image and sets it as primary
    Then the response status is 200
    And the new image is primary
    And the old primary image is demoted to non-primary

  Scenario: SP-AC043 — Bild-Upload ohne Auth → 401
    Given the user is unauthenticated
    When the user uploads an image for product "TST-001"
    Then the response status is 401

  Scenario: SP-AC044 — Falsches Dateiformat → Fehler
    When the Admin uploads a non-image file (e.g., .pdf) for product "TST-001"
    Then the response status is 400
    And the response contains error "Only image files (JPEG, PNG, WebP) are allowed"

  Scenario: SP-AC045 — Bild zu groß (> 10MB) → Fehler
    When the Admin uploads an image larger than 10MB for product "TST-001"
    Then the response status is 413
    And the response contains error "File size exceeds 10MB limit"

Sprint 2 Coverage Summary

Feature Happy Path Edge Cases Total
S2-F1 Product CRUD 4 4 8
S2-F2 Categories 4 2 6
S2-F3 Attributes 5 2 7
S2-F4 SKU-Varianten 4 3 7
S2-F5 Product Images 4 2 6
Sprint 2 Total 21 13 34

Erstellt: 2026-07-03 von PM (95ed75cd)
Issue: CAR-22 — Sprint-2 Stories + Gherkin AK (ergänzt CAR-14 Sprint-1 AK)
Roadmap: PM-ROADMAP-Phase1.md (CPTO approved)