Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions _refactored/restapi/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,34 @@
}
],
}

# Reference line item used by the Katalon-migrated order lifecycle tests —
# matches a product seeded into the dataset (orders.json).
ORDER_LINE_ITEM_TEMPLATE = {
"currency": "USD",
"price": 995.99,
"quantity": 1,
"productId": "product-acme-laptop-lenovo-ideapad-5i",
"sku": "product-acme-laptop-lenovo-ideapad-5i",
"productType": "Physical",
"catalogId": "catalog-acme",
"categoryId": "category-acme-laptops",
"name": "Lenovo Ideapad 5i",
"isGift": False,
"isCancelled": False,
"objectType": "VirtoCommerce.OrdersModule.Core.Model.LineItem",
}

ORDER_TEMPLATE = {
"isPrototype": False,
"objectType": "VirtoCommerce.OrdersModule.Core.Model.CustomerOrder",
"addresses": [],
"inPayments": [],
"shipments": [],
"discounts": [],
"status": "New",
"currency": "USD",
"childrenOperations": [],
"isCancelled": False,
"dynamicProperties": [],
}
2 changes: 2 additions & 0 deletions _refactored/restapi/operations/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from restapi.operations.member_operations import MemberOperations
from restapi.operations.notifications_operations import NotificationsOperations
from restapi.operations.oauth_operations import OAuthOperations
from restapi.operations.order_operations import OrderOperations
from restapi.operations.organization_operations import OrganizationOperations
from restapi.operations.price_operations import PriceOperations
from restapi.operations.pricelist_assignment_operations import PricelistAssignmentOperations
Expand All @@ -39,6 +40,7 @@
"MemberOperations",
"NotificationsOperations",
"OAuthOperations",
"OrderOperations",
"OrganizationOperations",
"PriceOperations",
"PricelistAssignmentOperations",
Expand Down
81 changes: 81 additions & 0 deletions _refactored/restapi/operations/order_operations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"""REST API operations for VirtoCommerce orders.

Endpoints verified from Katalon Object Repository
(Object Repository/API/backWebServices/VirtoCommerce.Order/*.rs):
POST /api/order/customerOrders — create
GET /api/order/customerOrders/{id} — get by id
GET /api/order/customerOrders/number/{number} — get by number
PUT /api/order/customerOrders — update (204)
PUT /api/order/customerOrders/recalculate — recalculate
DELETE /api/order/customerOrders?ids= — delete
POST /api/order/customerOrders/search — search
POST /api/order/customerOrders/searchChanges — search changes
GET /api/order/customerOrders/{id}/payments/new — generate new payment
GET /api/order/customerOrders/{id}/shipments/new — generate new shipment
GET /api/order/customerOrders/{id}/changes — changes by order id
GET /api/order/customerOrders/indexed/searchEnabled — indexed search flag
GET /api/order/dashboardStatistics?start=&end= — dashboard stats
POST /api/order/customerOrders/{id}/processPayment/{paymentId} — process payment
"""

from typing import Any

from restapi.operations.base import RestBaseOperations


class OrderOperations(RestBaseOperations):
PATH = "/api/order/customerOrders"

def create(self, payload: dict) -> dict:
return self._client.post(self._url(self.PATH), json=payload)

def get_by_id(self, order_id: str) -> dict:
return self._client.get(self._url(f"{self.PATH}/{order_id}"))

def get_by_number(self, order_number: str) -> dict:
return self._client.get(self._url(f"{self.PATH}/number/{order_number}"))

def update(self, order: dict) -> None:
self._client.put(self._url(self.PATH), json=order)

def recalculate(self, order: dict) -> dict:
return self._client.put(self._url(f"{self.PATH}/recalculate"), json=order)

def delete(self, *order_ids: str) -> None:
self._client.delete(self._url(self.PATH), params={"ids": list(order_ids)})

def search(self, *, keyword: str | None = None, skip: int = 0, take: int = 20, **extra: Any) -> dict:
payload: dict[str, Any] = {"skip": skip, "take": take, **extra}
if keyword is not None:
payload["keyword"] = keyword
return self._client.post(self._url(f"{self.PATH}/search"), json=payload)

def search_changes(self, *, order_id: str, skip: int = 0, take: int = 10) -> dict:
return self._client.post(
self._url(f"{self.PATH}/searchChanges"),
json={"orderId": order_id, "skip": skip, "take": take},
)

def get_new_payment(self, order_id: str) -> dict:
return self._client.get(self._url(f"{self.PATH}/{order_id}/payments/new"))

def get_new_shipment(self, order_id: str) -> dict:
return self._client.get(self._url(f"{self.PATH}/{order_id}/shipments/new"))

def get_changes(self, order_id: str) -> dict:
return self._client.get(self._url(f"{self.PATH}/{order_id}/changes"))

def indexed_search_enabled(self) -> dict:
return self._client.get(self._url(f"{self.PATH}/indexed/searchEnabled"))

def dashboard_statistics(self, *, start: str, end: str) -> dict:
return self._client.get(
self._url("/api/order/dashboardStatistics"),
params={"start": start, "end": end},
)

def process_payment(self, *, order_id: str, payment_id: str, payload: dict | None = None) -> dict | None:
return self._client.post(
self._url(f"{self.PATH}/{order_id}/processPayment/{payment_id}"),
json=payload or {},
)
71 changes: 71 additions & 0 deletions _refactored/tests/restapi/orders/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"""Orders module fixtures — operations + factory fixtures."""

import copy
import uuid
from typing import Any, Callable, Generator

import pytest

from core.clients.rest import RestClient
from core.global_settings import GlobalSettings
from restapi.constants import ORDER_LINE_ITEM_TEMPLATE, ORDER_TEMPLATE
from restapi.operations import OrderOperations


@pytest.fixture
def order_ops(rest_client: RestClient, backend_base_url: str) -> OrderOperations:
return OrderOperations(rest_client, backend_base_url)


@pytest.fixture(scope="session")
def seed_order_customer(dataset: dict) -> dict:
"""First seeded user — used as customer on factory-created orders."""
users = dataset.get("users") or []
if not users:
pytest.skip("No seeded users in dataset")
return users[0]


@pytest.fixture
def make_order(
order_ops: OrderOperations,
global_settings: GlobalSettings,
seed_order_customer: dict,
) -> Generator[Callable[..., dict], None, None]:
"""Factory: create a customer order; deletes everything created at teardown.

Defaults produce an empty-items order suitable for basic CRUD/search tests.
Pass `with_item=True` for an order containing the seeded reference line item
(required by payments/new, shipments/new, recalculate flows).
"""
created_ids: list[str] = []

def _make(*, with_item: bool = False, **overrides: Any) -> dict:
items: list[dict] = []
if with_item:
line_item = {**copy.deepcopy(ORDER_LINE_ITEM_TEMPLATE), "id": str(uuid.uuid4())}
items = [line_item]

payload: dict[str, Any] = {
**copy.deepcopy(ORDER_TEMPLATE),
"id": str(uuid.uuid4()),
"number": f"QA-{uuid.uuid4().hex[:8].upper()}",
"storeId": global_settings.store_id,
"customerId": seed_order_customer["id"],
"customerName": seed_order_customer.get("userName", "QA User"),
"createdBy": seed_order_customer.get("userName", "admin"),
"items": items,
}
payload.update(overrides)

order = order_ops.create(payload)
created_ids.append(order["id"])
return order

yield _make

for oid in reversed(created_ids):
try:
order_ops.delete(oid)
except Exception:
pass
Loading
Loading