mirror of
https://github.com/onyx-dot-app/onyx.git
synced 2026-03-16 21:22:41 +00:00
Compare commits
6 Commits
v3.0.0-clo
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6b7b3c68f | ||
|
|
f5073d331e | ||
|
|
64c9f6a0d5 | ||
|
|
f5a494f790 | ||
|
|
8598e9f25d | ||
|
|
3ef8aecc54 |
30
.github/workflows/deployment.yml
vendored
30
.github/workflows/deployment.yml
vendored
@@ -455,7 +455,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # ratchet:docker/metadata-action@v5
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # ratchet:docker/metadata-action@v6.0.0
|
||||
with:
|
||||
images: ${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }}
|
||||
flavor: |
|
||||
@@ -529,7 +529,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # ratchet:docker/metadata-action@v5
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # ratchet:docker/metadata-action@v6.0.0
|
||||
with:
|
||||
images: ${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }}
|
||||
flavor: |
|
||||
@@ -607,7 +607,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # ratchet:docker/metadata-action@v5
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # ratchet:docker/metadata-action@v6.0.0
|
||||
with:
|
||||
images: ${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }}
|
||||
flavor: |
|
||||
@@ -668,7 +668,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # ratchet:docker/metadata-action@v5
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # ratchet:docker/metadata-action@v6.0.0
|
||||
with:
|
||||
images: ${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }}
|
||||
flavor: |
|
||||
@@ -750,7 +750,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # ratchet:docker/metadata-action@v5
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # ratchet:docker/metadata-action@v6.0.0
|
||||
with:
|
||||
images: ${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }}
|
||||
flavor: |
|
||||
@@ -836,7 +836,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # ratchet:docker/metadata-action@v5
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # ratchet:docker/metadata-action@v6.0.0
|
||||
with:
|
||||
images: ${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }}
|
||||
flavor: |
|
||||
@@ -894,7 +894,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # ratchet:docker/metadata-action@v5
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # ratchet:docker/metadata-action@v6.0.0
|
||||
with:
|
||||
images: ${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }}
|
||||
flavor: |
|
||||
@@ -967,7 +967,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # ratchet:docker/metadata-action@v5
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # ratchet:docker/metadata-action@v6.0.0
|
||||
with:
|
||||
images: ${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }}
|
||||
flavor: |
|
||||
@@ -1044,7 +1044,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # ratchet:docker/metadata-action@v5
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # ratchet:docker/metadata-action@v6.0.0
|
||||
with:
|
||||
images: ${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }}
|
||||
flavor: |
|
||||
@@ -1105,7 +1105,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # ratchet:docker/metadata-action@v5
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # ratchet:docker/metadata-action@v6.0.0
|
||||
with:
|
||||
images: ${{ env.REGISTRY_IMAGE }}
|
||||
flavor: |
|
||||
@@ -1178,7 +1178,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # ratchet:docker/metadata-action@v5
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # ratchet:docker/metadata-action@v6.0.0
|
||||
with:
|
||||
images: ${{ env.REGISTRY_IMAGE }}
|
||||
flavor: |
|
||||
@@ -1256,7 +1256,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # ratchet:docker/metadata-action@v5
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # ratchet:docker/metadata-action@v6.0.0
|
||||
with:
|
||||
images: ${{ env.REGISTRY_IMAGE }}
|
||||
flavor: |
|
||||
@@ -1317,7 +1317,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # ratchet:docker/metadata-action@v5
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # ratchet:docker/metadata-action@v6.0.0
|
||||
with:
|
||||
images: ${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }}
|
||||
flavor: |
|
||||
@@ -1397,7 +1397,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # ratchet:docker/metadata-action@v5
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # ratchet:docker/metadata-action@v6.0.0
|
||||
with:
|
||||
images: ${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }}
|
||||
flavor: |
|
||||
@@ -1480,7 +1480,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # ratchet:docker/metadata-action@v5
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # ratchet:docker/metadata-action@v6.0.0
|
||||
with:
|
||||
images: ${{ needs.determine-builds.outputs.is-test-run == 'true' && env.RUNS_ON_ECR_CACHE || env.REGISTRY_IMAGE }}
|
||||
flavor: |
|
||||
|
||||
2
.github/workflows/pr-desktop-build.yml
vendored
2
.github/workflows/pr-desktop-build.yml
vendored
@@ -105,7 +105,7 @@ jobs:
|
||||
|
||||
- name: Upload build artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
|
||||
with:
|
||||
name: desktop-build-${{ matrix.platform }}-${{ github.run_id }}
|
||||
path: |
|
||||
|
||||
@@ -174,7 +174,7 @@ jobs:
|
||||
|
||||
- name: Upload Docker logs
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
|
||||
with:
|
||||
name: docker-logs-${{ matrix.test-dir }}
|
||||
path: docker-logs/
|
||||
|
||||
4
.github/workflows/pr-golang-tests.yml
vendored
4
.github/workflows/pr-golang-tests.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
outputs:
|
||||
modules: ${{ steps.set-modules.outputs.modules }}
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||
with:
|
||||
persist-credentials: false
|
||||
- id: set-modules
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
matrix:
|
||||
modules: ${{ fromJSON(needs.detect-modules.outputs.modules) }}
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # ratchet:actions/checkout@v6
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # ratchet:actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # zizmor: ignore[cache-poisoning]
|
||||
|
||||
6
.github/workflows/pr-integration-tests.yml
vendored
6
.github/workflows/pr-integration-tests.yml
vendored
@@ -466,7 +466,7 @@ jobs:
|
||||
|
||||
- name: Upload logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
|
||||
with:
|
||||
name: docker-all-logs-${{ matrix.edition }}-${{ matrix.test-dir.name }}
|
||||
path: ${{ github.workspace }}/docker-compose.log
|
||||
@@ -587,7 +587,7 @@ jobs:
|
||||
|
||||
- name: Upload logs (onyx-lite)
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
|
||||
with:
|
||||
name: docker-all-logs-onyx-lite
|
||||
path: ${{ github.workspace }}/docker-compose-onyx-lite.log
|
||||
@@ -725,7 +725,7 @@ jobs:
|
||||
|
||||
- name: Upload logs (multi-tenant)
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
|
||||
with:
|
||||
name: docker-all-logs-multitenant
|
||||
path: ${{ github.workspace }}/docker-compose-multitenant.log
|
||||
|
||||
2
.github/workflows/pr-jest-tests.yml
vendored
2
.github/workflows/pr-jest-tests.yml
vendored
@@ -44,7 +44,7 @@ jobs:
|
||||
|
||||
- name: Upload coverage reports
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
|
||||
with:
|
||||
name: jest-coverage-${{ github.run_id }}
|
||||
path: ./web/coverage
|
||||
|
||||
14
.github/workflows/pr-playwright-tests.yml
vendored
14
.github/workflows/pr-playwright-tests.yml
vendored
@@ -445,7 +445,7 @@ jobs:
|
||||
run: |
|
||||
npx playwright test --project ${PROJECT}
|
||||
|
||||
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
|
||||
if: always()
|
||||
with:
|
||||
# Includes test results and trace.zip files
|
||||
@@ -454,7 +454,7 @@ jobs:
|
||||
retention-days: 30
|
||||
|
||||
- name: Upload screenshots
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-screenshots-${{ matrix.project }}-${{ github.run_id }}
|
||||
@@ -534,7 +534,7 @@ jobs:
|
||||
"s3://${PLAYWRIGHT_S3_BUCKET}/reports/pr-${PR_NUMBER}/${RUN_ID}/${PROJECT}/"
|
||||
|
||||
- name: Upload visual diff summary
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
|
||||
if: always()
|
||||
with:
|
||||
name: screenshot-diff-summary-${{ matrix.project }}
|
||||
@@ -543,7 +543,7 @@ jobs:
|
||||
retention-days: 5
|
||||
|
||||
- name: Upload visual diff report artifact
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
|
||||
if: always()
|
||||
with:
|
||||
name: screenshot-diff-report-${{ matrix.project }}-${{ github.run_id }}
|
||||
@@ -590,7 +590,7 @@ jobs:
|
||||
|
||||
- name: Upload logs
|
||||
if: success() || failure()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
|
||||
with:
|
||||
name: docker-logs-${{ matrix.project }}-${{ github.run_id }}
|
||||
path: ${{ github.workspace }}/docker-compose.log
|
||||
@@ -674,7 +674,7 @@ jobs:
|
||||
working-directory: ./web
|
||||
run: npx playwright test --project lite
|
||||
|
||||
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
|
||||
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-test-results-lite-${{ github.run_id }}
|
||||
@@ -692,7 +692,7 @@ jobs:
|
||||
|
||||
- name: Upload logs
|
||||
if: success() || failure()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
|
||||
with:
|
||||
name: docker-logs-lite-${{ github.run_id }}
|
||||
path: ${{ github.workspace }}/docker-compose.log
|
||||
|
||||
2
.github/workflows/pr-python-model-tests.yml
vendored
2
.github/workflows/pr-python-model-tests.yml
vendored
@@ -122,7 +122,7 @@ jobs:
|
||||
|
||||
- name: Upload logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
|
||||
with:
|
||||
name: docker-all-logs
|
||||
path: ${{ github.workspace }}/docker-compose.log
|
||||
|
||||
@@ -319,7 +319,7 @@ jobs:
|
||||
|
||||
- name: Upload logs
|
||||
if: always()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
|
||||
with:
|
||||
name: docker-all-logs-nightly-${{ matrix.provider }}-llm-provider
|
||||
path: |
|
||||
|
||||
6
.github/workflows/sandbox-deployment.yml
vendored
6
.github/workflows/sandbox-deployment.yml
vendored
@@ -125,7 +125,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # ratchet:docker/metadata-action@v5
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # ratchet:docker/metadata-action@v6.0.0
|
||||
with:
|
||||
images: ${{ env.REGISTRY_IMAGE }}
|
||||
flavor: |
|
||||
@@ -195,7 +195,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # ratchet:docker/metadata-action@v5
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # ratchet:docker/metadata-action@v6.0.0
|
||||
with:
|
||||
images: ${{ env.REGISTRY_IMAGE }}
|
||||
flavor: |
|
||||
@@ -268,7 +268,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # ratchet:docker/metadata-action@v5
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # ratchet:docker/metadata-action@v6.0.0
|
||||
with:
|
||||
images: ${{ env.REGISTRY_IMAGE }}
|
||||
flavor: |
|
||||
|
||||
@@ -29,6 +29,8 @@ from onyx.configs.constants import OnyxCeleryPriority
|
||||
from onyx.configs.constants import OnyxCeleryQueues
|
||||
from onyx.configs.constants import OnyxCeleryTask
|
||||
from onyx.configs.constants import OnyxRedisLocks
|
||||
from onyx.connectors.factory import ConnectorMissingException
|
||||
from onyx.connectors.factory import identify_connector_class
|
||||
from onyx.connectors.factory import instantiate_connector
|
||||
from onyx.connectors.interfaces import HierarchyConnector
|
||||
from onyx.connectors.models import HierarchyNode as PydanticHierarchyNode
|
||||
@@ -55,6 +57,26 @@ logger = setup_logger()
|
||||
HIERARCHY_FETCH_INTERVAL_SECONDS = 24 * 60 * 60
|
||||
|
||||
|
||||
def _connector_supports_hierarchy_fetching(
|
||||
cc_pair: ConnectorCredentialPair,
|
||||
) -> bool:
|
||||
"""Return True only for connectors whose class implements HierarchyConnector."""
|
||||
try:
|
||||
connector_class = identify_connector_class(
|
||||
cc_pair.connector.source,
|
||||
)
|
||||
except ConnectorMissingException as e:
|
||||
task_logger.warning(
|
||||
"Skipping hierarchy fetching enqueue for source=%s input_type=%s: %s",
|
||||
cc_pair.connector.source,
|
||||
cc_pair.connector.input_type,
|
||||
str(e),
|
||||
)
|
||||
return False
|
||||
|
||||
return issubclass(connector_class, HierarchyConnector)
|
||||
|
||||
|
||||
def _is_hierarchy_fetching_due(cc_pair: ConnectorCredentialPair) -> bool:
|
||||
"""Returns boolean indicating if hierarchy fetching is due for this connector.
|
||||
|
||||
@@ -186,7 +208,10 @@ def check_for_hierarchy_fetching(self: Task, *, tenant_id: str) -> int | None:
|
||||
cc_pair_id=cc_pair_id,
|
||||
)
|
||||
|
||||
if not cc_pair or not _is_hierarchy_fetching_due(cc_pair):
|
||||
if not cc_pair or not _connector_supports_hierarchy_fetching(cc_pair):
|
||||
continue
|
||||
|
||||
if not _is_hierarchy_fetching_due(cc_pair):
|
||||
continue
|
||||
|
||||
task_id = _try_creating_hierarchy_fetching_task(
|
||||
|
||||
@@ -45,6 +45,21 @@ npx playwright test <TEST_NAME>
|
||||
Shared fixtures live in `backend/tests/conftest.py`. Test subdirectories can define
|
||||
their own `conftest.py` for directory-scoped fixtures.
|
||||
|
||||
## Running Tests Repeatedly (`pytest-repeat`)
|
||||
|
||||
Use `pytest-repeat` to catch flaky tests by running them multiple times:
|
||||
|
||||
```bash
|
||||
# Run a specific test 50 times
|
||||
pytest --count=50 backend/tests/unit/path/to/test.py::test_name
|
||||
|
||||
# Stop on first failure with -x
|
||||
pytest --count=50 -x backend/tests/unit/path/to/test.py::test_name
|
||||
|
||||
# Repeat an entire test file
|
||||
pytest --count=10 backend/tests/unit/path/to/test_file.py
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### Use `enable_ee` fixture instead of inlining
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
from unittest.mock import MagicMock
|
||||
from unittest.mock import patch
|
||||
|
||||
from onyx.background.celery.tasks.hierarchyfetching.tasks import (
|
||||
_connector_supports_hierarchy_fetching,
|
||||
)
|
||||
from onyx.background.celery.tasks.hierarchyfetching.tasks import (
|
||||
check_for_hierarchy_fetching,
|
||||
)
|
||||
from onyx.connectors.factory import ConnectorMissingException
|
||||
from onyx.connectors.interfaces import BaseConnector
|
||||
from onyx.connectors.interfaces import HierarchyConnector
|
||||
from onyx.connectors.interfaces import HierarchyOutput
|
||||
from onyx.connectors.interfaces import SecondsSinceUnixEpoch
|
||||
|
||||
TASKS_MODULE = "onyx.background.celery.tasks.hierarchyfetching.tasks"
|
||||
|
||||
|
||||
class _NonHierarchyConnector(BaseConnector):
|
||||
def load_credentials(self, credentials: dict) -> dict | None: # noqa: ARG002
|
||||
return None
|
||||
|
||||
|
||||
class _HierarchyCapableConnector(HierarchyConnector):
|
||||
def load_credentials(self, credentials: dict) -> dict | None: # noqa: ARG002
|
||||
return None
|
||||
|
||||
def load_hierarchy(
|
||||
self,
|
||||
start: SecondsSinceUnixEpoch, # noqa: ARG002
|
||||
end: SecondsSinceUnixEpoch, # noqa: ARG002
|
||||
) -> HierarchyOutput:
|
||||
return
|
||||
yield
|
||||
|
||||
|
||||
def _build_cc_pair_mock() -> MagicMock:
|
||||
cc_pair = MagicMock()
|
||||
cc_pair.connector.source = "mock-source"
|
||||
cc_pair.connector.input_type = "mock-input-type"
|
||||
return cc_pair
|
||||
|
||||
|
||||
def _build_redis_mock_with_lock() -> tuple[MagicMock, MagicMock]:
|
||||
redis_client = MagicMock()
|
||||
lock = MagicMock()
|
||||
lock.acquire.return_value = True
|
||||
lock.owned.return_value = True
|
||||
redis_client.lock.return_value = lock
|
||||
return redis_client, lock
|
||||
|
||||
|
||||
@patch(f"{TASKS_MODULE}.identify_connector_class")
|
||||
def test_connector_supports_hierarchy_fetching_false_for_non_hierarchy_connector(
|
||||
mock_identify_connector_class: MagicMock,
|
||||
) -> None:
|
||||
mock_identify_connector_class.return_value = _NonHierarchyConnector
|
||||
|
||||
assert _connector_supports_hierarchy_fetching(_build_cc_pair_mock()) is False
|
||||
mock_identify_connector_class.assert_called_once_with("mock-source")
|
||||
|
||||
|
||||
@patch(f"{TASKS_MODULE}.task_logger.warning")
|
||||
@patch(f"{TASKS_MODULE}.identify_connector_class")
|
||||
def test_connector_supports_hierarchy_fetching_false_when_class_missing(
|
||||
mock_identify_connector_class: MagicMock,
|
||||
mock_warning: MagicMock,
|
||||
) -> None:
|
||||
mock_identify_connector_class.side_effect = ConnectorMissingException("missing")
|
||||
|
||||
assert _connector_supports_hierarchy_fetching(_build_cc_pair_mock()) is False
|
||||
mock_warning.assert_called_once()
|
||||
|
||||
|
||||
@patch(f"{TASKS_MODULE}.identify_connector_class")
|
||||
def test_connector_supports_hierarchy_fetching_true_for_supported_connector(
|
||||
mock_identify_connector_class: MagicMock,
|
||||
) -> None:
|
||||
mock_identify_connector_class.return_value = _HierarchyCapableConnector
|
||||
|
||||
assert _connector_supports_hierarchy_fetching(_build_cc_pair_mock()) is True
|
||||
mock_identify_connector_class.assert_called_once_with("mock-source")
|
||||
|
||||
|
||||
@patch(f"{TASKS_MODULE}._try_creating_hierarchy_fetching_task")
|
||||
@patch(f"{TASKS_MODULE}._is_hierarchy_fetching_due")
|
||||
@patch(f"{TASKS_MODULE}.get_connector_credential_pair_from_id")
|
||||
@patch(f"{TASKS_MODULE}.fetch_indexable_standard_connector_credential_pair_ids")
|
||||
@patch(f"{TASKS_MODULE}.get_session_with_current_tenant")
|
||||
@patch(f"{TASKS_MODULE}.get_redis_client")
|
||||
@patch(f"{TASKS_MODULE}._connector_supports_hierarchy_fetching")
|
||||
def test_check_for_hierarchy_fetching_skips_unsupported_connectors(
|
||||
mock_supports_hierarchy_fetching: MagicMock,
|
||||
mock_get_redis_client: MagicMock,
|
||||
mock_get_session: MagicMock,
|
||||
mock_fetch_cc_pair_ids: MagicMock,
|
||||
mock_get_cc_pair: MagicMock,
|
||||
mock_is_due: MagicMock,
|
||||
mock_try_create_task: MagicMock,
|
||||
) -> None:
|
||||
redis_client, lock = _build_redis_mock_with_lock()
|
||||
mock_get_redis_client.return_value = redis_client
|
||||
mock_get_session.return_value.__enter__.return_value = MagicMock()
|
||||
mock_fetch_cc_pair_ids.return_value = [123]
|
||||
mock_get_cc_pair.return_value = _build_cc_pair_mock()
|
||||
mock_supports_hierarchy_fetching.return_value = False
|
||||
mock_is_due.return_value = True
|
||||
|
||||
task_app = MagicMock()
|
||||
with patch.object(check_for_hierarchy_fetching, "app", task_app):
|
||||
result = check_for_hierarchy_fetching.run(tenant_id="test-tenant")
|
||||
|
||||
assert result == 0
|
||||
mock_is_due.assert_not_called()
|
||||
mock_try_create_task.assert_not_called()
|
||||
lock.release.assert_called_once()
|
||||
|
||||
|
||||
@patch(f"{TASKS_MODULE}._try_creating_hierarchy_fetching_task")
|
||||
@patch(f"{TASKS_MODULE}._is_hierarchy_fetching_due")
|
||||
@patch(f"{TASKS_MODULE}.get_connector_credential_pair_from_id")
|
||||
@patch(f"{TASKS_MODULE}.fetch_indexable_standard_connector_credential_pair_ids")
|
||||
@patch(f"{TASKS_MODULE}.get_session_with_current_tenant")
|
||||
@patch(f"{TASKS_MODULE}.get_redis_client")
|
||||
@patch(f"{TASKS_MODULE}._connector_supports_hierarchy_fetching")
|
||||
def test_check_for_hierarchy_fetching_creates_task_for_supported_due_connector(
|
||||
mock_supports_hierarchy_fetching: MagicMock,
|
||||
mock_get_redis_client: MagicMock,
|
||||
mock_get_session: MagicMock,
|
||||
mock_fetch_cc_pair_ids: MagicMock,
|
||||
mock_get_cc_pair: MagicMock,
|
||||
mock_is_due: MagicMock,
|
||||
mock_try_create_task: MagicMock,
|
||||
) -> None:
|
||||
redis_client, lock = _build_redis_mock_with_lock()
|
||||
cc_pair = _build_cc_pair_mock()
|
||||
db_session = MagicMock()
|
||||
mock_get_redis_client.return_value = redis_client
|
||||
mock_get_session.return_value.__enter__.return_value = db_session
|
||||
mock_fetch_cc_pair_ids.return_value = [123]
|
||||
mock_get_cc_pair.return_value = cc_pair
|
||||
mock_supports_hierarchy_fetching.return_value = True
|
||||
mock_is_due.return_value = True
|
||||
mock_try_create_task.return_value = "task-id"
|
||||
|
||||
task_app = MagicMock()
|
||||
with patch.object(check_for_hierarchy_fetching, "app", task_app):
|
||||
result = check_for_hierarchy_fetching.run(tenant_id="test-tenant")
|
||||
|
||||
assert result == 1
|
||||
mock_is_due.assert_called_once_with(cc_pair)
|
||||
mock_try_create_task.assert_called_once_with(
|
||||
celery_app=task_app,
|
||||
cc_pair=cc_pair,
|
||||
db_session=db_session,
|
||||
r=redis_client,
|
||||
tenant_id="test-tenant",
|
||||
)
|
||||
lock.release.assert_called_once()
|
||||
|
||||
|
||||
@patch(f"{TASKS_MODULE}._try_creating_hierarchy_fetching_task")
|
||||
@patch(f"{TASKS_MODULE}._is_hierarchy_fetching_due")
|
||||
@patch(f"{TASKS_MODULE}.get_connector_credential_pair_from_id")
|
||||
@patch(f"{TASKS_MODULE}.fetch_indexable_standard_connector_credential_pair_ids")
|
||||
@patch(f"{TASKS_MODULE}.get_session_with_current_tenant")
|
||||
@patch(f"{TASKS_MODULE}.get_redis_client")
|
||||
@patch(f"{TASKS_MODULE}._connector_supports_hierarchy_fetching")
|
||||
def test_check_for_hierarchy_fetching_skips_supported_connector_when_not_due(
|
||||
mock_supports_hierarchy_fetching: MagicMock,
|
||||
mock_get_redis_client: MagicMock,
|
||||
mock_get_session: MagicMock,
|
||||
mock_fetch_cc_pair_ids: MagicMock,
|
||||
mock_get_cc_pair: MagicMock,
|
||||
mock_is_due: MagicMock,
|
||||
mock_try_create_task: MagicMock,
|
||||
) -> None:
|
||||
redis_client, lock = _build_redis_mock_with_lock()
|
||||
cc_pair = _build_cc_pair_mock()
|
||||
mock_get_redis_client.return_value = redis_client
|
||||
mock_get_session.return_value.__enter__.return_value = MagicMock()
|
||||
mock_fetch_cc_pair_ids.return_value = [123]
|
||||
mock_get_cc_pair.return_value = cc_pair
|
||||
mock_supports_hierarchy_fetching.return_value = True
|
||||
mock_is_due.return_value = False
|
||||
|
||||
task_app = MagicMock()
|
||||
with patch.object(check_for_hierarchy_fetching, "app", task_app):
|
||||
result = check_for_hierarchy_fetching.run(tenant_id="test-tenant")
|
||||
|
||||
assert result == 0
|
||||
mock_is_due.assert_called_once_with(cc_pair)
|
||||
mock_try_create_task.assert_not_called()
|
||||
lock.release.assert_called_once()
|
||||
@@ -32,15 +32,17 @@ def test_run_with_timeout_raises_on_timeout(slow: float, timeout: float) -> None
|
||||
"""Test that a function that exceeds timeout raises TimeoutError"""
|
||||
|
||||
def slow_function() -> None:
|
||||
time.sleep(slow) # Sleep for 2 seconds
|
||||
time.sleep(slow)
|
||||
|
||||
start = time.monotonic()
|
||||
with pytest.raises(TimeoutError) as exc_info:
|
||||
start = time.time()
|
||||
run_with_timeout(timeout, slow_function) # Set timeout to 0.1 seconds
|
||||
end = time.time()
|
||||
assert end - start >= timeout
|
||||
assert end - start < (slow + timeout) / 2
|
||||
run_with_timeout(timeout, slow_function)
|
||||
elapsed = time.monotonic() - start
|
||||
|
||||
assert f"timed out after {timeout} seconds" in str(exc_info.value)
|
||||
assert elapsed >= timeout
|
||||
# Should return around the timeout duration, not the full sleep duration
|
||||
assert elapsed == pytest.approx(timeout, abs=0.8)
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("ignore::pytest.PytestUnhandledThreadExceptionWarning")
|
||||
|
||||
@@ -176,7 +176,10 @@ function AttachmentItemLayout({
|
||||
<Section flexDirection="row" gap={0.25} padding={0.25}>
|
||||
<div className={cn("h-[2.25rem] aspect-square rounded-08")}>
|
||||
<Section>
|
||||
<div className="attachment-button__icon-wrapper">
|
||||
<div
|
||||
className="attachment-button__icon-wrapper"
|
||||
data-testid="attachment-item-icon-wrapper"
|
||||
>
|
||||
<Icon className="attachment-button__icon" />
|
||||
</div>
|
||||
</Section>
|
||||
@@ -187,7 +190,7 @@ function AttachmentItemLayout({
|
||||
alignItems="center"
|
||||
gap={1.5}
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div data-testid="attachment-item-title" className="flex-1 min-w-0">
|
||||
<Content
|
||||
title={title}
|
||||
description={description}
|
||||
|
||||
181
web/tests/e2e/chat/project_files_visual_regression.spec.ts
Normal file
181
web/tests/e2e/chat/project_files_visual_regression.spec.ts
Normal file
@@ -0,0 +1,181 @@
|
||||
import { expect, test, type Locator, type Page } from "@playwright/test";
|
||||
import { loginAsWorkerUser } from "@tests/e2e/utils/auth";
|
||||
import { OnyxApiClient } from "@tests/e2e/utils/onyxApiClient";
|
||||
import { expectElementScreenshot } from "@tests/e2e/utils/visualRegression";
|
||||
|
||||
const TEST_PREFIX = "E2E-PROJECT-FILES-VISUAL";
|
||||
const ATTACHMENT_ITEM_TITLE_TEST_ID = "attachment-item-title";
|
||||
const ATTACHMENT_ITEM_ICON_WRAPPER_TEST_ID = "attachment-item-icon-wrapper";
|
||||
const LONG_FILE_NAME =
|
||||
"CSE_202_Final_Project_Solution_Regression_Check_Long_Name.txt";
|
||||
const FILE_CONTENT = "Visual regression test content for long filename cards.";
|
||||
|
||||
let projectId: number | null = null;
|
||||
|
||||
type Geometry = {
|
||||
elementLeft: number;
|
||||
elementRight: number;
|
||||
elementTop: number;
|
||||
elementBottom: number;
|
||||
cardLeft: number;
|
||||
cardRight: number;
|
||||
cardTop: number;
|
||||
cardBottom: number;
|
||||
};
|
||||
|
||||
function getFilesSection(page: Page): Locator {
|
||||
return page
|
||||
.locator("div")
|
||||
.filter({ has: page.getByRole("button", { name: "Add Files" }) })
|
||||
.filter({ hasText: "Chats in this project can access these files." })
|
||||
.first();
|
||||
}
|
||||
|
||||
async function uploadFileToProject(
|
||||
page: Page,
|
||||
targetProjectId: number,
|
||||
fileName: string,
|
||||
content: string
|
||||
): Promise<void> {
|
||||
const response = await page.request.post("/api/user/projects/file/upload", {
|
||||
multipart: {
|
||||
project_id: String(targetProjectId),
|
||||
files: {
|
||||
name: fileName,
|
||||
mimeType: "text/plain",
|
||||
buffer: Buffer.from(content, "utf-8"),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(response.ok()).toBeTruthy();
|
||||
}
|
||||
|
||||
async function getElementGeometryInCard(
|
||||
element: Locator
|
||||
): Promise<Geometry | null> {
|
||||
return element.evaluate((targetEl) => {
|
||||
let cardEl: HTMLElement | null = targetEl.parentElement;
|
||||
|
||||
while (cardEl) {
|
||||
const style = window.getComputedStyle(cardEl);
|
||||
const hasBorder =
|
||||
parseFloat(style.borderTopWidth) > 0 ||
|
||||
parseFloat(style.borderLeftWidth) > 0;
|
||||
const hasRadius = parseFloat(style.borderTopLeftRadius) > 0;
|
||||
|
||||
if (hasBorder && hasRadius) {
|
||||
break;
|
||||
}
|
||||
cardEl = cardEl.parentElement;
|
||||
}
|
||||
|
||||
if (!cardEl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const elementRect = targetEl.getBoundingClientRect();
|
||||
const cardRect = cardEl.getBoundingClientRect();
|
||||
|
||||
return {
|
||||
elementLeft: elementRect.left,
|
||||
elementRight: elementRect.right,
|
||||
elementTop: elementRect.top,
|
||||
elementBottom: elementRect.bottom,
|
||||
cardLeft: cardRect.left,
|
||||
cardRight: cardRect.right,
|
||||
cardTop: cardRect.top,
|
||||
cardBottom: cardRect.bottom,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function expectGeometryWithinCard(geometry: Geometry | null): void {
|
||||
expect(geometry).not.toBeNull();
|
||||
expect(geometry!.elementLeft).toBeGreaterThanOrEqual(geometry!.cardLeft - 1);
|
||||
expect(geometry!.elementRight).toBeLessThanOrEqual(geometry!.cardRight + 1);
|
||||
expect(geometry!.elementTop).toBeGreaterThanOrEqual(geometry!.cardTop - 1);
|
||||
expect(geometry!.elementBottom).toBeLessThanOrEqual(geometry!.cardBottom + 1);
|
||||
}
|
||||
|
||||
test.describe("Project Files visual regression", () => {
|
||||
test.beforeAll(async ({ browser }, workerInfo) => {
|
||||
const context = await browser.newContext();
|
||||
const page = await context.newPage();
|
||||
|
||||
await loginAsWorkerUser(page, workerInfo.workerIndex);
|
||||
const client = new OnyxApiClient(page.request);
|
||||
|
||||
projectId = await client.createProject(`${TEST_PREFIX}-${Date.now()}`);
|
||||
await uploadFileToProject(page, projectId, LONG_FILE_NAME, FILE_CONTENT);
|
||||
|
||||
await context.close();
|
||||
});
|
||||
|
||||
test.afterAll(async ({ browser }, workerInfo) => {
|
||||
if (!projectId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const context = await browser.newContext();
|
||||
const page = await context.newPage();
|
||||
|
||||
await loginAsWorkerUser(page, workerInfo.workerIndex);
|
||||
const client = new OnyxApiClient(page.request);
|
||||
await client.deleteProject(projectId);
|
||||
|
||||
await context.close();
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ page }, workerInfo) => {
|
||||
if (projectId === null) {
|
||||
throw new Error(
|
||||
"Project setup failed in beforeAll; cannot run visual regression test"
|
||||
);
|
||||
}
|
||||
|
||||
await page.context().clearCookies();
|
||||
await loginAsWorkerUser(page, workerInfo.workerIndex);
|
||||
await page.goto(`/app?projectId=${projectId}`);
|
||||
await page.waitForLoadState("networkidle");
|
||||
await expect(
|
||||
page.getByText("Chats in this project can access these files.")
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("long underscore filename stays visually contained in file card", async ({
|
||||
page,
|
||||
}) => {
|
||||
const filesSection = getFilesSection(page);
|
||||
await expect(filesSection).toBeVisible();
|
||||
|
||||
const fileTitle = filesSection
|
||||
.locator(`[data-testid="${ATTACHMENT_ITEM_TITLE_TEST_ID}"]`)
|
||||
.filter({ hasText: LONG_FILE_NAME })
|
||||
.first();
|
||||
await expect(fileTitle).toBeVisible();
|
||||
|
||||
// Wait for deterministic post-processing state before geometry checks/screenshot.
|
||||
await expect(fileTitle).not.toContainText("Processing...", {
|
||||
timeout: 30_000,
|
||||
});
|
||||
await expect(fileTitle).not.toContainText("Uploading...", {
|
||||
timeout: 30_000,
|
||||
});
|
||||
await expect(fileTitle).toContainText("TXT", { timeout: 30_000 });
|
||||
|
||||
const iconWrapper = filesSection
|
||||
.locator(`[data-testid="${ATTACHMENT_ITEM_ICON_WRAPPER_TEST_ID}"]`)
|
||||
.first();
|
||||
await expect(iconWrapper).toBeVisible();
|
||||
|
||||
await expectElementScreenshot(filesSection, {
|
||||
name: "project-files-long-underscore-filename",
|
||||
});
|
||||
|
||||
const iconGeometry = await getElementGeometryInCard(iconWrapper);
|
||||
const titleGeometry = await getElementGeometryInCard(fileTitle);
|
||||
expectGeometryWithinCard(iconGeometry);
|
||||
expectGeometryWithinCard(titleGeometry);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user