diff --git a/.gitattributes b/.gitattributes
deleted file mode 100644
index 040321c04..000000000
--- a/.gitattributes
+++ /dev/null
@@ -1,2 +0,0 @@
-tableauserverclient/_version.py export-subst
-tableauserverclient/bin/_version.py export-subst
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
deleted file mode 100644
index a199226df..000000000
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ /dev/null
@@ -1,25 +0,0 @@
----
-name: Bug report
-about: Create a bug report or request for help
-title: ''
-labels: ''
-assignees: ''
-
----
-
-**Describe the bug**
-A clear and concise description of what the bug is.
-
-**Versions**
-Details of your environment, including:
- - Tableau Server version (or note if using Tableau Online)
- - Python version
- - TSC library version
-
-**To Reproduce**
-Steps to reproduce the behavior. Please include a code snippet where possible.
-
-**Results**
-What are the results or error messages received?
-
-**NOTE:** Be careful not to post user names, passwords, auth tokens or any other private or sensitive information.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
deleted file mode 100644
index b7a7a926d..000000000
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ /dev/null
@@ -1,28 +0,0 @@
----
-name: Feature Request
-title: "[REQUEST TYPE] [FEATURE TITLE]"
-about: Suggest a feature that could be added to the client
-labels: enhancement, needs investigation
----
-
-## Summary
-A one line description of the request. Skip this if the title is already a good summary.
-
-
-## Request Type
-If you know, say which of these types your request is in the title, and follow the suggestions for that type when writing your description.
-
-****Type 1: support a REST API:****
-If it is functionality that already exists in the [REST API](https://help.tableau.com/current/api/rest_api/en-us/REST/rest_api_ref.htm), example API calls are the clearest way to explain your request.
-
-****Type 2: add a REST API and support it in tsc.****
-If it is functionality that can be achieved somehow on Tableau Server but not through the REST API, describe the current way to do it. (e.g: functionality that is available in the Web UI, or by using the Hyper API). For UI, screenshots can be helpful.
-
-****Type 3: new functionality****
-Requests for totally new functionality will generally be passed to the relevant dev team, but we probably can't give any useful estimate of how or when it might be implemented. If it is a feature that is 'about' the API or programmable access, here might be the best place to suggest it, but generally feature requests will be more visible in the [Tableau Community Ideas](https://community.tableau.com/s/ideas) forum and should go there instead.
-
-
-## Description
-A clear and concise description of what the feature request is. If you think that the value of this feature might not be obvious, include information like how often it is needed, amount of work saved, etc. If your feature request is related to a file or server in a specific state, describe the starting state when the feature can be used, and the end state after using it. If it involves modifying files, an example file may be helpful.
-
-
diff --git a/.github/workflows/code-coverage.yml b/.github/workflows/code-coverage.yml
deleted file mode 100644
index 70bc845e9..000000000
--- a/.github/workflows/code-coverage.yml
+++ /dev/null
@@ -1,39 +0,0 @@
-name: Check Test Coverage
-
-on:
- pull_request:
- branches:
- - development
-
-jobs:
- build:
- strategy:
- fail-fast: false
- matrix:
- os: [ubuntu-latest]
- python-version: ['3.10']
-
- runs-on: ${{ matrix.os }}
-
- steps:
- - uses: actions/checkout@v4
-
- - name: Set up Python ${{ matrix.python-version }} on ${{ matrix.os }}
- uses: actions/setup-python@v5
- with:
- python-version: ${{ matrix.python-version }}
-
- - name: Install dependencies
- run: |
- python -m pip install --upgrade pip
- pip install -e .[test]
-
- # https://github.com/marketplace/actions/pytest-coverage-comment
- - name: Generate coverage report
- run: pytest --junitxml=pytest.xml --cov=tableauserverclient test/ | tee pytest-coverage.txt
-
- - name: Comment on pull request with coverage
- continue-on-error: true
- uses: MishaKav/pytest-coverage-comment@main
- with:
- pytest-coverage-path: ./pytest-coverage.txt
diff --git a/.github/workflows/meta-checks.yml b/.github/workflows/meta-checks.yml
deleted file mode 100644
index 0e2b425ee..000000000
--- a/.github/workflows/meta-checks.yml
+++ /dev/null
@@ -1,49 +0,0 @@
-name: types and style checks
-
-on: [push, pull_request]
-
-jobs:
- build:
- strategy:
- fail-fast: false
- matrix:
- os: [ubuntu-latest, macos-latest, windows-latest]
- python-version: ['3.10']
-
- runs-on: ${{ matrix.os }}
-
- steps:
- - name: Get pip cache dir
- id: pip-cache
- shell: bash
- run: |
- echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT
-
- - name: cache
- uses: actions/cache@v4
- with:
- path: ${{ steps.pip-cache.outputs.dir }}
- key: ${{ runner.os }}-${{ matrix.python-version }}-pip-${{ hashFiles('pyproject.toml') }}
- restore-keys: |
- ${{ runner.os }}-${{ matrix.python-version }}-pip-
-
- - uses: actions/checkout@v4
-
- - name: Set up Python ${{ matrix.python-version }} on ${{ matrix.os }}
- uses: actions/setup-python@v5
- with:
- python-version: ${{ matrix.python-version }}
-
- - name: Install dependencies
- run: |
- python -m pip install --upgrade pip
- pip install -e .[test]
-
- - name: Format with black
- run: |
- black --check --line-length 120 tableauserverclient samples test
-
- - name: Run Mypy tests
- if: always()
- run: |
- mypy --show-error-codes --disable-error-code misc --disable-error-code import --implicit-optional tableauserverclient test
diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml
deleted file mode 100644
index fe3728b9d..000000000
--- a/.github/workflows/publish-pypi.yml
+++ /dev/null
@@ -1,86 +0,0 @@
-name: Publish to PyPi
-
-# Builds the package, checks wheel structure, publishes to TestPyPI, runs smoke
-# tests against TestPyPI, then publishes to real PyPI only if all prior steps pass.
-on:
- release:
- types: [published]
- workflow_dispatch:
- push:
- tags:
- - 'v*.*.*'
-
-jobs:
- build:
- name: Build and check wheel
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
- with:
- fetch-depth: 0
- - uses: actions/setup-python@v5
- with:
- python-version: '3.13'
- - name: Build dist files
- run: |
- python -m pip install --upgrade pip
- python -m pip install -e .[test] build
- python -m build
- git describe --tag --dirty --always
- - name: Check wheel structure
- run: python -m pytest test/test_packaging.py -m packaging -v
- - uses: actions/upload-artifact@v4
- with:
- name: dist
- path: dist/
-
- publish-test-pypi:
- name: Publish to Test PyPI
- needs: build
- runs-on: ubuntu-latest
- steps:
- - uses: actions/download-artifact@v4
- with:
- name: dist
- path: dist/
- - name: Publish to Test PyPI
- uses: pypa/gh-action-pypi-publish@release/v1 # license BSD-2
- with:
- password: ${{ secrets.TEST_PYPI_API_TOKEN }}
- repository_url: https://test.pypi.org/legacy/
- skip_existing: true
-
- smoke-test-pypi:
- name: Smoke test from Test PyPI
- needs: publish-test-pypi
- strategy:
- fail-fast: false
- matrix:
- os: [ubuntu-latest, macos-latest, windows-latest]
- python-version: ['3.x']
- runs-on: ${{ matrix.os }}
- steps:
- - uses: actions/setup-python@v5
- with:
- python-version: ${{ matrix.python-version }}
- - name: Install from Test PyPI
- run: |
- pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ tableauserverclient
- - name: Smoke test
- run: |
- python -c "import tableauserverclient as TSC; server = TSC.Server('http://example.com', use_server_version=False)"
-
- publish-pypi:
- name: Publish to PyPI
- needs: smoke-test-pypi
- if: ${{ github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v') }}
- runs-on: ubuntu-latest
- steps:
- - uses: actions/download-artifact@v4
- with:
- name: dist
- path: dist/
- - name: Publish to PyPI
- uses: pypa/gh-action-pypi-publish@release/v1 # license BSD-2
- with:
- password: ${{ secrets.PYPI_API_TOKEN }}
diff --git a/.github/workflows/pypi-smoke-tests.yml b/.github/workflows/pypi-smoke-tests.yml
deleted file mode 100644
index a125b4bc5..000000000
--- a/.github/workflows/pypi-smoke-tests.yml
+++ /dev/null
@@ -1,38 +0,0 @@
-# This workflow will install TSC from pypi and validate that it runs. For more information see:
-# https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
-
-name: Pypi smoke tests
-
-on:
- workflow_dispatch:
- release:
- types: [published]
- schedule:
- - cron: 0 11 * * * # Every day at 11AM UTC (7AM EST)
-
-permissions:
- contents: read
-
-jobs:
- build:
- strategy:
- fail-fast: false
- matrix:
- os: [ubuntu-latest, macos-latest, windows-latest]
- python-version: ['3.x']
-
- runs-on: ${{ matrix.os }}
-
- steps:
- - name: Set up Python ${{ matrix.python-version }} on ${{ matrix.os }}
- uses: actions/setup-python@v5
- with:
- python-version: ${{ matrix.python-version }}
- - name: pip install
- run: |
- pip uninstall tableauserverclient
- pip install tableauserverclient
- - name: Launch app
- run: |
- python -c "import tableauserverclient as TSC
- server = TSC.Server('http://example.com', use_server_version=False)"
diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml
deleted file mode 100644
index 9ac7ebb6a..000000000
--- a/.github/workflows/run-tests.yml
+++ /dev/null
@@ -1,56 +0,0 @@
-name: Python tests
-
-on:
- pull_request: {}
- push:
- branches:
- - development
- - master
-
-jobs:
- build:
- strategy:
- fail-fast: false
- matrix:
- os: [ubuntu-latest, macos-latest, windows-latest]
- python-version: ['3.10', '3.11', '3.12', '3.13', '3.14', '3.14t']
-
- runs-on: ${{ matrix.os }}
-
- steps:
- - name: Get pip cache dir
- id: pip-cache
- shell: bash
- run: |
- echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT
-
- - name: cache
- uses: actions/cache@v4
- with:
- path: ${{ steps.pip-cache.outputs.dir }}
- key: ${{ runner.os }}-${{ matrix.python-version }}-pip-${{ hashFiles('pyproject.toml') }}
- restore-keys: |
- ${{ runner.os }}-${{ matrix.python-version }}-pip-
-
- - uses: actions/checkout@v4
-
- - name: Set up Python ${{ matrix.python-version }} on ${{ matrix.os }}
- uses: actions/setup-python@v5
- with:
- python-version: ${{ matrix.python-version }}
- allow-prereleases: ${{ matrix.allow-prereleases || false }}
-
- - name: Install dependencies
- run: |
- python -m pip install --upgrade pip
- pip install -e .[test] build
-
- - name: Test with pytest
- if: always()
- run: |
- pytest test -n auto
-
- - name: Test build
- if: always()
- run: |
- python -m build
diff --git a/.github/workflows/slack.yml b/.github/workflows/slack.yml
deleted file mode 100644
index 9afebf25b..000000000
--- a/.github/workflows/slack.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-name: π¬ Send Message to Slack π
-
-on: [push, pull_request, issues]
-
-jobs:
- slack-notifications:
- continue-on-error: true
- runs-on: ubuntu-latest
- name: Sends a message to Slack when a push, a pull request or an issue is made
- steps:
- - name: Send message to Slack API
- continue-on-error: true
- uses: archive/github-actions-slack@v2.8.0
- id: notify
- with:
- slack-bot-user-oauth-access-token: ${{ secrets.SLACK_BOT_USER_OAUTH_ACCESS_TOKEN }}
- slack-channel: C019HCX84L9
- slack-text: Hello! Event "${{ github.event_name }}" in "${{ github.repository }}" π€
- - name: Result from "Send Message"
- run: echo "The result was ${{ steps.notify.outputs.slack-result }}"
diff --git a/.gitignore b/.gitignore
index 6f8a86128..379adaad7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,161 +1,149 @@
-.claude/
-
-# Byte-compiled / optimized / DLL files
-__pycache__/
-*.py[cod]
-*$py.class
-test.junit.xml
-
-# C extensions
-*.so
-
-# Distribution / packaging
-.Python
-env/
-build/
-develop-eggs/
-dist/
-downloads/
-eggs/
-.eggs/
-lib/
-lib64/
-parts/
-sdist/
-var/
-*.egg-info/
-.installed.cfg
-*.egg
-pip-wheel-metadata/
-
-# PyInstaller
-# Usually these files are written by a python script from a template
-# before PyInstaller builds the exe, so as to inject date/other infos into it.
-*.manifest
-*.spec
-
-# Installer logs
-pip-log.txt
-pip-delete-this-directory.txt
-
-# Unit test / coverage reports
-htmlcov/
-.tox/
-.coverage
-.coverage.*
-.cache
-nosetests.xml
-coverage.xml
-*,cover
-.hypothesis/
-
-# Translations
-*.mo
-*.pot
-
-# Django stuff:
-*.log
-local_settings.py
-
-# Flask stuff:
-instance/
-.webassets-cache
-
-# Scrapy stuff:
-.scrapy
-
-# Sphinx documentation
-docs/_build/
-
-# PyBuilder
-target/
-
-# PyCharm stuff
-.idea/
-
-# IPython Notebook
-.ipynb_checkpoints
-
-# pyenv
-.python-version
-
-# poetry
-poetry.lock
-
-# celery beat schedule file
-celerybeat-schedule
-
-# dotenv
-.env
-env.py
-
-# virtualenv
-venv/
-ENV/
-.venv/
-
-# Spyder project settings
-.spyderproject
-
-# Rope project settings
-.ropeproject
-
-# VSCode project settings
-.vscode/
-
-# macOS.gitignore from https://github.com/github/gitignore
-*.DS_Store
-.AppleDouble
-.LSOverride
-
-# Icon must end with two \r
-Icon
-
-
-# Thumbnails
-._*
-
-# Files that might appear in the root of a volume
-.DocumentRevisions-V100
-.fseventsd
-.Spotlight-V100
-.TemporaryItems
-.Trashes
-.VolumeIcon.icns
-.com.apple.timemachine.donotpresent
-
-# Directories potentially created on remote AFP share
-.AppleDB
-.AppleDesktop
-Network Trash Folder
-Temporary Items
-.apdisk
-
-
-
-# Windows.gitignore from https://github.com/github/gitignore
-# Windows image file caches
-Thumbs.db
-ehthumbs.db
-
-# Folder config file
-Desktop.ini
-
-# Recycle Bin used on file shares
-$RECYCLE.BIN/
-
-# Windows Installer files
-*.cab
-*.msi
-*.msm
-*.msp
-
-# Windows shortcuts
-*.lnk
-
-# Documentation
-docs/_site/
-docs/.jekyll-metadata
-docs/Gemfile.lock
-samples/credentials
-.venv/
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*,cover
+.hypothesis/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# PyCharm stuff
+.idea/
+
+# IPython Notebook
+.ipynb_checkpoints
+
+# pyenv
+.python-version
+
+# celery beat schedule file
+celerybeat-schedule
+
+# dotenv
+.env
+
+# virtualenv
+venv/
+ENV/
+
+# Spyder project settings
+.spyderproject
+
+# Rope project settings
+.ropeproject
+
+.vscode/
+
+# macOS.gitignore from https://github.com/github/gitignore
+*.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+
+
+# Windows.gitignore from https://github.com/github/gitignore
+# Windows image file caches
+Thumbs.db
+ehthumbs.db
+
+# Folder config file
+Desktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# Documentation
+_site/
+.jekyll-metadata
+Gemfile.lock
diff --git a/CHANGELOG.md b/CHANGELOG.md
deleted file mode 100644
index c018294d3..000000000
--- a/CHANGELOG.md
+++ /dev/null
@@ -1,245 +0,0 @@
-
-## 0.18.0 (6 April 2022)
-* Switched to using defused_xml for xml attack protection
-* added linting and type hints
-* improve experience with self-signed certificates/invalid ssl
-* updated samples
-* new item types: metrics, revisions for datasources and workbooks
-* features: support adding flows to schedules, exporting workbooks to powerpoint
-* fixes: delete extracts
-
-## 0.17.0 (20 October 2021)
-* Added support for accepting parameters for post request of the metadata api (#850)
-* Fixed jobs.get_by_id(job_id) example & reference docs (#867, #868)
-* Fixed handling for workbooks in personal spaces which do not have projectID or Name (#875)
-* Updated links to Data Source Methods page in REST API docs (#879)
-* Unified arguments of sample scripts (#889)
-* Updated docs for - links to Datasource API (#879) , sample scripts (#892) & metadata query (#896)
-* Added support for scheduling DataUpdate Jobs (#891)
-* Exposed the fileuploads API endpoint (#894)
-* Added a new sample & documentation for metadata API (#895, #896)
-* Added support to the package for getting flow run status, as well as the ability to cancel flow runs. (#884)
-* Added jobs.wait_for_job method (#903)
-* Added description support for datasources item (#912)
-* Dropped support for Python 3.5 (#911)
-
-## 0.16.0 (15 July 2021)
-* Documentation updates (#800, #818, #839, #842)
-* Fixed data alert repr in subscription item (#821)
-* Added support for Data Quality Warning (#836)
-* Added support for renaming datasources (#843)
-* Improved Datasource tests (#843)
-* Updated catalog obfuscation field (#844)
-* Fixed revision limit field in site_item.py file (#847)
-* Added the Missing content permission field- LockedToProjectWithoutNested (#856)
-
-## 0.15.0 (16 Feb 2021)
-* Added support for python version 3.9 (#744)
-* Added support for 'Get View by ID' (#750)
-* Added docs and test data to MANIFEST.in file (#780)
-* Added owner_id property to ProjectItem (#784)
-* Added support for skipping connection check while publishing workbook (#791)
-* Added support for 'Update Subscription' (#794)
-* Added support for 'Get Groups for a User' (#799)
-* Improved debug logging by including put/post request contents (#743)
-* Improved local and active-directory group creation (#770)
-* Improved 'Update Group' to match server requests/responses (#772)
-* Improved SiteItem with new properties and functions (#777)
-* Improved SubscriptionItem with new properties (#794)
-* Improved the 'type' property of TaskItem to convert server response to enum (#796)
-* Improved repository to use Github Actions for running tests/linter (#798)
-* Fixed data_acceleration field causing error in workbook update payload (#741)
-
-## 0.14.1 (9 Dec 2020)
-* Fixed filter query issue for server version below 2020.1 (#745)
-* Fixed large workbook/datasource publish issue (#757)
-
-## 0.14.0 (6 Nov 2020)
-* Added django-style filtering and sorting (#615)
-* Added encoding tag-name before deleting (#687)
-* Added 'Execute' Capability to permissions (#700)
-* Added support for publishing workbook using file objects (#704)
-* Added new fields to datasource_item (#705)
-* Added all fields for users.get to get email and fullname (#713)
-* Added support publishing datasource using file objects (#714)
-* Improved request options by removing manual query param generation (#686)
-* Improved publish_workbook sample to take in site (#694)
-* Improved schedules.update() by removing constraint that required an interval (#711)
-* Fixed site update/create not checking booleans properly (#723)
-
-## 0.13 (1 Sept 2020)
-* Added notes field to JobItem (#571)
-* Added webpage_url field to WorkbookItem (#661)
-* Added support for switching between sites (#655)
-* Added support for querying favorites for a user (#656)
-* Added support for Python 3.8 (#659)
-* Added support for Data Alerts (#667)
-* Added support for basic Extract operations - Create, Delete, en/re/decrypt for site (#672)
-* Added support for creating and querying Active Directory groups (#674)
-* Added support for asynchronously updating a group (#674)
-* Improved handling of invalid dates (#529)
-* Improved consistency of update_permission endpoints (#668)
-* Documentation updates (#658, #669, #670, #673, #683)
-
-## 0.12.1 (22 July 2020)
-
-* Fixed login.py sample to properly handle sitename (#652)
-
-## 0.12 (10 July 2020)
-
-* Added hidden_views parameter to workbook publish method (#614)
-* Added simple paging endpoint for GraphQL/Metadata API (#623)
-* Added endpoints to Metadata API for retrieving backfill/eventing status (#626)
-* Added maxage parameter to CSV and PDF export options (#635)
-* Added support for querying, adding, and deleting favorites (#638)
-* Added a sample for publishing datasources (#644)
-
-## 0.11 (1 May 2020)
-
-* Added more fields to Data Acceleration config (#588)
-* Added OpenID as an auth setting enum (#610)
-* Added support for Data Acceleration Reports (#596)
-* Added support for view permissions (#526)
-* Materialized views changed to Data Acceleration (#576)
-* Improved consistency across workbook/datasource endpoints (#570)
-* Fixed print error in update_connection.py (#602)
-* Fixed log error in add user endpoint (#608)
-
-## 0.10 (21 Feb 2020)
-
-* Added a way to handle non-xml errors (#515)
-* Added Webhooks endpoints for create, delete, get, list, and test (#523, #532)
-* Added delete method in the tasks endpoint (#524)
-* Added description attribute to WorkbookItem (#533)
-* Added support for materializeViews as schedule and task types (#542)
-* Added warnings to schedules (#550, #551)
-* Added ability to update parent_id attribute of projects (#560, #567)
-* Improved filename behavior for download endpoints (#517)
-* Improved logging (#508)
-* Fixed runtime error in permissions endpoint (#513)
-* Fixed move_workbook_sites sample (#503)
-* Fixed project permissions endpoints (#527)
-* Fixed login.py sample to accept site name (#549)
-
-## 0.9 (4 Oct 2019)
-
-* Added Metadata API endpoints (#431)
-* Added site settings for Data Catalog and Prep Conductor (#434)
-* Added new fields to ViewItem (#331)
-* Added support and samples for Tableau Server Personal Access Tokens (#465)
-* Added Permissions endpoints (#429)
-* Added tags to ViewItem (#470)
-* Added Databases and Tables endpoints (#445)
-* Added Flow endpoints (#494)
-* Added ability to filter projects by topLevelProject attribute (#497)
-* Improved server_info endpoint error handling (#439)
-* Improved Pager to take in keyword arguments (#451)
-* Fixed UUID serialization error while publishing workbook (#449)
-* Fixed materalized views in request body for update_workbook (#461)
-
-## 0.8.1 (17 July 2019)
-
-* Fixed update_workbook endpoint (#454)
-
-## 0.8 (8 Apr 2019)
-
-* Added Max Age to download view image request (#360)
-* Added Materialized Views (#378, #394, #396)
-* Added PDF export of Workbook (#376)
-* Added Support User Role (#392)
-* Added Flows (#403)
-* Updated Pager to handle un-paged results (#322)
-* Fixed checked upload (#309, #319, #326, #329)
-* Fixed embed_password field on publish (#416)
-
-## 0.7 (2 Jul 2018)
-
-* Added cancel job (#299)
-* Added Get background jobs (#298)
-* Added Multi-credential support (#276)
-* Added Update Groups (#279)
-* Adding project_id to view (#285)
-* Added ability to rename workbook using `update workbook` (#284)
-* Added Sample for exporting full pdf using pdf page combining (#267)
-* Added Sample for exporting data, images, and single view pdfs (#263)
-* Added view filters to the populate request options (#260)
-* Add Async publishing for workbook and datasource endpoints (#311)
-* Fixed ability to update datasource server connection port (#283)
-* Fixed next project handling (#267)
-* Cleanup debugging output to strip out non-xml response
-* Improved refresh sample for readability (#288)
-
-## 0.6.1 (26 Jan 2018)
-
-* Fixed #257 where refreshing extracts does not work due to a missing "self"
-
-## 0.6 (17 Jan 2018)
-
-* Added support for add a datasource/workbook refresh to a schedule (#244)
-* Added support for updating datasource connections (#253)
-* Added Refresh Now for datasource and workbooks (#250)
-* Fixed Typos in the docs (#251)
-
-## 0.5.1 (21 Sept 2017)
-
-* Fix a critical issue caused by #224 that was the result of lack of test coverage (#226)
-
-## 0.5 (20 Sept 2017)
-
-* Added revision settings to update site (#187)
-* Added support for certified data sources (#189)
-* Added support for include/exclude extract (#203)
-* Added auto-paging for group users (#204)
-* Added ability to add workbooks to a schedule (#206)
-* Added the ability to create nested projects (#208)
-* Fixed sort order when using pager (#192)
-* Docs Updates and new samples (#196, #199, #200, #201)
-
-## 0.4.1 (18 April 2017)
-
-* Fix #177 to remove left in debugging print
-
-## 0.4 (18 April 2017)
-
-Yikes, it's been too long.
-
-* Added API version annotations to endpoints (#125)
-* Added New High Res Image Api Endpoint
-* Added Tags to Datasources, Views
-* Added Ability to run an Extract Refresh task (#159)
-* Auto versioning enabled (#169)
-* Download twbx/tdsx without the extract payload (#143, #144)
-* New Sample to initialize a server (#95)
-* Added ability to update connection information (#149)
-* Added Ability to get a site by name (#153)
-* Dates are now DateTime Objects (#102)
-* Bugfixes (#162, #166)
-
-## 0.3 (11 January 2017)
-
-* Return DateTime objects instead of strings (#102)
-* UserItem now is compatible with Pager (#107, #109)
-* Deprecated site in favor of site_id (#97)
-* Improved handling of large downloads (#105, #111)
-* Added support for oAuth when publishing (#117)
-* Added Testing against Py36 (#122, #123)
-* Added Version Checking to use highest supported REST api version (#100)
-* Added Infrastructure for throwing error if trying to do something that is not supported by REST api version (#124)
-* Various Code Cleanup
-* Added Documentation (#98)
-* Improved Test Infrastructure (#91)
-
-## 0.2 (02 November 2016)
-
-* Added Initial Schedules Support (#48)
-* Added Initial Create Group endpoint (#69)
-* Added Connection Credentials for publishing datasources/workbooks (#80)
-* Added Pager object for handling pagination results and sample (#72, #90)
-* Added ServerInfo endpoint (#84)
-* Deprecated `site` as a parameter to `TableauAuth` in favor of `site_id`
-* Code Cleanup
-* Bugfixes
-
-## 0.1 (12 September 2016)
-
-* Initial Release to the world
diff --git a/CODEOWNERS b/CODEOWNERS
deleted file mode 100644
index 10fb2b98c..000000000
--- a/CODEOWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-#ECCN:Open Source
-#GUSINFO:Open Source,Open Source Workflow
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
deleted file mode 100644
index a69cfff21..000000000
--- a/CONTRIBUTORS.md
+++ /dev/null
@@ -1,82 +0,0 @@
-This project wouldn't be possible without our amazing contributors.
-
-The following people have contributed to this project to make it possible, and we thank them for their contributions!
-
-## Contributors
-
-* [jacalata](https://github.com/jacalata)
-* [jorwoods](https://github.com/jorwoods)
-* [t8y8](https://github.com/t8y8)
-* [bcantoni](https://github.com/bcantoni)
-* [shinchris](https://github.com/shinchris)
-* [vogelsgesang](https://github.com/vogelsgesang)
-* [lbrendanl](https://github.com/lbrendanl)
-* [LGraber](https://github.com/LGraber)
-* [gaoang2148](https://github.com/gaoang2148)
-* [benlower](https://github.com/benlower)
-* [liu-rebecca](https://github.com/liu-rebecca)
-* [guodah](https://github.com/guodah)
-* [jdomingu](https://github.com/jdomingu)
-* [kykrueger](https://github.com/kykrueger)
-* [jz-huang](https://github.com/jz-huang)
-* [opus-42](https://github.com/opus-42)
-* [markm-io](https://github.com/markm-io)
-* [graysonarts](https://github.com/graysonarts)
-* [d45](https://github.com/d45)
-* [preguraman](https://github.com/preguraman)
-* [sotnich](https://github.com/sotnich)
-* [mmuttreja-tableau](https://github.com/mmuttreja-tableau)
-* [dependabot[bot]](https://github.com/apps/dependabot)
-* [scuml](https://github.com/scuml)
-* [ovinis](https://github.com/ovinis)
-* [FFMMM](https://github.com/FFMMM)
-* [martinbpeters](https://github.com/martinbpeters)
-* [talvalin](https://github.com/talvalin)
-* [dzucker-tab](https://github.com/dzucker-tab)
-* [a-torres-2](https://github.com/a-torres-2)
-* [nnevalainen](https://github.com/nnevalainen)
-* [mbren](https://github.com/mbren)
-* [wolkiewiczk](https://github.com/wolkiewiczk)
-* [jacobj10](https://github.com/jacobj10)
-* [hugoboos](https://github.com/hugoboos)
-* [grbritz](https://github.com/grbritz)
-* [fpagliar](https://github.com/fpagliar)
-* [bskim45](https://github.com/bskim45)
-* [baixin137](https://github.com/baixin137)
-* [jessicachen79](https://github.com/jessicachen79)
-* [gconklin](https://github.com/gconklin)
-* [geordielad](https://github.com/geordielad)
-* [fossabot](https://github.com/fossabot)
-* [daniel1608](https://github.com/daniel1608)
-* [annematronic](https://github.com/annematronic)
-* [rshide](https://github.com/rshide)
-* [VathsalaAchar](https://github.com/VathsalaAchar)
-* [TrimPeachu](https://github.com/TrimPeachu)
-* [ajbosco](https://github.com/ajbosco)
-* [jimbodriven](https://github.com/jimbodriven)
-* [ltiffanydev](https://github.com/ltiffanydev)
-* [martydertz](https://github.com/martydertz)
-* [r-richmond](https://github.com/r-richmond)
-* [sfarr15](https://github.com/sfarr15)
-* [tagyoureit](https://github.com/tagyoureit)
-* [tjones-commits](https://github.com/tjones-commits)
-* [yoshichan5](https://github.com/yoshichan5)
-* [wlodi83](https://github.com/wlodi83)
-* [anipmehta](https://github.com/anipmehta)
-* [cmtoomey](https://github.com/cmtoomey)
-* [pes-magic](https://github.com/pes-magic)
-* [illonage](https://github.com/illonage)
-* [jayvdb](https://github.com/jayvdb)
-* [jorgeFons](https://github.com/jorgeFons)
-* [Kovner](https://github.com/Kovner)
-* [LarsBreddemann](https://github.com/LarsBreddemann)
-* [lboynton](https://github.com/lboynton)
-* [maddy-at-leisure](https://github.com/maddy-at-leisure)
-* [narcolino-tableau](https://github.com/narcolino-tableau)
-* [PatrickfBraz](https://github.com/PatrickfBraz)
-* [paulvic](https://github.com/paulvic)
-* [shrmnk](https://github.com/shrmnk)
-* [TableauKyle](https://github.com/TableauKyle)
-* [bossenti](https://github.com/bossenti)
-* [ma7tcsp](https://github.com/ma7tcsp)
-* [toomyem](https://github.com/toomyem)
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 000000000..37f5eaa42
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,2 @@
+source 'https://rubygems.org'
+gem 'github-pages', group: :jekyll_plugins
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index 22f90640f..000000000
--- a/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2022 Tableau
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/LICENSE.versioneer b/LICENSE.versioneer
deleted file mode 100644
index fd650462d..000000000
--- a/LICENSE.versioneer
+++ /dev/null
@@ -1,7 +0,0 @@
-## License
-
-To make Versioneer easier to embed, all its code is dedicated to the public
-domain. The `_version.py` that it creates is also in the public domain.
-Specifically, both are released under the Creative Commons "Public Domain
-Dedication" license (CC0-1.0), as described in
-https://creativecommons.org/publicdomain/zero/1.0/ .
diff --git a/MANIFEST.in b/MANIFEST.in
deleted file mode 100644
index 7acbed103..000000000
--- a/MANIFEST.in
+++ /dev/null
@@ -1,19 +0,0 @@
-include CHANGELOG.md
-include contributing.md
-include CONTRIBUTORS.md
-include LICENSE
-include LICENSE.versioneer
-include README.md
-recursive-include docs *.md
-recursive-include samples *.py
-recursive-include samples *.txt
-recursive-include test *.csv
-recursive-include test *.dict
-recursive-include test *.hyper
-recursive-include test *.json
-recursive-include test *.pdf
-recursive-include test *.png
-recursive-include test *.py
-recursive-include test *.xml
-recursive-include test *.tde
-global-include *.typed
diff --git a/README.md b/README.md
index 5c80f337e..1353e7d92 100644
--- a/README.md
+++ b/README.md
@@ -1,22 +1,5 @@
-# Tableau Server Client (Python)
+# Tableau Server Client - Documentation
-[](https://www.tableau.com/support-levels-it-and-developer-tools) [](https://github.com/tableau/server-client-python/actions)
-[](https://app.fossa.com/projects/git%2Bgithub.com%2Ftableau%2Fserver-client-python?ref=badge_shield)
+This is the documentation branch.
-Use the Tableau Server Client (TSC) library to increase your productivity as you interact with the Tableau Server REST API. With the TSC library you can do almost everything that you can do with the REST API, including:
-
-* Publish workbooks and data sources.
-* Create users and groups.
-* Query projects, sites, and more.
-
-This repository contains Python source code for the library and sample files showing how to use it. As of September 2024, support for Python 3.7 and 3.8 will be dropped - support for older versions of Python aims to match https://devguide.python.org/versions/
-
-To see sample code that works directly with the REST API (in Java, Python, or Postman), visit the [REST API Samples](https://github.com/tableau/rest-api-samples) repo.
-
-For more information on installing and using TSC, see the documentation:
-
-
-To contribute, see our [Developer Guide](https://tableau.github.io/server-client-python/docs/dev-guide). A list of all our contributors to date is in [CONTRIBUTORS.md].
-
-## License
-[](https://app.fossa.com/projects/git%2Bgithub.com%2Ftableau%2Fserver-client-python?ref=badge_large)
+Refer to the [Developer Guide](https://tableau.github.io/server-client-python/docs/dev-guide) for information on making changes or view the [published docs](https://tableau.github.io/server-client-python/) to see the final output.
diff --git a/_config.yml b/_config.yml
new file mode 100644
index 000000000..5ea15f228
--- /dev/null
+++ b/_config.yml
@@ -0,0 +1,17 @@
+# Site settings
+title: Tableau Server Client Library (Python)
+email: github@tableau.com
+description: Simplify interactions with the Tableau Server REST API.
+baseurl: "/server-client-python"
+permalinks: pretty
+defaults:
+ -
+ scope:
+ path: "" # Apply to all files
+ values:
+ layout: "default"
+
+# Build settings
+markdown: kramdown
+highlighter: rouge
+
diff --git a/_includes/analytics.html b/_includes/analytics.html
new file mode 100644
index 000000000..0cdbad25d
--- /dev/null
+++ b/_includes/analytics.html
@@ -0,0 +1,7 @@
+
+
+
diff --git a/_includes/docs_menu.html b/_includes/docs_menu.html
new file mode 100644
index 000000000..62dbde929
--- /dev/null
+++ b/_includes/docs_menu.html
@@ -0,0 +1,115 @@
+
diff --git a/_includes/footer.html b/_includes/footer.html
new file mode 100644
index 000000000..8167ebe55
--- /dev/null
+++ b/_includes/footer.html
@@ -0,0 +1,20 @@
+
+
+
+
+
+ This site is open source. Suggestions and pull requests are welcome on our GitHub page .
+ © Copyright Salesforce, Inc. All rights reserved. Various trademarks held by their respective owners.
+ Salesforce, Inc. Salesforce Tower, 415 Mission Street, 3rd Floor, San Francisco, CA 94105, United States
+ Documentation last generated on: {{ site.time }}
+
+
+
\ No newline at end of file
diff --git a/_includes/head.html b/_includes/head.html
new file mode 100644
index 000000000..5f280e83e
--- /dev/null
+++ b/_includes/head.html
@@ -0,0 +1,25 @@
+
+
+
+
+ {% if page.title %}{{ page.title | escape }}{% else %}{{ site.title | escape }}{% endif %}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+{% if jekyll.environment == "production" %}{% include analytics.html %}{% endif %}
diff --git a/_includes/header.html b/_includes/header.html
new file mode 100644
index 000000000..1981675d9
--- /dev/null
+++ b/_includes/header.html
@@ -0,0 +1,29 @@
+
diff --git a/_includes/icon-github.svg b/_includes/icon-github.svg
new file mode 100644
index 000000000..4422c4f5d
--- /dev/null
+++ b/_includes/icon-github.svg
@@ -0,0 +1 @@
+
diff --git a/_includes/search_form.html b/_includes/search_form.html
new file mode 100644
index 000000000..41bb34259
--- /dev/null
+++ b/_includes/search_form.html
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/_layouts/default.html b/_layouts/default.html
new file mode 100644
index 000000000..38ee020bb
--- /dev/null
+++ b/_layouts/default.html
@@ -0,0 +1,34 @@
+
+
+
+
+ {% include head.html %}
+
+
+
+
+ {% include header.html %}
+
+ {% for post in site.posts %}
+
+
{{ post.title }}
+
+
Posted on {{ post.date | date: "%-d %B %Y" }}
+
+
+ {{ post.abstract }}
+
+ {% if post.photoname %}
+
{% endif %}
+
+
+ {{ post.content }}
+
+
+ {% endfor %}
+
+ {% include footer.html %}
+
+
+
+
diff --git a/_layouts/docs.html b/_layouts/docs.html
new file mode 100644
index 000000000..d40bafbdf
--- /dev/null
+++ b/_layouts/docs.html
@@ -0,0 +1,31 @@
+---
+layout: docs
+---
+
+
+
+
+
+ {% include head.html %}
+
+
+
+
+ {% include header.html %}
+ {% include docs_menu.html %}
+
+
+
{{ page.title }}
+
+
+ {{ content }}
+ {% include footer.html %}
+
+
+
+
+
diff --git a/_layouts/home.html b/_layouts/home.html
new file mode 100644
index 000000000..c2cf32fcb
--- /dev/null
+++ b/_layouts/home.html
@@ -0,0 +1,19 @@
+---
+layout: home
+---
+
+
+
+
+ {% include head.html %}
+
+
+
+
+ {% include header.html %}
+ {{ content }}
+ {% include footer.html %}
+
+
+
+
diff --git a/_layouts/search.html b/_layouts/search.html
new file mode 100644
index 000000000..96dbd94a1
--- /dev/null
+++ b/_layouts/search.html
@@ -0,0 +1,43 @@
+---
+layout: search
+---
+
+
+
+
+
+ {% include head.html %}
+
+
+
+
+
+
+
+
+ {% include header.html %}
+ {% include docs_menu.html %}
+
+
+
+
+
+
Loading search results...
+
+
+ {% include footer.html %}
+
+
+
+
diff --git a/assets/fiddler.png b/assets/fiddler.png
new file mode 100644
index 000000000..cb5fde8e6
Binary files /dev/null and b/assets/fiddler.png differ
diff --git a/assets/logo.png b/assets/logo.png
new file mode 100644
index 000000000..607611521
Binary files /dev/null and b/assets/logo.png differ
diff --git a/contributing.md b/contributing.md
deleted file mode 100644
index bba25e1d6..000000000
--- a/contributing.md
+++ /dev/null
@@ -1,55 +0,0 @@
-# Contributing
-
-We welcome contributions to this project!
-
-Contribution can include, but are not limited to, any of the following:
-
-* File an Issue
-* Request a Feature
-* Implement a Requested Feature
-* Fix an Issue/Bug
-* Add/Fix documentation
-
-## Issues and Feature Requests
-
-To submit an issue/bug report, or to request a feature, please submit a [GitHub issue](https://github.com/tableau/server-client-python/issues) to the repo.
-
-If you are submitting a bug report, please provide as much information as you can, including clear and concise repro steps, attaching any necessary
-files to assist in the repro. **Be sure to scrub the files of any potentially sensitive information. Issues are public.**
-
-For a feature request, please try to describe the scenario you are trying to accomplish that requires the feature. This will help us understand
-the limitations that you are running into, and provide us with a use case to know if we've satisfied your request.
-
-### Making Contributions
-
-Refer to the [Developer Guide](https://tableau.github.io/server-client-python/docs/dev-guide) which explains how to make contributions to the TSC project.
-
-## Running Tests
-
-### Unit tests
-
-```bash
-pip install -e ".[test]"
-pytest
-```
-
-### End-to-end tests
-
-E2e tests run against a real Tableau server and are kept in `test_e2e/`. They are excluded from the default `pytest` run.
-
-**Required environment variables:**
-
-| Variable | Description |
-|---|---|
-| `TABLEAU_SERVER` | Server URL, e.g. `https://10ax.online.tableau.com/` |
-| `TABLEAU_SITE` | Site content URL |
-| `TABLEAU_TOKEN` | Personal access token value |
-| `TABLEAU_TOKEN_NAME` | Personal access token name |
-| `TABLEAU_PROJECT` | Project to publish test content into (defaults to `Default`) |
-
-**Run:**
-
-```bash
-TABLEAU_SERVER=https://... TABLEAU_SITE=mysite TABLEAU_TOKEN=... TABLEAU_TOKEN_NAME=mytoken TABLEAU_PROJECT="My Project" \
-pytest test_e2e/
-```
diff --git a/css/api_ref.css b/css/api_ref.css
new file mode 100644
index 000000000..62da93510
--- /dev/null
+++ b/css/api_ref.css
@@ -0,0 +1,709 @@
+
-
-
-
-
-
-
- "Europe"
- "Middle East"
- "The Americas"
- "Oceania"
- "Asia"
- "Africa"
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Country ranks by GDP, GDP per Capita, Population, and Life Expectancy
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Gross Domestic Product
- in current US Dollars
-
-
-
-
-
-
- Gross Domestic Product
- per capita
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- "[World Indicators new].[sum:F: GDP (curr $):qk]"
- "[World Indicators new].[rank:sum:F: GDP (curr $):qk]"
- "[World Indicators new].[sum:F: GDP per capita (curr $):qk]"
- "[World Indicators new].[rank:sum:F: GDP per capita (curr $):qk]"
- "[World Indicators new].[sum:P: Population (count):qk]"
- "[World Indicators new].[rank:sum:P: Population (count):qk]"
- "[World Indicators new].[avg:H: Life exp (years):qk]"
- "[World Indicators new].[rank:avg:H: Life exp (years) (copy):qk]"
-
-
-
-
-
-
-
-
- [World Indicators new].[:Measure Names]
- [World Indicators new].[yr:Date:ok]
- [World Indicators new].[none:F: GDP (curr $):qk]
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- <[World Indicators new].[none:Country / Region:nk]>
- Γ
- <[World Indicators new].[:Measure Names]>:
- <[World Indicators new].[Multiple Values]>
-
-
-
-
-
- [World Indicators new].[none:Country / Region:nk]
- [World Indicators new].[:Measure Names]
-
-
-
-
-
-
- <
- [World Indicators new].[yr:Date:ok]
- >
- GDP per capita by country
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Gross Domestic Product
- per capita
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- [World Indicators new].[yr:Date:ok]
- [World Indicators new].[none:Region:nk]
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Country:
- <[World Indicators new].[none:Country / Region:nk]>
- Region:
- <[World Indicators new].[none:Region:nk]>
- GDP per capita (curr $):
- <[World Indicators new].[avg:F: GDP per capita (curr $):qk]>
- % of world average:
- <[World Indicators new].[usr:Calculation1:qk]>
-
-
-
-
-
- [World Indicators new].[none:Country / Region:nk]
- [World Indicators new].[avg:F: GDP per capita (curr $):qk]
-
-
-
-
-
-
- GDP per capita by region
- Click on a point to filter the map to a specific year.
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Gross Domestic Product
- in current US Dollars
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- [World Indicators new].[Action (Country Name)]
- [World Indicators new].[Action (Region)]
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- <[World Indicators new].[none:Region:nk]>
- Year:
- <[World Indicators new].[yr:Date:ok]>
- Average GDP (curr $):
- <[World Indicators new].[avg:F: GDP (curr $):qk]>
- GDP per capita (weighted):
- <[World Indicators new].[usr:Calculation_1590906174513693:qk]>
-
-
-
-
-
- [World Indicators new].[usr:Calculation_1590906174513693:qk]
- [World Indicators new].[yr:Date:ok]
-
-
-
-
-
-
- GDP per capita by country
- Currently filtered to
- <[World Indicators new].[yr:Date:ok]>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Gross Domestic Product
- per capita
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 199.0
- 104512.0
-
-
-
-
-
-
-
- "The Americas"
- "Europe"
- %null%
- "Oceania"
- "Africa"
- "Middle East"
- "Asia"
- %all%
-
-
-
- [World Indicators new].[avg:F: GDP per capita (curr $):qk]
- [World Indicators new].[none:Region:nk]
- [World Indicators new].[Action (YEAR(Date (year)))]
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- <[World Indicators new].[none:Country / Region:nk]>
- Γ
- Region:
- <[World Indicators new].[none:Region:nk]>
- Subregion:
- <[World Indicators new].[none:Subregion:nk]>
- GDP per capita (curr $):
- <[World Indicators new].[avg:F: GDP per capita (curr $):qk]>
- GDP % of Subregion average:
- <[World Indicators new].[usr:Calculation1:qk:5]>
- GDP % of World average:
- <[World Indicators new].[usr:Calculation1:qk:1]>
-
-
-
-
-
- [World Indicators new].[Latitude (generated)]
- [World Indicators new].[Longitude (generated)]
-
-
-
-
-
-
- <Sheet Name>, <Page Name>
- Γ
- Click the forward button on year to watch the change over time
Hover over mark to see the history of that country
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- [World Indicators new].[avg:H: Health exp/cap (curr $):qk]
- [World Indicators new].[avg:H: Life exp (years):qk]
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- <[World Indicators new].[none:Country / Region:nk]>
- Γ
- Region:
- <[World Indicators new].[none:Region:nk]>
- Year:
- <[World Indicators new].[yr:Date:ok]>
- Health exp/cap (curr $):
- <[World Indicators new].[avg:H: Health exp/cap (curr $):qk]>
- Life Expectancy:
- <[World Indicators new].[avg:H: Life exp (years):qk]>
-
-
-
-
-
- [World Indicators new].[avg:H: Life exp (years):qk]
- [World Indicators new].[avg:H: Health exp/cap (curr $):qk]
-
- [World Indicators new].[yr:Date:ok]
-
-
-
-
-
-
-
-
- Lending and deposit interest rates, GDP per capita and % of world GDP
sorted by GDP per Capita for region and subregion,
- <
- [World Indicators new].[yr:Date:ok]
- >
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Gross Domestic Product
- in current US Dollars
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- "[World Indicators new].[avg:F: Lending interest rate (\%):qk]"
- "[World Indicators new].[avg:F: Deposit interest rate (\%):qk]"
- "[World Indicators new].[usr:Calculation_8570907072742130:qk]"
- "[World Indicators new].[usr:Calculation_1590906174513693:qk]"
- "[World Indicators new].[pcto:sum:F: GDP (curr $):qk]"
-
-
-
-
-
-
-
-
- [World Indicators new].[:Measure Names]
- [World Indicators new].[yr:Date:ok]
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- ([World Indicators new].[none:Region:nk] / [World Indicators new].[none:Subregion:nk])
- [World Indicators new].[:Measure Names]
-
-
-
-
-
-
- <[World Indicators new].[yr:Date:ok]> Country <Sheet Name>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Gross Domestic Product
- in current US Dollars
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- [World Indicators new].[yr:Date:ok]
- [World Indicators new].[sum:F: GDP (curr $):qk]
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- <[World Indicators new].[none:Country / Region:nk]>
- Γ
- Region:
- <[World Indicators new].[none:Region:nk]>
- % of World GDP:
- <[World Indicators new].[pcto:sum:F: GDP (curr $):qk:1]>
- GDP (US $'s):
- <[World Indicators new].[sum:F: GDP (curr $):qk]>
-
-
-
-
- <[World Indicators new].[none:Country / Region:nk]>
- Γ
- <[World Indicators new].[pcto:sum:F: GDP (curr $):qk:1]>
<[World Indicators new].[sum:F: GDP (curr $):qk]>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- GDP per Capita
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- [World Bank Indicators (Excel)].[:Measure Names]
- [World Bank Indicators (Excel)].[Region (group)]
- [World Bank Indicators (Excel)].[none:'Regions and subregions$'_Subregion:nk]
- [World Bank Indicators (Excel)].[none:Country Name:nk]
- [World Indicators new].[none:Region:nk]
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 2009
-
-
-
-
-
- [World Bank Indicators (Excel)].[none:'Regions and subregions$'_Region:nk]
- [World Bank Indicators (Excel)].[none:Country Name:nk]
- [World Bank Indicators (Excel)].[yr:Date:ok]
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- [Sample - Superstore Sales (Excel)].[none:Order Date:qk]
- [Sample - Superstore Sales (Excel)].[none:Order ID:ok]
- [Sample - Superstore Sales (Excel)].[none:Product Name:nk]
- [Sample - Superstore Sales (Excel)].[none:Product Sub-Category:nk]
- [Sample - Superstore Sales (Excel)].[none:Region:nk]
- [Sample - Superstore Sales (Excel)].[none:Ship Mode:nk]
- [Sample - Superstore Sales (Excel)].[qr:Order Date:ok]
- [Sample - Superstore Sales (Excel)].[yr:Order Date:ok]
- [World Bank Indicators (Excel)].[none:'Regions and subregions$'_Region:nk]
- [World Indicators new].[none:Country / Region:nk]
- [World Indicators new].[none:F: GDP (curr $):qk]
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- [Sample - Superstore Sales (Excel)].[none:Order Date:qk]
- [Sample - Superstore Sales (Excel)].[none:Order ID:ok]
- [Sample - Superstore Sales (Excel)].[none:Product Name:nk]
- [Sample - Superstore Sales (Excel)].[none:Product Sub-Category:nk]
- [Sample - Superstore Sales (Excel)].[none:Region:nk]
- [Sample - Superstore Sales (Excel)].[none:Ship Mode:nk]
- [Sample - Superstore Sales (Excel)].[qr:Order Date:ok]
- [Sample - Superstore Sales (Excel)].[yr:Order Date:ok]
- [World Bank Indicators (Excel)].[none:'Regions and subregions$'_Region:nk]
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- [World Bank Indicators (Excel)].[none:'Regions and subregions$'_Region:nk]
- [World Bank Indicators (Excel)].[none:'Regions and subregions$'_Subregion:nk]
- [World Bank Indicators (Excel)].[none:Country Name:nk]
- [World Bank Indicators (Excel)].[yr:Date:ok]
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 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
- 99DO9LsFdXdHSp4fGNzb1tJkakr7jgMP3rlH8HDX3tuacxlENFthp89esNMZrOo7d+1KKXjL
- D3ETseIHoqE3OTlj5VozupieK2Sb21ubUvNz822dXQQBACxNnPz6j87+zOMP93Z3VpcX8stO
- d093qVTu6GzHAACitLiwWAm7O1vmZudVTRt795V5o//BO/e2Z41Cyc+aeHq+2NaWLixVdV3r
- 7uwAHk5OzlJQtm1r+f5f/Of07Z9+9J5BACgvLSCzKW0q+Zmpks87s/An/+lvSqXSz//W7/Q1
- py2VzS4Uu/v6KksLuZa2pcXlto62LVvgZuEqHIk0cJartK0le4Xlq8VFrqUyVqPzet26p6cK
- XT0dGK0ToBCCzU5NMsXq6WzfCmB80FAnIMFePfIcNVts4Te3ZVB250C77RbGz+bhnn39V1Mh
- e/vtE/feewgAIre46OHOlky5MCPs9qxZC5t4S5PPvzPVkVV9zz2wa4fa2pNanZg4Oz3VltVe
- O7/4iXv2Xdkzo8m5xb6ezitvpRDBsePnuZc3DNPuu2OwM3Xl924hidqgRksjc6z18/ff/cKT
- 33X8ZaOKLpyY72nShyaLCsZ37ul588XnC45oyyoLRbd391137uo+c/zlsZL204/c//bRv19w
- ob2ze3F+Vk1llvJFd+n7ToSaLHw6r/36Lz367ivPOm13m86UK8zHP3347LFXKd4+Op3HkeNM
- nTf2fyKavlhmeovFlEznA/fc8aMnv33P/fefeOd8dWmu1eLTC+X7P/UZ258++sYZTlRNs/b1
- 505cmM/qjs/0wdvv77fdv/vO0488eHB6fnHbnkO3DbSdeuuVKd/4zMP3vv78U3rXrvFzxzUt
- ta2va3RysqOrfzGf7+3Jzc6WhodO33f7Hn927NJ7s/sO3r2thRx98wL3Szrh+VJ4z323v/3a
- sV27ekZm/IcPP9hkXwkr/cihpjwQKxeW5qenZxilBNjwhTN2x860ocxMjJQcX4hobLqYQd7o
- TGHHzv7hoRHul46dHh65eJEJKBaXOztbzxx/G9Jthfk5t1ItVYKBdrMYovbOdiJQS0tra7O1
- UOJsed4TqKW1tXv7rqCyyBlramlub9JOD82lbXup4t1z1x0Ek5a2zo7WXFf/Hmdx7J0TZ42U
- rWJMA8duaadMrc4OvfLWidnJS9MLy53t2cnpgm6l2zs7lmbHO7q7JiZmIr/87pkLQ5cuCUB9
- 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
- uPD8s0+9ceL84szID5/84ZsnzlFO3zjyzNPPPj+/XL2eZ1C/dOrcaBh4lK22+MKl7zz54rUZ
- gZxRz2/0aG/h/Qf58pe/DADA3D/+D388cNtdLAhYcfT0HIj5UxO+cezF53bv6PnekRMP3nsw
- qBReeeW14cm5jq7OmaHTx04PtbU2vfP22yPjU509vQSJiUun3n7vQnNz6rUjLzpCh8r8G8fe
- mS8G2Ctcmq2++r0/PZtXutPspdfetnIdGUuHYPHrf/s0FaLJJiNzZUtUxgteSzYVeeXXXnmp
- GKkmr7z8yhtqumV65JyRa750+kylvHT65MlFVxQuvf6X33qhs6P5zKlTY9MzVY+l1WB4utTa
- vJXK/b6izoEq80HTrkO7u57+1ncWgqi1s++hBw7NzkyFlaWjr7974OB+DOAuz5y4MB0tnP/W
- d77/J3/6tTdffOrvnjzy9DPP5zp6NIKq+ZFvPvlqS9b0/Whq/MJf/NnXLp57d8FXTr34/ZGF
- 4ujEvEJIR1cnp/6F99786reekY9VDdsOZ7790ntHnn7q6DM/9LEBAM888bUSyQB1v/7Vr6Wb
- 0098/YlXXzpa9PzXXnr5vdePzgfKCz98wmXETDVDdeboG+cGtvW+fuRHL73woxLdksjvN+ru
- 6kx3tnLpyaOWbZkY0PCZ4/MnF+/7mV8sDZ/5R//9/2jVPZputTSPmd3brWmK2dRz3x2D87Oz
- +3f3YwBMFB4407PzhYmh8zNliEKPiuLivOfTlpYmPOK1tDQvTE0/c/wtR5DAqcU1A7cyM0+y
- PYP79OWnL7o/2Z0BAF1Xp+dmLUIMjcxMT2PNTGvOi88+PbXktmazO/YcCKZOpnPNXnm45LR2
- 9w9u69t2z+7mb746/eXP3sh9sbZwJVjxREeBMzQ0mm7tajLEyMRsS0dvV1t2cmKyp69fbpy9
- NHHya0+99/ijnxgc2FZcmJrJl7fvGFhcXNq2Tbr5xeLc1Hwp2N7XOTE+qRnGzOm3pknnA3fd
- 1tNizS66LSk8MrnY05WdXSgbpj6wrQ+YPzQ8EYG6e1f3U1/9C7L78Gfu3wsAPPIvDQ2nWrpb
- bTw8Nt07MKjSyuh0XtfNtEmMXIezNNfc1jE1OmxlmzjSelrs537wzXL24OcfueumdeRHFVcR
- yoi8Sr4cdndsHpKUKBXmuJFrShlXUvfoyGz/QN81rohl0djUbG9fn4Kv6fYtXAfqBCT4my8f
- 8dXsQx87RDYYBhbOLVbc/ITVu7czK/MPxbFXjiA9pds6x00dLXZH23oUJtiLL7/+8CceKubn
- 9KZOcyXQK14+dv4nDu19/cRFjODe2/d8NHYX/vCgNpJ0eWTKz37+44fefvP17Z250yPzjhOY
- JgEznYZgqRw0ZbXlktvWknvl2NAdO3K8UC60NrvcvnNn+rVjF+8+0E+ZPrsw++Lc/OM/cefo
- 9FxrS9Pk1NK27lyhWGrv2pafGjs9Mvvwxx98+anvNt31SDQ7So3so5+4n7nFF1871bp9YGp+
- SUHXuuxiCzcPtfmO9XRQWSwUCnMz04X87NT0VGbbfi1yD96x/8SxdwH4Ur44MNA7Mb3Q0dOT
- 1khnb/d3vvGt9q5OzUp19/Q2aVDyQtXM9HR3j5497kYicEtm67bi1IVqCKXpIVdvb0rpgHBz
- c1NXkzLvG4vTYwLgxHtDew4MHD81xDlnclnnFm4prOhA+enRmRLrb7dGJuZaWlvtbCv4pVRL
- u5OfWnBQe0bTTLPihu7ivJZKlxYmjo04X/zFn8TApqfzaR0CpIShcIrzTW0d09Pzuwb7qj5u
- tsSZC2N7b9s/fuGUT9J37Bt0S/nReccGB1Id27tbp+YXe9qbp+eXIsYIQl2dreqWHnNLYSsf
- aAvXhZoOFEWR5zXmjW9hC5uiRkC+76dSt+qW29VqNQxDuWwAY6zreqVSka/jOA4AUEpTqZTv
- +/KjZpqmyRsxxmEY2rYdr3gKw7BSqWCMMca8rpMhdFk+jeofA0wWU1VV07S1yxg2hrj679Qm
- QSlljPm+zzlPpVKqukk6qxCCUso59zxP7m+JEJIrikzTvNwSvxjx2qMVe/oW/RaEEELXdUII
- 5zwMQwBACKmqWqlUDMOQJCJLGobBOXddV5aPoogxZllWcvWgpmm5XE6u6xNCOI4T397QOfF4
- x10ZE1kYhopydRsErq3/quC6ru/7AKCqajqd3mBdomxwGIau60qKke+CMY6nQRRFpmluUAOl
- tFqtykVkt/xOZAghTdNUVQ3DUA6267ryEmNM0zRN06rVahRFQRAYhiGEKJfL8XgTQpLr5RBC
- iqLEx6ZphmEYb0oKiWm2lp7iA4QQpTSKok3ZwPVDCCGXBRNCbNu+HOFSShVFEUKEYSi5cvw6
- GGO5FpYxdoVEzOuADwEBQZ0HaJqmKIrneWEYWpaFMY57M51OA0AURRhjQoj8utO6dNAAQohp
- mo7jEELWSrEkScXHUKfCDVZ5Xg7XJsWCIEAIGYaxAb3KWUEplRsyJ1mUaZqMMVVVS6USAGCM
- LcvauBmqqhqG0SjCblFUKhU51wkhhmEYhmGaZnIVtxwVjLFt25zzdDotxzse7A06S0pDSQrJ
- uxog+VOSjBBCG6wzvxyugYAopZRS0zQ3VbliPpqUtrGwrlQqspdM06SUbsw7OeeymBKz61sX
- UniHYSg7Qs4eIYTUKFOplOu6UngFQcA5lzPVcRzZBbKGDUQ+IUQq1LCGXSUpJsmBIKGnX+27
- XFV5IYTneQghXdeTbRNCcM6lXij7R2rKaz+MLJVC+XYYY8MwPM9TVVWqAVJZ9H1fCKGqakwt
- kuwYY67r3toEJISIoigePKkDycGTSiKlNMk/pMZdqVQIIa7rxls+EEIuN4Ol1NvACmvQjQDA
- tu1rkF/XAMl+5DAnz8vJY1mWFO5hGEoVO4Z8cUleST1a0lk8W6RoC4JAzrR0Oi37Vr6pqqq3
- vAhjjMk+kj3IOQ+CQHIjyZBjqpKWkbRO5eyEurIiTVlN2+g7c0nhwjlHCDMhOAeMQUls1i6E
- iGv+Mb96zQ6H9bY9cF03iiJN06TBJfsnyX4a+CXUbfiGk2EYSgVr7etI1hVF0fsd+5bC5UbV
- JrtG8vD6VnRC8mrGWBiGGGPpEJLsV3J1ObdkDWEYSg+K67rrNkxWGPegpKSQCS+CgIEXQUBX
- Madk4R8rpP0Fa4hVvr7kvpICJDOWV+VBPN/WVovqSBJZrDxJ34fsMdldNyF5wnGcy0kEzrnv
- +47jSENp06qk7WrbdtIGid8Z6pw5nU4rikIp9X1fUZRYJCX1St/3K5XK2ucihFKplJzldT6E
- ogSlRay+vaUQ0u6LosZPFl/h60C9HjnwG8806WUAAKmjxOdjnUZSkhSmsSiPH5H0/axtQHyQ
- ZD++7wdBUCqVSqVSpVK5aWa8tIPWnpdf/ZGzRzIJjLGmaXK6NGgVcgZIapDcJR5jqRXJR1Sr
- VekdkYMRhqFpmlJsSUezlHTygHNeLpfXunE551EUrcxFQMlel8dJ9Xmt/bWxZ2+lKiE4547j
- UEplkzKZzOUMHem5kEpMGIaxtShZshSm0m6QP5OO9XUf3XCc5EMSsZ82yY/fbwK6XD8yxiqV
- Stxc6ZKXjEH2vvQ9YIyl5uj7vjwpy0vqkU6/5IMkG1dVVZKjFKCWZem6Xq1WJcPAGMdMXggh
- uVSynZJ/xK42BKBgoHX2r+CVfVNjor/Ct04iJp1YBGOMq9WqZVlSXW0oDAAYY9kDURTJ14lF
- uXz35C0NBvzlsLap66pHMd5vAootzORJaSaslR3xLdJREYahpmlBEMRTJDYugiCQTmepGRBC
- YjmFMZa7OEob1fM8abdLvpLNZqMo0nVdTi+x3lZ8hmFomua6bjwkOgGMgHKhYKQRiCewqqq2
- bV+DDiQd5UEQlMtl0zSFELlcToqJarUae5mhrmMFQSA9pTHvxBhLEWOapqR1yZtjpRNdxoEe
- n2/o/wbX6Lpl4KaIsEqlYtt2UkxEUbRWb1gLqSHFZAGJV5J6AwBIfQgApMM+lUpJBibrlwex
- QJHcXlEUyaVkGcnhkkxIDlgYBlHEFAVHEVNVFYtIx7UwGmNCVYhp2QgBuiYHdBRFFbdS9kq2
- leKMa5qWnFGSPVuWFZ83TVMqi2vNK8lNpQ7A1+ToSW9qFEVyMiT1RVgjyJI90GBMxOdvAgFx
- zmW0XNIQQkh6LOT4xVotrOG6SfbQ0C9SFUAIeZ4X3yV1mrgqabNAQk+KOYoQwrbtSqUi7Qs5
- WrF3TlJnpbTMQLVMjUah7/tccEXVU5bBaLRc8VvTWtUBrGg5TRNCRDyiIjKIeYUfeR2qXHhl
- +mggPAOb++yD+7XbGyLkUsBJK1I6QqV9TilNDmo8neTESE7Lmv1Yjxgme3itzZUkrPjqWqX7
- JuhAcbOkrzOVSslOkdInOaKo7vqD1eIMrY6BS8mlKIq0V2O3GNR7P/bWJ/tFahhSc5Lx1CAI
- VFW1LEsaibJ5kpnJqgzDKFd8lLIhDFUFBRGmUYjA8P1QUzBjrOJWe3q6KYuO5988u3Qq4mGL
- 0fZwz6fazI6NO2TemX1u8ikmKELIYdVjpdebrZYecxsAxNFc2TOSXOLXhDUiCdXjLTFDhQRZ
- yJnT0BsNZSDB3ZPjBZfxUNwEApIqrewLaW5I9TnmLknOufanRPySsSIZWweGYUh3haxT2rqS
- M8UTTt4lIxjyfBiGnPNYeEmhFj9Xyj4hIHArARUp2yLUx6pGFKWtrXVxqaioSldzU6lYvBSc
- eWP+JdnpFb9cCou/svvXTcXaoEMuFs9THq28HYJRZ7jH3CaEkII4l8vJsZfe8427VxZoUAni
- d19X6alE5aWw0KF36aRxDVaDUfKBEGGpVIoxVq1WdV2X+RWSCcUFGszIhrnSwFelizmKIqlQ
- y7HXNC1mZrKAzPeIO1Fq1vK853nS3JVJZ1EUSW0jqembpklp1rQFxjgNIITIpNOpVEpe7emp
- 0Yfakrl0/lxyjMtBcaw8vK/54OV6gzEW1VOO6qKHLIWFc+XTGZTtVnsQwtIpGoszkXDhJHsj
- /ruuFt+g7sTsJODBD2e+7dBqv73jkY6fRGu2RU+iYSzg/edAMQPIZrPSGYMQSqfTUqxUq9UG
- adXQL0l9KJZl1WoVEtEuqWvHxQBAqpmWZUkFU9d1qV1KH4FMJwKAarUqawiCoGFTbUIIxkR+
- fgfqHvC1b8cEZ0DjkZaBWI9555ZPX1w+hxDa33RwZ3a3bPmSv3h68US+uuCGLgOmgNxRHxX8
- QhVVF4M8CDgY3XWo+X6ZbSx5tljPGm/gE+t2ewOpSRYuhAhZ4DMPAMpRSU7mZOVrxVwDblos
- TL6ADNlIub6uIZacakkZlKQk+dqapkVRxDmX3kXpKAIA27bL5bJUI4QQMuVDqqKy/viTBlIB
- ktZyw1STClOcNi51r7Wt1bDWaXVXwrJUvBBCBJEFd/5C8YwsMFYefrTvp/Y1HZiqjv9g7Nte
- 6EqKpyJCgEzVDLivCMXSbOnxOlV8d2dmd7vaGXtHk6+/lpgadGFYw3hgTQZLWk3f0/LgtDtx
- IHunqqiZTMZxnNigSdawbrU3cx2oHBUpdGLvTozLTaZYsxZ1w1ISme/7uq6n02nDNBlbEYiV
- SiW+0bIsqfdIH4lMeI2iqFKpyBB9JpOReUVrn56MtkoaXds2hNBDXYdbzXZJQAQpd7d9bMaZ
- XGk8iOMLbwLAc5NPuYETm9kmsWw19Uu7f+2OlntsJYXqrmQBosLLMkclHvgNqCTG2h5L0o0Q
- wncqheUSZ9HCQmF/5o7Hu37GcnU/YpVqJfRdx2scjnU7Hz44GYlrBwyh2udO1uWoDdINAFRV
- VRS15AauF4AQCgGdAEK1EJV0/8RWFQBIZ6OiKFLvwRg7jmNZlvSUrG2hzE6ULvINksWyetMv
- DP7Xk5Vxn7kdVndWy75XOBZfFUJ4zAWAUliE1UQQ8kCA6LS7TxXejQdJU7UUSTdwGlR3UqzV
- hJKUBKslV7KRQojvfOsrS8Gee/ebU3NL93zqsz1o5htPHmttyS7mL21rbt9z/6d3GFqyPKwm
- 1rjCm78SXXru5ZDE8kiaQqqqplIp27Z1XccYSzaefBlIvEkYhgtLJcf1ZYmIQchqRqkUN3G0
- KAlUz/oAAEqp1KalC25tUw3DyGaz0jG9wRvpRB/M7T7Qcmeb2a5gtdte9fHQ/tTA2lvke3mB
- t6dpf5fdUw5LpbDIBNud3deqtSeDPFCPY8QDGQe/JD9LSjpYExCNL9115+3l+bnTl85bJBie
- K/qlQsf+e6vF+alzx0fL2vb2LKwnvNbytptPQDIJN5vNxolRcshN08xkMjIElkql2trabNuO
- XajrMFiEZHwq1l7lT5nuIxXbdePbSXkvK/c873IGs1SVrjyTEyN8uOexDrMLA8aAe1LbPt79
- CACYih3yoBQVS1GRIYYx1rHRYXfqRG83OzFgIURayR5KP8AZF4lMS1ij2FqWldSLIWFPNBxL
- SPobunjJD7xde+8olqpqsFTRO+beftbMdQ/effhTu63XTk8kaQ7qMy1ZiVQoa22KF1JdYb/8
- OCBbUq1WJUNam9cnhJDBLEppMtO0BoSc1ZsmEgydLdlkNqeu62szxqMoKpfL8c/Yqb1BJPxq
- wQSbK88GftCZ6bJMSwjx7OQPn514Mh7XnNr8s9s/f3vb3Qihb13667NLp6ig3Wbvz/f9KkEK
- ABiGIT1VDVwBAKTuX++DVYnbawlO1M1SQlilAul0YxpdA2tfy+njk3IJ0QdFB4K6C0f6Yxo8
- 9HEB6SSU9pRI+MQAQAihYojqTEgIoWCQwW25skIIIdPHdF2PwxTSapPKRINSFUXRDSEgIUTA
- gkK4cGnpvFrU7u65t9feNlK61G52lMMS5ZQgRSf6QHoXALieSyO2FC0SIEwwVI+ESKMyaX/F
- 9TuOg+o+97VXYT0yIoQgpGQyEFshcoI1dGl879oghqIotUy96++gG4LYeZPJZFBicVYDZIYh
- ALiuK5Mcki+mKQBUUI4wAlVBBNUiEtJRBHV9K04ljoPVSW4vx0mmFcuEm+thzEKI9xbf+dHE
- k3PurBDCJNa56qmf3PZZh1Y5iIyaU1DN/eOBm8XZkfKlS9VzBIiCFCKUIAwsfZUXWzZGGrDS
- wRifj6lHutE30PST0lDansvLy0II6YBtmJzJn1CfcrF8uPkEJOoOZZnus2l5aQ1BXV9BCJxq
- RTdtGvpY1XWFoDDUVZ3RyPVD0zIYFQRxUHSCVrhxnOCh67r0/cTRaUVRLMsKgsD3/VKpJIO+
- 10xDs+70i9M/ynsLnDMOohgtVWn5axf+jPIIEOKcG4qZUlIZLYsjnC8tjBVHNdBNbIYQmprp
- BFVTW7ViRI6lYRgy1SkmBTkroJ4k5Hle7GtYu0YnKZhiNzcAmKaZ9HrEZRBCcn2LzGyMORbG
- +KYRUEzUsk1JA3tTUEoNw5CCP/JdL+S6wRVVrToOsfTF5XJ3ZzsAKIg7rgccDDtla4qMIyYn
- XxRFkmqTQX4ZFYm1jWq1ms1mr2GRl8Rw6RIXnHEGgKgIAYAJRqnLgEuiiKLQp16G5JacxWfn
- ngy4Dwj67IGlsLAY5N9jxw9bj63lx7HiH1tYcj7EJW3bltkHDfxj3ZkgM+elgz4+KdXk2OyI
- VUPJ4STbVlX1Jlhh0uCM27oxs10XpmlqmmbbNgBgRVOJcP0QAwjOS+UqAk4Zx0ThggvOAt8X
- nMugW9LKhfoSXWm9y5rjDkqaZuua9FcIBIAAaVgT9TAIF5xD4vvdAFSEs/7UkYVnAu5jhNMk
- 8+m2n8mRJs55Rs1Kz0KyTtlC13WlfRC/VEM3SqtWVdVYKq1tnmS0MqUOAKrVauwjQPWgW+xD
- gYREk8UYYzeBAyXdEtdcQ6IqoBEzdeL6vqrrtmm4rgucRpRHTGiGoSiGhqgf0vgWQkgcapUc
- G61x08lHSF37elo7mN3zTv7tFqtt3p2N6PpJcwKgTEsXKmc1rGXV3C57X0bPfr7/lx1aTSkZ
- lzrggUxZgTpNy0iwNA7iBSdruyjWiqR+KT3scUxN3mIYhowjxSQo6l5KyekBIPbEQmL4pCH8
- ATLjrxZCCLnq9HLZjLz+GWL5jtKNBAAy3LPuMk0AkLFVIYSclKVSSeYJXZsUE0KcXT710vTz
- AfML/gICVApLEQ9rqfgJPoQAMOB2rfPxzs/tzu2VJ1+Yf3rcGfnp7s83K61yoa20A6T4SHLT
- mMdgBAqtghBUTQFaabNlWQsLC/K9pAIeRVGc9gqJtQOSvCilcZhZSnaZthVbZNKbevOV6GuG
- 9KHJuZg8H1vjccow1DMS40wPXdcl5SW1n3iVNOdcGoMy9TiKIrmc49oaub/p4O7cvmKwrAr1
- 9ZmXn575Xkw0SaEiADjwxSg/VDnXbw4sVRbzaD6nNlf1ikksyR544ovgSX0OEowB8xDzCAAw
- C7liJlsiyUVKvTifTgiBEQgBUg2ShRtcGAgh2W+SjCRj1nWdUnoLExDUPUOSPze4vOLFOklz
- VAZTGWO6rksNWvZIXEBSjCQdhJCqqnKyXpcahJCK1DazXQih6VpayUThIr/sjrRoIZp7Ov+9
- clhyI+eXBn/truZ7NVUDgDgsKEWwVE1kopxsp/RWcKRwpAAIotuWaUm3he/7i4uLcV4Ury9q
- RghhEEpQFIBDNRNPNimz5MIp2Sx5khAil2uqqiq1q1ubgGC9oHTykmTsTABGCCMkU46kI0Aa
- 54ZhFItFyWZkf0nlQMb2JTHdKH80QogKGokI1ggveSwAFERc5rKACRCf7H18oHWn3DEtubIC
- AJqbmxFCMoFJ7gcVp68AIKpmAEAjijS8K5WK9E1AndFKUSXTiAWAQESg2i5b8ZLTWHhBnb1J
- m0MarbK2m2nG3yjEPRsrAUmSYhx8KgQgAKHgWnwe1Zf9ykxqwzCkViHVUjkY168+xxBCDBcv
- LroFW00NFy/6zCOIULGyaEtIGhIACEIRedR1oNqitd3WdjtCSG5uJISoVCqu62oKQZjEViTU
- 3VrxqgQZh5YKouQxSQebnB6Smcm1UIAwVTOifjXJdWINEtU3PJEqlNwUkFLqed4tT0DxtJA/
- Jc+or+0VHkVQz9GkHBACQ6n1RawzJp250k65NnVnXQgh3pp/7dXpo6Ww5NCKqPMdjDAA4qKm
- yCOoZTUxQZejpRatVSe6qqjSyyet8UwmI6iPvOWAY4fgTDZrGEalUpFOUVVVDQUQC3yqAyDH
- cVKWaaKQGNnFYjm2kJIGbGyNJqW/pD+UcEDL/pSboLW1tcU7AtY8Tzeqp24W1oYa4iArX5Pe
- S3ltksn4kUgEvzRN23RnrmsAE+z88mlA4NIqEwwACCIEKa1mWyksCSF85iFAJjFNYheCeSo4
- AnC506S3uBU3ZMHrhZcD5n2s9eM5rYkzrgHTEUduIRSeYrdKs0gVAY88FmGNABJouVjknBua
- KjD1POdy9nVSv4kPYv6N6llWkkDj5AgAEIJzGgoWKZp9yxOQ1Arjn0kXJUJotaGziqCS/lkA
- iPc1E0JQxgWAgjG+7l3PMcJpLVNw8wIEQiitZD3mMGA5vfnndvyihox3Cm/Nu7MCeJPaSstR
- OSymlEyL2WpiEwSMVIfOl09hROxi6sHWw4hoEdIAuCI4gFAUuckGwzxQMA+Q5gpFRqMZDTEP
- mJkjGMNmefLJA1GPniKE5Cpby7IqpWXCPPAogwzChFYKnEVAKeP+LU9AcrFzvPlBUpwREBgB
- 4yuEohKA9XKs4lSkMKIlx49oTbewDTVt6dfDljDCh3s+zfhTTLBqVEmr6ayRbdZbf277L5rE
- iqLoZ3u/cHT+R+eWThVh0VKtlJpWQMEIt9udS+Hi8eU3CkEeAaL2bVLNjyJi2xmEEVUNzHgU
- RRiTgOtYUFANHoa1rYwJ0gV1Xce00xs0XwghTXhI5P3ElCTFlmVZuvBo5DERCRoAYMEpYwyE
- gOjW14GkyhJ7llEi1IUQMlXwI2ACEIBKQEZu1g3ZIoQiyhbLXnJGVr1QCMjY10VDzUbLFwZ/
- 2Y/80/Mnp72JNMnsz9zBfVEV1cCpOqx8sXgGY8wE07GR05tDFvTZ/fd2P/D0+PcZou1GB2d0
- wRnH1l2gWZqd8jzPtm0/CP0gVAgRAEi3PM8Tvh/vT+37ng+qQpRKpSJPri/FqIt5wJQUw2r8
- 1skClmUhwZGoRQY5Y4KHAlbSFm55AoJ6bornebFzeYWMAAxFAEKobizH5BU77A3DkEZp1Qsb
- ug8AHD+0TVW51mCqhODAAr7T2j1gDHLOVVIbLc45AizNMIRQh9n1hZ2/LAA0rGGEHVoFABWw
- ECRkHg9dTbeQ5xmc4cAHQoBxHAZc14VuSAuAUBdRHiATADHOiVLbqfhyEwABRyAAeGx5rE07
- RJhgVVcEF0JwLuTcjP3yHwYCkiowQmhpaQkAAq/q+tROpyF0uWoT5nNipEzN8xzH8XRdVXQ7
- 8P1M2pb+jHgNRhjRdesPKb8eAhJCVCsV36liRY3qOz7J84pumFS5I3vfuepJU7U+3v1I4IQI
- IVBBVdWdmd0LlWnBuQDRpXZqxBQCSBjoALy+0QwAIC5EnPOFcBTRUIRy+xG5O8wGG1VRxQZu
- AFYaNEKozzTf923bVtNtzCtHUYhVSxEh9ytx338YCEgiznLiNEQhVzBUgohgpmAkBABChmlG
- DGHuLxdL3d2dWHDG2LoreBpw/YZZFPicUkYZMQxCiOBcCI6JggkRAAet0YvGZAAAFWJJREFU
- O/dk9iHAlrAoo0IIx3F0XR/U91KrPONPNeHM7eZ+gnHEuQAEGAHnSAgQQqgqr2f4M8YYKBxj
- U9MQQowxuTkJxth1qqZhIJL40F+N1yIk2eF6S8xitoQwUewmUr/KiMpDDyFMzNSHh4CkvVCt
- VjFGBIEfhIRgxigiteCfQQRp7RTOfKatxa06nR0t5XI5CEJp7euaomuK6zfGZRGAqlwL+6n7
- orjneRHjWFWJomJFEUJQ3xOMqXZKJrIhhDSkRZ7regHWNKLW8pQx4AOZgwfNvcAYCCEwIRgB
- RoJzzHnNxIworlZ4Ki2dF6qiYCy4EBGlGOM43q5QBwVFByxCCOYRIgrlID8wEq93a6AeXN/G
- FQAYjYKIGbrqB6FhGAyp2LZVhdAo+PAQEMiok6qSdJaoBkIYBOdyZVgQGoZp6KqJFTA6ARNb
- VwAhRVEXyy4gjBAiGOVSZhgxmliUiBCkTH2jb4BeHjL+IPddBIQAExEHQTGG1X5zTinIkHgU
- SQISnNMwIIqqClB0nVIqDBMYQ5xj6acQAABIcEQFqlZFKhUxZmgKCd0ANEAr2zorigLY4oxi
- hsrF5VZTIKFxJS1DGZAw4DHGIBjmERKcI0IUXaZzlIuFigetLVmnXAwCizKuKjSXTS0uLX/Y
- CEgGIjIZXSYWBhQYR6qiK6qCJQMnCgBgVRVCUIGgnrXOuPDCqC1ne0HkhVRwoSrEMlSFXGNO
- tDSDpctbxh2lj0pRFKKt2EQIIZlWIbVTEvMDzgVjHGNqWZplqUL4rotdB9Y0RoDAnFkgcp2d
- CKEwsCvLy6t0NiEAqwirhgphYGhq5DAsxbe00VZEleBqsITr/nFMsp6npVIpVTcUz4kEsm2r
- WHENO+N7LmRtgA9YLKwhon5tkPsEqqoaRJSFFAAiDjF7FvUF0VEU0SiS7EEIwBjhWhgfGZqi
- 1yjn2pshP8Em9+qWz5UZPDIGJz11MleQEBKEoaobXHBEFJmLIwhBqsaEiHw/DMO2trZA7hiB
- kNRWaq8DAAKEEFBfL6sbRlNTk1xNIIQgPCDMj7SsTNhIpdIAgQi5pBuZy+D7ftogQgjNMJCk
- HgQAwMOqZrcAgEIIKLpOoOqzltY236nY6bTvRylL/2ARkNTj5LcvrnnwYiehSggCKgAwgjAM
- NFUBAM/zfN/PZDK6riuKysqu44VcCAIojOjcYsjrkkVXiaYqKXOjDcg3BSFEWkxxMob0fMqw
- f8OqWYIUGQyXwS8hBKnPJUqppuuhbkDgSwGHGBOxgi9E3MSaPU+IXEAnEOG4tn8Sxlh+zIrU
- Pw8leztnYsRDK5UmqkmjIqrvGooQkdRvWJluCwDAMEwA0LXm2sOMDxgBQT3Id0Oqsm0LEHh+
- iBFEYegAMMY8P4g4UkOWIoQQnEubrh8hEAThkuMDIIUgBP9/e1e23UZyZCNyz6oCwEWi1G23
- p///p+aM7RlLokhhKVRVbhHzkACEpsSWmovItnmfcEAQtQVyibhxL6ZCY0haCUTs/F2ML3ZN
- EcxpHABRWldKEcBUirUuhFCTe8wE+0Tw4doP4h6wH5XHcVwsFjnnyleinHRMIsUdE0yKz1Mb
- 83azjKSgxH47tW1LUvSbTdPOtVY5jCRtbWatvQPOGsXbkVXrTxkAlYOyY4gLv5iGjWtmKG59
- Is8ugOALeu+dIYSYdV3jSy0HMnNMeYgspFxuRonsnRWIRktm0Xqz3kLKhY98ZHOmlD97Q30T
- zDT2vRDSNg0RTeOIiELpyjiFA914L8dGpeRxkNZRlRGOQUh1YHtxycCMSgPAsFnrvRkKAigi
- 7joAkOPAREJK1bb7s6BN31t3EqZgBI0xe5FAiEKwvP6g3PztRVtvsnNuHIZGJSEUoL26ur64
- uNDnv5RhySUJ2wJgXL7PUHR3ftslP8cAeljUak79zRUYMY6ZuFC+vPr0y89vlBTni6YUirlI
- gVT3/AwAwAAM0I+hsdqa77pROaWx7xHROJdzhpwYIElV5ykpZUrEYkfqq/1MqHTKWSqFCJQz
- APJha1ZZStqUUoCh32xmSllrISdFZLzX3gOd0DShUmLPRmXApvHr5QqtlFQAgIida4GJGIEL
- AwPv+gWGbS/KRIVKESGVYRjm87meva5fRTmidgWVuv0n9O8fQAcgYudtSCWkzMxMZtlPJzNv
- tWLF620opSghamWRaFeTJ+Ixpu8MIKW0bRohJAohhNi1oTNEAO/9OI5GqRJDIDbGGGtLKaiU
- IKpPTvmGiHjf1i2tq6QlZnZdBwCr5XKxWNjZDE+hrtVACLVX2ttdJiAw66Y5afXlp5WVLKVj
- Itt23llvcAi5MTtOj3UeWVMM8zyMXwz8KLWev6nrpNsESf6DAggAlJKvT7sQ8xTTcjOuhymV
- ctp5YEopEO3Sr0Lst7UAiGD1994lFKJbnNTXxpjFyelmuTTAOae+L0KImDMR5ZSc1ogohcAj
- r0ncs9sq7xb2yaTdw2PGnIZ+k/c69ovF4isDA+Li9PUCAADemt1G/Vj+/LhNlZmb2QkAD9f/
- apRvjphDNW6qL1bd2H69HPudt+bfBgLRW03MWsmYyjSld1PorLAKt6FUwSQqXOsEUuC8de77
- hp8v0S0WDLC+vspE1luNmJnPLt68++c/hBQlRYpROF/7+uq2SEkBgCzljrDMXOc7yrnEoKwD
- IYZhEELcZjlyjErSRS7T6oObnaG82eNct4TOOXfydqczRKVMG84JldFuVheDtVH6q4f7E/eF
- 3QfMPIT0aTMigBSgIRfeSS8wUUHDAIVYCuy8RYHeGm/vklkopWzX676qx5dMgAqBmZrF6RRC
- GMfC7J3LpdRfeR4HFKKgAICDGGhKyRpDJaNUNXt0cJe+9QJLzuNKKCNsl/rrMq707Fz5xfEd
- qPmC648fvCxaKWG8sG1aX9JeudF0p6o9qx1ktxUNn1cAEREVlupeghjfj5hKzFkhrDb9lAEA
- Gg05xYw65aIEZgIGFAgC8eKs83+kgf8YBxPn2kptrBVSTtM09n0Yh0orkdrknKUUdW3zmZZE
- 5Lyv+aEDJb7msX7niKm/omkDgPr0ZwSkOAjbHu/Ga8mWiHS4pjggotBWaMc5ElH1G9HW2dO/
- 4lFz5pfP5XlNYf1qyLHMzzqlH2Yn//swWmolPn78SLy7RyGmFCbnETg31m+2IwvFLBAhhuBu
- oWV9E0od2dPuV6NaqW2KtQqGzAWTUvo4g1olpAUTU9FKIQKiqA0637TYFdIQAAqFKFEI6ec3
- PoCIbdtu1itK0+GSShhLbVLYp6EKlTrK3fAP+Xxpd7gd9wEzh0zulvjQRgOgkI81/NxoWwGA
- vt9OiXIcpTbO6Djl+XyeUmoaj4iz1nNl2SAAwDRNVQriQQbIYbNOMe6KLESYs9gbCdbHl1Ly
- TcM5gxApTCUl282qed7xFdUXN05JuE4bV6Pnd86h8X4aJAMopQGQgQtIiQWBAaFIX2JCzIfG
- oDoyHec5n0Bo/Eb0HNpHAMA11reW95Xqw7884NFvdAlOKReQwniNOcedCtjxlrXep0qvqQJ7
- WuvaC3HbuvI7IZUy1obqDiMlA4jffmEOITHNzs7W6w0Iaby+ET1USr9aUinNbKatoxzfffjY
- LU5nTn1c9hevTjer5TDFrnGsfBz6k7OzY2IB1gHNtXna5kJSojQugBdhBRTd/M1YsMRYG/Lr
- Hai6W7UxvNqTPcEIlAn00Rhz+f4fIbs3r2f/++7yv/72NwT68M//cee/DMsPzijTnbTuLpWE
- 7zyZMA4IyhqpUNfdzZefuSHwdhB/qbPJnQekZjZvutk0jh/evwNA3zRi/1X1cN18nmMYhkEK
- LAS+bW8kY8a+TyEAwHa1Wry2zCVnMlqtlp9CBADs5idcLqcwffzXx7/++uuXtBQhpF28AfyY
- pkG6TrenRkiieUlJWieGAX/rrFC7datSYAhhGMYn0Ae6wbBs25apCO06X5eouFjMASAM623G
- xt7ceT4gEHE+6xqDWuy4m5UcDUdiFzeC4/gB55zX6/XvO5t+8wxc03SLE2Nt7dI/Nl2USmnn
- KaU8jrW8+vtfxszWudWn65hqtpLHfpVl02hx9up8vVwetQt8BqCwiwt3/teAboophEDE2joA
- cM7VJtRaiK0kAqXUbDarmX1KT2F5eSOtMgxDSUx1JZBjRlVLwbZdzJxYbaeT7sH6RL9EFfSE
- 37ZzHP7K+6IVHhmdwFH/631EFw6oSllVPBSP2h1rVw0IKa07UHaO4buOqFApfjYTQqDxRg7t
- 4pVVYh5iDIEBuUT03UJbShMBSABguHz/9yn5sxPbr9faNSElbZrzk/by/QeQKk7btm1DFuen
- s1pviduVazqhLTPXObTW/FPMz2EbTymx/iHbrq+iSn0BwDiFklMVQKkP9Tj9mgrFggLBKjhZ
- LKq/qff+MGjdGaWU1WpFRIjADNUCpuqHaK3rmqPmbM7Pz5kI76f7Cczb/urqU/7LL2/WV5dS
- qT7kkvmXv5z+/b/fK6u5TIHUr3/7S8lZSllyKuv3yneyOS059f22mc3T2AvTaPl0pPqcyrAZ
- jdPWmydR2jvAORdCGCIVYVGCtaaul6uBy+FjiZAYiEERVPeWqoZ+/xOok2CJgVISxlYa/DiO
- O14UUQpBSCmFiNPULz/Nz85RyspQu8PhGGAYxpL46sP/TVm+8kbF5LomBPaNjoWlMj8t5h+v
- V6/PTxAAtVFnPwPKlNLq+nK5jWc5FjQ8XDv/dL3xKeacCjPYW9g2OeUYstJSm3ttdr6Jyuoa
- 4gT7uanu1Q86OvVjEqEAIIDcka3wezo6vgdVoRF3E2Ux1nHJ2rnqPwRMyACZEhU5nzezOUrZ
- 931V6LnD4RDh1cXPi1PQeieh5PxOFODVxWdzRXf00xjGsFPuFkrAOE7Rto5SoUJPFkDWG2DW
- t6yRcyrr6536ervw9tE2YhXVvWA7jEwAiGPayS1UAZRa1zQSlWBEQLhpHnBPVPopSCWVhuoP
- HAMqxUICACpdiKpLlZSyaF3Hv3sMfogo69h6KOLe9lEiqjqC+zc452KdnLYrZZsY4xP6haHv
- bq0MpPi5vSaF/NgBhIhVd/zdu3famExCIBCwUupgBIMIcn+jiXYlhQc5ehV9ZqZSSgwJABjQ
- KqWlJKKUi2+axWJxWLlz9VmqmptE2/W6lNzM5vp3LWDuhgMXr47EUplXr14BAHNl+j6Q9tYf
- AjOHKcYptzMnb2m5UloB7KJe37UY/kchhFDGpVIaDbUcj18z/oV9/9BDTaxN06w+fcrTCIha
- aSRihJQS5FxyFtbNZrNDsBJRDlNNBwNAinEatgAw4kaf3cob/KMopVTJiuMtKjNQZGZABKF3
- vOmnGYHCEEumGLK/NYBkd9KkkJRWxj1iKug3ZxVzyJALpJIVJ601CzXE0mDWR1I6NRdSjese
- 5LhKKSlFBmAiyAl2YquYAFGq2hJ0rJYqjT0cXUhZs3xKP9jwU/Nb9fVRYY4owi7dglgiS4uI
- TxRAzcynmK3/TWT0q+s+8MXr09VytTg5BZpIGevN8vojS6uQnPfDmObz9pFW1JWCCABCSG90
- KTmkIpROOap9jth7X9fOD1tg0dbGEICZATknABBKWetqylsc+ThVrVa75wVIpRavXhORutOC
- +quoZf8b+TCgffTs3gIujOoptvGIqI36cmIKcQRoSorDMM4X809XV9i9njWm77fNXI/D+nq1
- /fnnt4+3H3NWCWQWaBUCsJTScilccswxQjUSeMCB5xhKaWVddZxR2gghlNZcSplG99tsQtd1
- TGW5vGbhnKQx8+nJqVYwbdcZNRVSUKRv7Z3asSvqYut44t5f8ldS809vOHeAti4P2yyMNSoM
- fSKMIQLzq4u3FMdM2Dg1hq9rij8IqBTFqTMod717LKUQwIfy6kPV4b9EnacqNdFYq42pxAHx
- pcgaokAYpyCF7DebXa9HyZ+urscQry/f9ZHN/Vb3tYXteP0HAEJ+HnRrZaO+84z4QEYp3TRW
- IrSd9c1P1kWCKaQcR9PMG2Tr/TiMDPaRBqGdHM5RtauSlL33Nbn3gOKbN1A5Yjvnr5x35Oic
- 5mfnzRGduYIBrPPDZrU4f03TZoxFxE1moJiUcZQmgrm8h6ZIzYNXpezPdwNBGiwJgIGBhd6t
- rJ9RALlm4RoAAF+LA9ruHpf7TJ7quvYr//lAOPzaEkEqbCQKJqHtNpI34mx200HxYVFtxXMI
- lCIAaOu0c03XfXlQRKkFiG4mKEW0rSiiO/nJ+UjI1BrBMRV/v63rQXny8EMSQtjGllJSTLQX
- LAN+ZozEp0UdgYg5JGCAAGAFZJYoMLOQ8nHvVf25475227Ttl2PP4aOL01f15ech0XyuKtx/
- OX1QoB76VSjy4nyx3Q5Sm2noSTor5Wp5jdIApZcA2qHukGEnvwLAIBCkFBoEA1qrH7vQjIjz
- +ZyZKWcUQj1cnukOqGs+Y4yWtNxk387ytCWQnberAG3bbFbLlBJyekaL6KdFSmkYBmOMQGwU
- OAVeAQK0Vpwv2tPZo2y+dmC6uvyw7gchxLbfaudKCh8vP6zXm+0Ytpt1fgjSyB9CdYOQUqac
- IadN3wttwrhNLLgkADw5PZMCGR5zUv9zYdyu++3AVDabDQEoASUFRgXMCnKIX5dPfBhQWm+D
- lGLsV9erNQO4dial0IIu37+fSEh8sse0WJyenJ8vZq313auzE2vs61fniMJ7d/H2p4s3b16m
- sB2GoS8FxnEgRiKWEoCysk1/9c9xai8uXj/eoRnQe7dZrShHSqkQC8iFlZIotcox7u00ngBC
- mto57dxOfGj/F6k0ADw/eZengnWuXF1F7VqrYi5nJ6dkRQBgRgFUBYQe6dAolOBs2/m8a/t+
- QymQFE3XCslnZw5LSMTm0TpV7omXANpBoBBaL2bdMIxNgwSotAcUi9Oz1pshRN3csavw20Bx
- 8fan+rJ68wBANaFoNQA8LhPhnngJoB1cM2/8IKWaz+fee2c0gFYAzswBYPaD6rl/PrwsogH2
- 7IVDveIPeZD/h+PHB9DOt+xe3TAPjUPdh5mrF91Tn9GfBk8wAn14/2H16eMYnlEAVTnwyhGr
- jadPfUb3wpdW84+HJwigHIf1kFv/jJYVB74fIh7bzj0s9/mH4SA+/wPwBAGkXff2rLta9j/+
- 0LehlhFyzrWPrr5Z+eRPe2J3w8Py3b5xrGfQWPgsEGOsJsht2x4W0bfpur3ggJdd2A5Va4KZ
- h2GoVVX4ljBIbWX/IWf3fPGSB9qheodVj9nvHHWEEHdr7ft3wucprIoHPPX5vOBPBvwz7jJe
- 8HzwsgZ6wb3wEkAvuBf+Hw5YFop5YionAAAAAElFTkSuQmCC
-
-
- iVBORw0KGgoAAAANSUhEUgAAAMAAAADACAYAAABS3GwHAAAACXBIWXMAAA7EAAAOxAGVKw4b
- AAAgAElEQVR4nO29WXBk153m97tL7vuCRGIrFFCF2veNRVJcRUnNYUvd6tZ0aHqssB1W22H5
- weFwOMJjv9gPjgl3OGYe7FC4HzxLd7ul1kYptFIUd9bCqiJrRQEo1IbCjgSQ28289+Zdjh9I
- djdLpLkBmZeX+XtiBMDMLwv53XO+8/+fcyQhhKBLl88pKkDXA10+r6jv/oeu653U8R5c1+XW
- +jSu4nZayoZgWS2aFZ18OIkUTZGIBf/JTwXTV66T3zKCUa0RCro0HYWgrOC6DoXBHsrGese0
- +x0VQJIkotHop3qh+tJt5ltJBpIypg0OLgpgGQ3C0QSr6xXUgEppcY6+oVGSYZmWHEIRDsK1
- KFd1IkGZUCxBNp3iJ5e/z7K1sBGfseNYusWgNkKOFK4SRHZs9KaDKyT2PLqNW0u3KcRLrM2W
- GTnQx+3Ls1TvrTJ0ci/J8jS/m/tNpz+Cb1E//Fc+GrbZoK6HuXH7Ggt1QW+Pwp11F2t5jqAi
- IUWTOI6CGwpSXn8LRw2guBZIMq6+BokRMCtISpQvf+nRjZLlCdSwSr6YIlbvAceiWWlQHE0z
- f3MNANdxsG1BMhelXqoTjIUZODCCrWmInmSH1fubDTNAOFXAWVkgPzhMY6FCPJNmf9rF7U8j
- qzGUYADXgZZl4EgqmbCMrUTQqyXU4ACRRJ7Xf/tjenec3ChJnsE2bGYm59l3KANIxItxzEaT
- 6PYQZXOd/NEcYCIBFg7RdOCd/1OmbHanP5uJ5MVVINd1efHucxiy0WkpG0JD05iYnODYseOd
- ltLlPjxrgFsLZVCCH/7LnxUEIHVaRJf72bAp0Pvh2jV+85uzpPJZRgZ7iMaTlMsVLMtGUVTS
- 6SRCSKiKy4XL1zm4axtyJEUmEeXHZ2YpaZ7z5ieiZTRZX7pLceueTkvpch+bagCBTaWs0WhV
- uDNxhUQ0RHFkO/fu3cO2XEwL8pk0ZlAQsluMX71MzQryh3/w5GbK6gzeG2i7sMkGkOQIo6P9
- xLM5MMsEw0kSqRSZdAbHcZAkiVKpRt9AhrVyg5hikwulN1NSRwiEIvR2n/6exLMZ4NWrC7SE
- 0mkpG4Jl6qwv3qV36+5OS+lyH5s6Anwain01CHjOm58Io2ngGhpbhrtLml6jPQZwHZ792c/p
- H+xn5+gwlhSmUV4mnMwiCxfXaqE7Eql4BIB0Os33pv+jbyrB7w6y0uXuMpDXaNsIoK3O8OK9
- FarlKsK1cJBx66e4fqdET/82kCwG0wHCuTEePuGvHGAbNpV7NXp25jotpct9tMcAksRDX/wq
- 9VqNdDyMJUdQJIFkDjC0QxCNRlher9Jcus2uff6cJ3svaXUBD4fgl+7+1jeVYHh7GiRJ3SmQ
- 1/BsCD5hDhHutIgNotFoMDk5ydGjRzstpct9tN0AwnV448xpHCnI7p1bMQybG3fuMTpQIJZI
- 0TAdBvt6aH7vr7GWF9stb1NoWBaNSpX6a7/rtJQu99F2A7hOg1pTEDDnef1NDaNWJxVXufzG
- FCVd4uRTf9RuSZtOVFU5mO8GYC/SdgMoaoKRwQxTN+uM9BWwensJKg7NZIqhSIJi5tNtzPEi
- TdtmulLjUE/XBF7DsyG49tLzhAx/hGCt0WByYoJjx451WkqX+/CsAaZWJ0D1nLRPTHcVyJts
- +BRo6tJ51omyY6gXx3FYnp1ltakzPNCPEowiWjrJVIL5uzdQM1sJqRBVbAgmsBsV5lcrHD9y
- iO9P/7VvKsGWblOZrdKzozsF8hobboBEMsqLr1yhfMvk2q0Vkok8qcEhtuXXuHr9OsurVUKK
- xN59Y1y9dBlDr9GbiiFJAllWQfZfBgCB8McBF75jww3gymEePH6YdMBmcEwQi8WwhcPCSokd
- e/dRWFkmmcqQzSZBjqIZBv09WRDQ1CoEYv57SqphlZ4d2U7L6PI+eDYDvDzzPKZPKsHWu71A
- XRN4Ds9WgvuiAwi/tEMrJgTnGE6MdlpKl/vYXAMIl98+92tCiTx7tw8iB6OsrZTI5LKsrVdQ
- gwHyyShaC1qNMnNLa+zcMUY+l+X7N//GNyFYCAECpCvdVSCvsckGEKCGcBoVfvviNMVcgnq1
- xbrWJJGM4zgKiXya2uoqyWSC0u3LKPFe8jl/TRVsw+muAnmUzTWAJNGTThBO9jA8PIQUCIAD
- 9aZONBrFccF2TMxslmw6ya6x7SDLmyqpM3RXgbyKh0Pw73wTghEC13WRFX/scfYTng3BDyoF
- Ij45HbrRaDA1NcWRI0c6LaXLfXxkA7z4i78nVdzGnVKDw9uLFPsHWCqVSYYlCCXRKyV6Brei
- mmV++eI5nnzqcX7+01/w1SeOcGZymWwmzfFDe1memWamVCWmCtasCMPZEFIkjiQgJNvIkTTZ
- ZAze+L9Bm93Mz942hGHjLGqw+mynpXS5j49sAM2RuPjCy2zd1suZ126giQTPfO1LjF8+z8rS
- LLmt+5i+t87jJ3ezvnCHq+dDrK6WmLy3wtzMLeLpk7iuy83pOxx64inKNy4wcfMGC7dlnGCA
- bEQmLAtqdpCv+uxgrFhI4chwqtMyurwPHzkDTN+cxm5ZBBSHlZUy+WI/lbpOLGAh1BhCr1Pc
- todUoMXFKzeIxJPs3jHAjck7ZIoDrC3cxFLy7BnJMjl9h0QigSskWpaDFAwSVgRGvYIIpdk9
- thXzx98h4pMRoGHaTC01uibwIJ4Nweb4r4iIZqelbAiapjExMcnx4912aK/hWQMs3l1DlQIf
- /sufAQQghIss+XGJ97PNpq8CXTn3MiI5ykD+7S5PXavi2ibLNYeefBbX0NCqZexYjqFMGDmc
- JpuKcfnFGYyKP1aBjJbOQukeowM7Oy2ly31s7unQwmVi+h4iVGE5GcF1oLk+SzxdwHQlXOEw
- ffkCjpogkq1SCzq+DMFv1wGcTqvo8j5srgHcFscfehLhGLi2geEGkPvzxJNZyuUyvQP9uPpO
- YokswVgE0axQ8OHp0KFghJHu09+TeDYDTL81D44/Kqdmy2ChNNM1gQfxbCXYHqr4Zk+w1TQw
- nDL29tVOS+lyH20xgFUv8YuXznPk2DFCsouqyNRqNQqFPGdefYMDDx3BtWTM+hrJ/ADpRIQf
- 3PxbX7VDC1cgX+2uAnmNthhACoYpL9zh6psuE5NT9PUmsdwoQsj0ZhO88NJL5BM5TEMjHF3k
- i0880g5ZbcM2HaqzNfJj/mrz9gNtMYDrOOw9fJzt24YZHhkmHE9TW1mkp7fA3PwqewaytFoQ
- VCUk2R/z/vcgBK7jj+mc3/BsCH7l3gu0ZLPTUjYEIQSO46Cqno1cn1s8+xcpSodB9sc9wc1G
- gxs3bnDo8OFOS+lyHxtigPrSLe7pcbSladLpPMlEmJu3blPcupuADFHVZvzmHE984QFe/t1z
- 9A4OsVpp0ZeLYWhrhHIjBBSIyBZqLEsmEeVn5+b8dU/w4iwXVpOdltLlPjbEAK5loDVVKqV7
- LM8vorVshARzKxqhSIiw+vYcWCAhOwYzy4u4ZYO7E8so0TiyskowGiYm6gQyW3n8QX89KQOh
- CIUt3RqAF9kQAyT7thMsXWXf0Udp6QZ6q0UgEMB2XAwHkgGHpvP2W+UHthJN96Ctl8hoGbL5
- HlwBhgMJxUaJZTZCkqewWwbl5RkKW3Z1WkqX+/BsCD59fZGW8GxE+Vg0NI2Jycnu6dAexLMG
- uL5yDRHwRzcoAoQrkJTuuUBeY0MfsdOXT1NuqYhwhlxUpbR4j4ZpM7ZzN1HFRQ5FKK2sU6mW
- yaeTJNMZak2TfDLE2UvXObxr29shOBnlh7f+X99Ugi3DpjpXI7+9WwjzGhtqAKNR5/L4XRwX
- 4qk8SjhOOmBzffwy9fU18rkEVqAfa32a6fEmthIllchCTCJo21y5eB7FhyEYIXBtzw20Xdhg
- AxSHd/DM1n00dJNyuUpPLommO8SjAbAMUIPIgTh2I8rKaplYIkssFkFWXdYqTTJhGTnqv6ek
- GlbJb/dfuPcDns0Ar9570TeVYNuwqc7VyXVN4Dk8u8xSkA6BT/YEGzRpWdMMSgc7LaXLfbRl
- BBDC5dyZU0RSBYqZKEINUV5bp39wgIWFZQq5BOcuXefwnm1I4TT5dJy//PEV31SCxTtbIhXF
- s8+bzy1t+YsIt0VdFxw9McprLz7PWqVEUI7x1tVxGpUq8b5+EnKL69euUrNDvtsT/HYh7F63
- GuxB2mIAWQ4xtrXI5PQdiv39FAaGiIajNE2TSrlKobdArdEkKtsUw/7bEyyEwHXsTsvo8j54
- NgSfub6EJfyxN8AVAsexCaj+yDR+wrOT0mS+hFD9UQluNptMT09z8GA3BHuNthlAuA5vnD1D
- OtdHLASO2UBT0oQViYhiIYWSWI0yC6s1Hjh2mB/d+jv/VIJ1i/JMjVPKC52W0uU+2jcCCEG5
- NENZF6zNTpOIhNHUFKZeozeTQML17T3Baljt1gA8SvsMIMvs2nsEJZKjUYgRiiRpGhZ1w6Av
- nwEBeqNKIOa/SrBtOlTn6t1qsAfxbAh+7d5LtBR/VIIbWoOJyYluO7QH8awBGuNXCbn+CMEC
- ge0KAr68APCzTfumQK7Da6+/jG4FOHRgF8KxQEjYAqyWhRwIUlm+S7UV4eETB2j+8O+wlhfb
- Jm8zaVo2t2s19vns+lc/0NZl0Imrl+gbO86lixdxHIel6euUDIlte/fR0uqkkxCM+O82dYHA
- 8slo5jfaZwBJ4st/+A1aehMkgSWCjGwdQQAhVaJmOKQTEcyW52Zkn5qIqrI32w3AXsSzGaD+
- 2ssEW/4IwbrtcLtW65rAg3i2EnxvZxbXJ5Vgo2kwd7NJ4EBvp6V0uY+2GcC1df72ez/jn3/9
- D6hqJqqqUNF05m5NsWVkK+vVJkOFNFI4RT6T4Ee3vuebSvDbzXAuyjV/9Db5ibYZYHbiErVa
- mfNvXqRcqROKJpgvVRjKR5mcnEZVJGqlWep+bIc2HWrzdXLbulMgr9G+KVCsyH/1F9/i7t0F
- QsF1enqLFPtNkkGBo0QIKgK9XgEfXpGEEDiWP6ZzfsOzIfj12Zd9sydYCIFlWQSD/jjs1094
- NgSngxnfHIzVbDaZnrnJwQMHOi2ly320zQAvPf9rtoztxWpWEUIhpFg0lAxh9e1Tof9pO/TJ
- 40f48e3v+yYEv9sOfVp+sdNSutxH2wxQr9cJqCo3pq9gWSE0y0IEk5+PduiQSm6bD7OND2ib
- AQ4efYChgSKueRRJCqK3WjguaLpB8T3t0P5bKbFb3VUgr+LhEPwKlk9CsNbQmJzong7tRTxr
- AHPmTSKyP05ScAVYjktI7bZDe422rwK5js258xfIppIk0mlqDYOedAzNFFiNMsmeQXKpGFz4
- f0Cbbbe8TUE3HW6tNDgw1L0iyWu01QBCuKysLLM2f4sb400cNcbQQI6rhoTVqJGIBQnPLvPk
- E4+2U9amI4TAtP2xpOs32moACYl6ZZ2DDzzGwtws5dUSkewgIwkVzXRIJ2K+vCc4GlI4MNh9
- +nsR72aAyeeJCKPTUjaEZsvh5kqTA4OJTkvpch+erQTfyPT4qx26PIdTGOi0lC73sfkGcB1+
- /etfIoeTHNo1CpLD7fk6qagMAiLZHqIqGFqNpmGT7e2jJ5PkJ7f/3jeVYOEKXNvlhXH/Te8+
- 67RlBKhrGkIzeenVOXpyCe7cWqR3cJB6o4UcXyAdsHFtcIXD3MISX3zysXbIahtOy6G6UCc3
- 2i2EeY3NN4AksWvHDgglSAZBCgTp6yny3MtneeIrz5CMyFQ1nWwy/vav+zAECyFwWv6YzvkN
- z4bgU7OvYvnkYCzXFVhWi1Ao1GkpXe7DsyH4mBshIvzRP9/Um9y6NcP+ffs7LaXLfWyaAcYv
- nKIsJclHVQKKIJHro7a6QDzTw/pahXQyjGFJJMMylhxGKy8TCseIprJkkzG48O99Uwl2DRtj
- UYNStyPUa2yaAdLZDK++conRvgyWbSO0MyjxAuvadfRqjVihSC4k4ahBFKeFK8nUy6sUh3fy
- hZNHN0tWR4gGFfZ3awCeZNMMoASjPPLgcRKRd6YxdoPnf/Ucu04+gWq3KK2vMjg6RiosY8th
- ZEngmg0UH54ObVgut0rNrgk8iGdDsDn5OyL4oxKsNRpMTExwvNsO7Tk8a4Aryxd9Uwl+91QI
- Jei/Jd7POpu6CuRaTU6deZN0Ty+FZAgpGKFUqhCQTGxXodBXZGV5lVQyQssSRCMhkCR68nme
- vf0D31SCbcOmtlAn2y2EeY5NNYBVX8VW49yYmmJCr5LPJWipfbjNWVzT5q03L6LrBtHePrIR
- GdWqE+ndQ08+v5my2o4QArtbCPMkm2qAYGaILXmNTHI7mYiCFAgiqTGE1YNwHXS9RaWmkSkU
- iaiCm9cusnuv/y6TVkMq2dHuEqgX8WwGOD33mm/2BNumQ21BIzuS6rSULvfh2UrwgfU4YSfS
- aRkbQsMwmVyocDTaHQW8xgca4PTp04wWYsybcaJBCWyTpblZxnbvIhhJUJ2dYE0pcmJPP//h
- r3/Ev/jGM8yV6rhGnXplDSnTz+Fd23n5+V/RP7qbiGTRMCzS6Til1TrRZBJXr6Nra4Tzo6gK
- RGULNZYln06g/+QH2D65IskVgmHHQXvRs8+bzy0f+BdpNhoYTZganyYcikFcISEEZ15/lXQs
- gC3HCPRkmb1+EdNocO7Ni4QSWW5cOoejxBga1JnvG0bTNFRFZvz6TYRwke0q6+UW2w8cZurS
- edRYAmmmQigaJibqBDJbefzBw+38N9h0TMfhbq3O7u4FGZ7jAw1w4sh+bs6WeOwLJ7BscBRQ
- Wjq2GCUUjVFdWSDV34vQw/zFtw8yPXWbXLEPxTaIxJO0bEFfKoC+azcty2bf/n0ACFvHMF0y
- PQVk2yCb78FxBaYDcdVGifjvS+IKgeE4nZbR5X3wbAiun36NkGV1WsqG4LourZZJOOyPTOMn
- PDspvT0aw1X98dTUdZ1bt+fYt3dvp6V0uY+2GWD8/GkSQ9txLQfJqmPrDTT1vadD240y86s1
- HnrgGD+980PfVILfPR36jPRSp6V0uY+2GUBvaFx78zJPHt/Jcy+8TiISQlPSGEaNvndOh5Z8
- fDp0twbgTdpmgC1ju+kVsFazOXHiKKFokqZhoekGvfk0CAm9USUY9V8IdloOtUWN7Ei3DuA1
- PBuCz8y9jqW0Oi1lQ2hoGhOT3dOhvYhnDXB56S3fhOB3T4VQQ912aK/RvlUg1+FXv/oF+w8d
- oVYziAYdQtEEa+Uaw0N9LKxUSIVBjWXJpeK+CsG2YXenQB6lrcugWqOB4jaYW65irU6yprnE
- k2kuXp7iyace5tUXnqPPh3uChRDYpj9GM7/RPgNIEg8+/CjF3gxuIEt5WWFbMEokGiUQCLK6
- ss4DJ04Q8OGe4O4qkHfxbAY466MQbJsO9UWNzNauCbyGZyvBYTVKUPXHwViGZbKirzIY6J4O
- 3QkKkSJbElvf92efyAC1hWnuNhNoixMkUzlSiQjTt27TN7IHVYaYajM+PcuTjx7k+V+fJpLr
- pScp47oK/UNDLMzeo6e3lzfPnOfIoyfQNQtaGg3DIt87QCGX4mc+CsHCFTi2wyvXPfu88TVP
- DnwFZb7MD1+5zBdPHuDW/Aqt1Xn2PfzPPuEI4Fo0dJ16ZYm1lXXqpomQJJbLFwlGgkRUcB0B
- OExeu8KhJ55m5tZVbDvMGxcuUOjp4fzlaQrJEGcuXCKBQLccZOGyuFTy5enQ3VWgzhKIJXEq
- y0SyRdYvnaNVaTE5eeOTGSDRN0Zi/RqjJ56kpesYlkUgEMCyHQwHkgEX3VGR5Cjf+NZ/TrNZ
- Idx7HEkK0tDWee6F0zz9ta9RXSszNJSn1rCJBGQQAnx6OnR3FaizJNMZto7txtbWOHTkCRR9
- hVjPsJdD8CnfhGDXdTFNg0jEf31OnwUG40NsS+143595dlIaVsME1ECnZWwIerPJ8uwKe7rt
- 0BtKVImyN3fgU71GxwxgN1b4ze/eoDC8jWhAoVwuMbZ9OyBRKPTwszs/8k0I/sd26Jc7LcVX
- 9McG2ZsY5i//7V/x5aef5urV6+zfPcL8apMnjmzlu//upwyObSUfj3LogUfJJ8O/9xodu7pc
- CBtDq7M0f4/Tr7/OQqXJ1QuvcWturVOSNg01pJLt1gA2BReZZDxEtQmKpZHt7Wd1ZQVdq2E7
- JjO3bzFxd4Vk7P2X1Ds2AiihFMcffoi6VqfYkyfdU2Bh6hK797z/XO2zjGM51JY0slu7q0Ab
- jRyIMDy0hWI2hNi+j2a5xJHjh7ADEY6fOE7fwCC96Rhr1SZ92fjv/f+eDcFvzPsnBDe0t0+H
- Pna82w69kcTUOEcLD3yq1/CsAWanVpDxRwgWwqVltwgFfn8O2uWj0z+WQVE3dta+YVOgqbde
- paTLEMmSjahkMkkss0UwFCQckPjlL3/DI08+hSSrOI11jKaGEcq/Z0+w1SizsFrj4ZPHGT81
- h1Hxx4GyZstgpbzAUO9op6V8pnnmO4f5u//4Xbbve5CFm9foHdvP7elpvvkv/pSf/u1/IJ3L
- 0CTDtrEx9u8Y+kivuWEGaJk6U5OzWLZDPJUnGgtxZ+o6xx56ii0pl+2HHubSuVOEEmms8iJr
- DYGayGPodfpySSTh+HZPsBAuZssfl310mlQ6ydz8CluLGSotm2yugCrplKs683MzzDeTPPDQ
- R58WbZgBBkb38NVth2gaFpVyhaFiln07d5IrDqKVZokbLXqPnMBBRq/m2B6MIikqdd2kN5cC
- IWE0qgR8uCc4GAgz2DvSaRm+YHBgC3KsQHV1mWxIoMlxVldbPHjyCMFEhnR+gPr6KmRiH+n1
- PJsB7lxbQhaerdN9LEzLZGV9gaGuCT4VW/bmvZsBNpr+6B0isj9OhtOaJnVznpGE3mkpny2S
- A5Af29S3eI8B7o2fg9wW7t1dJpsIkM2kWKvr9KYjLFd09u4Y5aUXnsNwIxzdv40WIZqVZRK5
- fuqrCyR7h8kFdH74m9P84TNP8YPvP8uffuUBTk+ukEkleOj4IeZuXme+0iQqWSw2A4zkIxCO
- ISMRUV0IpSjm03Dxb3xzT3DEFeyxHFj17PPGm+x6hsVyk+89f4EvPXyY6Xsl4nqJsS/9Mdr0
- m5wan6cvFyUYiPDUl79MQJE+9lu85y/SMnXuTE5yb3KSPQcP8sqrp+ntHWAiKhFybPbuGOX2
- rRvkhvZx6eokCOftO4CvnEOOFQg1Ajy2fwCrusT5U6dwbJOJmWWWZu+QzDyA2bKYvTfPnocf
- Y33qTbTSHG8uCJxQiFxEIiBs6k6Yrz39xQ37N/QCpu0ys6azp797TerHJRBNIOolAqkClbXr
- 7N21Bctx0ep19GaDG0tTDO99FFX++F9+uM8APVvGKESS7BzdTqW8zlef+TKOkJmbmeDOcguQ
- +Mozf4plGKhBFVcOo2Aj7Aa/+9VzHN51DJA4cPQksWSax554lLu3Z9m++yCV5TvcuL3A0QdP
- cmNqknRmgKNHB7BsBykQICQLdK2KFPJfy4DrChrdduhPRDKdZXTnXmSjyqGjxyn2JjGkFuHt
- ezgWLdJX+CIqDi3HJfQJ8oFnQ7A5/TIR/FEJdlwHQzeIxT7aykSXd0hvgd49m/oW3p2UKm9f
- meoHTF1nZmGZPbt3d1qK9+ndC/FC297uIxngxsXXWGrKqIEY2UyMVCSCYVnU6k2Gh4oslzUc
- o8GunQP87NnXyPcXGOxJEIjEaegGyUgEV1FYW6tSzCfQbRlXr2A06hihPBEVgsKkYdrkegd8
- F4Jdw6axqEGp2wz3oTz+P3L2whlurkbY0euikePu1Dj/ybf/M07/9pdUbIvWukl+eJQnHjnO
- p31EfiQDtIwmtogzP3GRwJFj3Bm/xnqjSTgc5eyFK+QLWWJBmZ07+khkemnWlrk+P8Gq5rL3
- 6FFujV/DVMMszi2wc2wL1aZFUC+x1hAoiTymXqM/m8YVLksrqxSf8Nee4EhQYXf/73cidnl/
- 0tksCxcmePToI5y5fI+hvh4EUDddHKvMhbOX+fr+o5/6yw8f0QD9o3txltfZ9tjjxLMFYq7F
- WCxJIBBEUVWqDZ1UNIQsR8klZfIDx6mtzrM9GCHfP0RE2MQyebYODdKTTWLYArO2zlgoiiSr
- 1HWDQi6NJASSD/cEm7bLvTWDPV0TfCRyuV72H1Ipr5U5fuwwIddAW1/jwJ4xGu4uDh/6Erbd
- RMCnNoGHQ/ArRPDHPcFa4+126OPd06E/nP5DkCi27e08a4C3Fs/jqHanpWwIwhU4poMa8e6a
- Q6fYntpJLpzv2Pu3/S/iOi1++qMfceiBh6nX6gz091Iq13HNJqrskipsoZBN8vO7P/HNnmDb
- tKkvNcgM+6/G8Wn59p7/humzp5guRdhZdFmtCWZn7vKVb3yLu5deoWK3MNdb5IdHePKRExsy
- 7/+ntH1PsCRJBCSLKxfOUVpZ4tlfvczps2eZm73HzTszXLt8qd2SNh3hCizdH6PZZpDOZVm8
- e4fi4FZmblwiV+hhsVR7O/QaVd564wyRRGbDv/zQoTrA6K6DyI7BTFXiK4+NUapq9GaSIEm+
- DMFKSCUznOy0DM+SzxXZf1ilvF7mK3/yLebv3mZrPkB/eAcNV+bwoadwNij03o9nM8C5+TPY
- qj+6QR3Tob6kke5OgX6Pnek99ETaV/i6H8+mMlVW/VIIxnYdKvUKPVLnwp4X2J7eQS7c02kZ
- 76FtI8Dc9DjzVZ10Mk0yEUNr6ghkVEfHkiMkokHclo4az1LIpvhfz/wr34Rg1xU4pk0g4o9N
- /p+U/2L3d3AnVpgqhdjZpzK/uMbt23P82V/810yc+jUaYFcs8ltHNyXwvh9tGwFmZxfZ8/Cj
- GEs3+fufn2N0IEVAlmkhEZIlHOGi2k2UzFYKDx5ul6y24FoO2kqzuwoEpHI5ls6Ps3twF4VC
- lpu37lLXDBqNBnXXZvL8Vb6670hbvvzQRgMce+gkUzemyGTT7BrbQtBt0je8nXktypsAAA0/
- SURBVHgkhIrNSs0kFZKQo/7rl+muAv0j+XyRA4clmg2NwtAIJx4IkFItDhw6QlMoPPbgl7Ft
- fVMC7/vh2RB8fuEstuKTEOw46LpOPP75boXYkd7d0cD7fng2BO9elAk5/lgS1fUWM3cX2bV7
- V6eldAx1+06UiLcCMHTAAMK1ef30GTLJJOlMBst1qdd1ivkkugWzs7M8dPIE+s+fxV5ebLe8
- TaFhWaxWqjTOfn5XgWLf/g4Xrp1maiXEzn6V+cXVfwjAV1/6KZmRMWbHb5MfHuXJR9sTgKET
- p0MLaFRXeeP0Kc6cfoNqtczZ10/xq5dPMTk5ia777+SEsKIylu4G4HQux9Ldu8iKSqGQwxUO
- dc1kYOsYtyYvc/HCG4TjqbZ9+aETUyBZZt+BIwwOlomnMqRSMexjgkJvD46QCW7wuS9eoOU6
- LDSa7PicmyCXL3LgqESz2aAwuJUHTgZJqRZGLMrRE0/whQefbmsABg+HYO38WUK2P1ZOtIb2
- Tjv08U5L6Rjqjl0oPd4KwOBhA7y5cM43rRDCFdiGQyDq2TWHT0VYiXC457O516FtfxFtbZ63
- JucoFos4epVQNEY0nmJ9ZYm+/iILpSrpsIQay9KTTfKLmWd9Uwm2TZv6coPMFn9OgQqRXg6n
- d/K//x//F1/6g6e5euUK2VScQLqfP3j8Af7u3/8V4VSBaCzO4RNfoDfjnQOQ22aA9eV54tkC
- 4+PXiIRUauslYkFY0SSkN8d56uknef2F5+gb3kHPyc/m0+SDEK7AavpjOvdBCGQyqRir63Uy
- uQKV9SVcrUVtqYezb44zPKKhxAo89mSo01LfQ9sMMLTrCMbUFPv3HyAaUnFbTeRwgnJpiWJf
- kcVSlYcePIkay7ZLUttQggrpLf5uh5bUMNtGRsllopQUmQO7R9BsGSuQ43/+n/4H6s0WuXSC
- clUjkvfOSOjZDHBh4Q3/VIJbDvXlBukhf5ogrEY40vPZDPgbOwIIQbm8RiAUIx6L3PcjF8cR
- qOrb1d2mVqdpWMRjEQLhCPefa6rqo0jeLVR/LIRlYeklAlp/p6VsKKPFOImATUVrUV5dIRhL
- Ulkt0dPXj21o2KgokowQDrF4vK3r+x+VDTaAy2uvvw6SyujIVoSlUy7XicRjmM0KTZJEFYc9
- h49z7pWXCMmCViBNb18PqqxgNcpEs/2MbR3kt5cWKWmeG5w+EXbLpLa2SLbPXzngzx8dxrj9
- AtcWgzx+dJBzZ85xY0Hjv/vv/1te/MUvaEktZsZneeiZP+bRBz7dhdabxYZXnQy9yb4DB7lz
- +wY3xq9wffoOd27N0Dc4SHV1AdM0mC9VkWQVraFjN6vcmpkjGlZZKZWZu3dvoyV1HFlViae9
- 1wezEWR6elm8Pc1bF6+w+9hDPHxgC9dvzpNLxyhVGkTjcaqVSqdlfiAbmwGEQDcMIuEw9XoN
- JRCkZRiEw2ECwQBN3cS2LNLpNIahE1BlGnqLcFDFtAWysAiE4wQUib/88RXfjAAto8n64h2K
- I3s7LWVD+fNHhzkwGGBuqU5IsQlGE9gtk2QyiWO3EEoQBcnTUyDPhuBSRSMQ8NaS2SdFNwzu
- zcywc+fOTkvZUGJhlVDgs92xu+kGEEJQKZeJRN4/7L6LrutEIm8HZ9d1+cnU36NR20xpbcNx
- XZqNBomEty/I+EL/E2wJDVCumwQlm0A0Qa1SprfYh9msYQkFVZZxXYd4IuHJJ/rHZdOXWVrl
- Wf7mBy9wYKRIfGArrmkSDau4LZ1yWSMaj1LVXeJRlUxEoSklOLh7G1fXL/mmEmzpFuWZGoVd
- uU5L+f9lZ2YPK+evcHle5Ysnhjn31hS9cSgd/BLTbzyHSYvZ6/c4+fQf8djJg52WuyFs/jpj
- KEEhZDG/WifszhHFpeZWCMZ7mZi+TTaZIBpPUzdcVrQKTijHwd3bNl1WO5FkieBnpA8o09PL
- 8mtneFMps2vfIS68cZFDjwQop2NcurNONJGgWi53WuaGsflTINehXK4SjUaQZImW5RJSJeRA
- mEa9SigUwn2n/9W1WqAGiYZD/joVwhHYpk0w6u1TIf7TXf8lxzKHmVuqEVEdJDWIYRik0llU
- ycH9DITaj4t3Q3B9GTXk7S/MR6WhaUxMTnLM46dDxwNxQkq40zLayqcel1tGk3JNI5vLE1Bk
- ms0m0ehH6/Zr1KtoukkkEiOZeO/9WWevGzQtf9wRZlstamsR1s81Oi3lAzm5M0ci5bC0vkYQ
- m2AsSbVcptjXR7m0iOHKpBNJhPBPAIYNMMDUW6+xYgZAjdPSquhmg76BLeSiEtV6k9J6DUVR
- 2TI6ytz8ItkwpPtG2Tbcj3AdXnnlVUKqTGFwK3HVQU30sHN0CxNzVd/UAVzXwW4pBBvrnZby
- gWzvj7N49WUuz8s8dWKEN377EsW4xOrBp+hXG7x+/jJ3Lo3z4NNf57GT3qzqfhI2pBIshyLM
- Tl1h+sZNDDlCvbzC1NQN7twYx3Zs6vUap8+8yc07d6mVS0xMXmdxuUw0FicWixOOxamXl7k+
- Pk5pdW0jJHkKu2Wyvni30zI+lExPgeXb05y/8BY79+xkekmjP6myurzMAw8/QiyRpFr2rok/
- CZ86A7RMnVq9QSIRp9FoEnxnrd+xLZAkFEVFCIEQAtOyCQdkhHCpajZ9vVkMw/jHF3MdpECI
- oKr4qhJst4x3eoFGOi3lA/nmI1s4PBRibrlGWHWR3wnAyVSGUFAlGAphGibCdYj6JACDh0Pw
- aqVBIOiPSrBwXWzLIBDyzk6o+4mGlM98VfeTsOmL045lsrpeIZPLE3ynFVoIF9sRBNT3/oOL
- d3qJwqEQryz/3DeVYKNpMHtznrED3qhvfGPbv0Q0TBwljF6v0FPsp2XqhAIxXLuF1tQJBCPY
- VotEwt+n2W26AarzU1yeqWLWL5NORoiEgxjNMuWmwmBfD66A1VKZbWP9LC5VMfQGX3ziMa6t
- X/ZNHcA2bepGg/XlpU5LAeBrI3/Kz3/41/Rve5AEa4zfnGd6+gbf+fa3OPv8T7g8v8ZqSfDN
- f/lnvjdAWw7h0ZoOI8UYpmly7fpN+gaHcFtNrl27xsTUXSprq1ybmmJm5m475LQdOaAQL3hr
- +lPo7eXG5ARXbsxz6PABhvuyNLQKs/Ml6tUKqUSctXK10zI3nU3PAK7dwhYKAVWiVq0TDodQ
- Ayq6bmBZFo3yMtfurvHIyYM4DgQDKsFg0FeVYK/1Av1vJ/8NSsOh5QZpGRqZfBEZB4BQMICu
- 6yjBCI5lEovFPuTVPtt4NgSX6isEfFIJ1nWdmZkZdu3yxuG4qVAaRfr8Bd73o+0GcO0Wa+U6
- uXwO+QPW0lzXpfKj7xPU/BGCHcdF15sdPR5diieI/dmfs7w4TzyVoVyuk0/HaNoS2WSUxcUl
- EskEwUAAy5WJRYId09pO2t6i+Pqrr7BluJ83Tp9FDgUIBEKMDBdZLNVwjTL5/jF2jw1jXbsM
- Pjod+kalyuGezp0OLWdz8PU/4rvf/Sv++J9/k5vX3qIpRQiqMl9/6gR/9X9+l/zwAGooxzf/
- 7E86prPdtP+eYFnCsmwa1TUcq0WtWuet8Slm7t5BN10W52baLWnTkSWJmNr56ZxAptiT4urF
- t6jYIdJhCSFc1tdWiadTaFWdeFhF081OS20b7Z8COTbVWoN4LIztuAgA18UW/EOdIBgMUvpf
- /hUBn4wAjiswHJtYoHMmkLM50v/63zB3b4ZEKkNNa5BLJ7GFRECR0ep1YvE4wWAAyxZEwp+P
- KZBnQ7C+WiLSwS/MRqK90w59vJPt0LKMnPLf/Wuflo+VAarlNQwbent+fznPtS1cOcC7x/sb
- zSaBSBQZ8Q/7fSvrq4RiKaLhIMK10U2HaOT92x2uv3EKp6l9/E/kQVq2Q6laQ6x3rhA2euIh
- 0kGV1apOSLYJxjM0qmV6eousLc9juDKZZAohHBLJpG96fT6Mj2WAV196kR0Hj7M8e5uqKUjH
- w+DarC4uobeapPp24LZ0AqrAbjTIDg6xulrGbGoc3F5kuuQSj6wiuRaiVWWtbGI4guGhAcoN
- k3zEpUmcQ3vHWLwxgb66slmfu624rkvLdpiZ7dyI1rt9J7dvX+HiLHz5we289rNncVHZ+/A/
- Y3va5Oz5y9y6eIWTT/8Jj/uo3fnD+NirQMJ1mV9cxnQElZUWdqPG0lqddE8BfX6RZMzlngY7
- MwpXJyZpNQ2KPTlkWcZxTCYmbhINuki2Qa1mkcjluXDxGsFolFW3jgjnObR3bDM+a8do2Q7z
- a+ts6+vtqI50vsDyq6c4K5fZuf8A1y5doaU3KRkrHHvwYVZuz1JdW+2oxnbzsTJAo17DtAXR
- sMrU9B22jw4jKyot0yQYCiEA23YIBlRkCWzHwXEhoCqEQiG0eg0lGMa1WiiqgnAFkqygKBJm
- y0HGQVIChENBfvFv/7VvRoCWZbNSrTGY79zJ1ye+8ecM7d/HwkqdiCpQI3GE3SISjSJJEoFg
- ENNogXCIxGKfmymQZ0NwbX2NoE9CsBCClm0T6uDnCUaiqMHPx8rOx+H/A2WZKd6inSWwAAAA
- AElFTkSuQmCC
-
-
- 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
- iQAAAABJRU5ErkJggg==
-
-
- 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
-
-
-
diff --git a/test/assets/Sample View Image.png b/test/assets/Sample View Image.png
deleted file mode 100644
index 007de5680..000000000
Binary files a/test/assets/Sample View Image.png and /dev/null differ
diff --git a/test/assets/SampleDS.tds b/test/assets/SampleDS.tds
deleted file mode 100644
index 40b850ace..000000000
--- a/test/assets/SampleDS.tds
+++ /dev/null
@@ -1,93 +0,0 @@
-
-
-
-
-
-
- a
- 130
- [a]
- [xy]
- a
- 1
- string
- Count
- 255
- true
-
- "SQL_WVARCHAR"
- "SQL_C_WCHAR"
- "true"
-
-
-
- Today's Date
- 130
- [Today's Date]
- [xy]
- a
- 1
- string
- Count
- 255
- true
-
- "SQL_WVARCHAR"
- "SQL_C_WCHAR"
- "true"
-
-
-
- x
- 3
- [x]
- [xy]
- x
- 2
- integer
- Sum
- 10
- true
-
- "SQL_INTEGER"
- "SQL_C_SLONG"
-
-
-
- y
- 3
- [y]
- [xy]
- y
- 3
- integer
- Sum
- 10
- true
-
- "SQL_INTEGER"
- "SQL_C_SLONG"
-
-
-
-
-
-
-
-
-
-
-
- A thing
-
Something will go here too, in a muted gray
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/SampleFlow.tfl b/test/assets/SampleFlow.tfl
deleted file mode 100644
index c46d9ced9..000000000
Binary files a/test/assets/SampleFlow.tfl and /dev/null differ
diff --git a/test/assets/SampleWB.twbx b/test/assets/SampleWB.twbx
deleted file mode 100644
index bd7b2755b..000000000
Binary files a/test/assets/SampleWB.twbx and /dev/null differ
diff --git a/test/assets/World Indicators.hyper b/test/assets/World Indicators.hyper
deleted file mode 100644
index b6b3b543a..000000000
Binary files a/test/assets/World Indicators.hyper and /dev/null differ
diff --git a/test/assets/World Indicators.tds b/test/assets/World Indicators.tds
deleted file mode 100644
index 958127103..000000000
--- a/test/assets/World Indicators.tds
+++ /dev/null
@@ -1,406 +0,0 @@
-
-
-
-
-
- <_.fcp.ObjectModelEncapsulateLegacy.true...ObjectModelEncapsulateLegacy />
- <_.fcp.ObjectModelTableType.true...ObjectModelTableType />
- <_.fcp.SchemaViewerObjectModel.true...SchemaViewerObjectModel />
-
-
-
-
-
-
-
-
-
-
-
-
- <_.fcp.ObjectModelEncapsulateLegacy.false...relation connection='World Indicators newleaf' name='Extract' table='[Extract].[Extract]' type='table' />
- <_.fcp.ObjectModelEncapsulateLegacy.true...relation connection='World Indicators newleaf' name='Extract' table='[Extract].[Extract]' type='table' />
-
-
- Country / Region
- 129
- [Country / Region]
- [Extract]
- Country / Region
- 0
- DATA$
- string
- Count
- 209
- false
-
- <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[Migrated Data]
-
-
- Date
- 135
- [Date]
- [Extract]
- Date
- 1
- DATA$
- datetime
- Year
- 11
- false
- <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[Migrated Data]
-
-
- F: Deposit interest rate (%)
- 5
- [F: Deposit interest rate (%)]
- [Extract]
- F: Deposit interest rate (%)
- 2
- DATA$
- real
- Sum
- 50
- true
- <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[Migrated Data]
-
-
- F: GDP (curr $)
- 5
- [F: GDP (curr $)]
- [Extract]
- F: GDP (curr $)
- 3
- DATA$
- real
- Sum
- 2120
- true
- <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[Migrated Data]
-
-
- F: GDP per capita (curr $)
- 5
- [F: GDP per capita (curr $)]
- [Extract]
- F: GDP per capita (curr $)
- 4
- DATA$
- real
- Sum
- 1877
- true
- <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[Migrated Data]
-
-
- F: Lending interest rate (%)
- 5
- [F: Lending interest rate (%)]
- [Extract]
- F: Lending interest rate (%)
- 5
- DATA$
- real
- Sum
- 72
- true
- <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[Migrated Data]
-
-
- H: Health exp (% GDP)
- 5
- [H: Health exp (% GDP)]
- [Extract]
- H: Health exp (% GDP)
- 6
- DATA$
- real
- Sum
- 22
- true
- <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[Migrated Data]
-
-
- H: Health exp/cap (curr $)
- 5
- [H: Health exp/cap (curr $)]
- [Extract]
- H: Health exp/cap (curr $)
- 7
- DATA$
- real
- Sum
- 936
- true
- <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[Migrated Data]
-
-
- H: Life exp (years)
- 5
- [H: Life exp (years)]
- [Extract]
- H: Life exp (years)
- 8
- DATA$
- real
- Sum
- 45
- true
- <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[Migrated Data]
-
-
- Number of Records
- 2
- [Number of Records]
- [Extract]
- Number of Records
- 9
- integer
- Sum
- 1
- false
- <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[Migrated Data]
-
-
- P: Population (count)
- 5
- [P: Population (count)]
- [Extract]
- P: Population (count)
- 10
- DATA$
- real
- Sum
- 2295
- false
- <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[Migrated Data]
-
-
- Region
- 129
- [Region]
- [Extract]
- Region
- 11
- DATA$
- string
- Count
- 6
- false
-
- <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[Migrated Data]
-
-
- Subregion
- 129
- [Subregion]
- [Extract]
- Subregion
- 12
- DATA$
- string
- Count
- 12
- true
-
- <_.fcp.ObjectModelEncapsulateLegacy.true...object-id>[Migrated Data]
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Gross Domestic Product
- in current US Dollars
-
-
-
-
-
-
- Gross Domestic Product
- per capita
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- <_.fcp.ObjectModelTableType.true...column caption='Migrated Data' datatype='table' name='[__tableau_internal_object_id__].[Migrated Data]' role='measure' type='quantitative' />
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- [Region]
- [Subregion]
- [Country / Region]
-
-
- <_.fcp.SchemaViewerObjectModel.false...folder name='Financial' role='measures'>
-
-
-
-
-
-
-
-
- <_.fcp.SchemaViewerObjectModel.false...folder name='Health' role='measures'>
-
-
-
-
- <_.fcp.SchemaViewerObjectModel.false...folder name='Population' role='measures'>
-
-
- <_.fcp.SchemaViewerObjectModel.true...folders-common>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- "Europe"
- "Middle East"
- "The Americas"
- "Oceania"
- "Asia"
- "Africa"
-
-
-
- <_.fcp.ObjectModelEncapsulateLegacy.true...object-graph>
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/World Indicators.tdsx b/test/assets/World Indicators.tdsx
deleted file mode 100644
index 6e041442b..000000000
Binary files a/test/assets/World Indicators.tdsx and /dev/null differ
diff --git a/test/assets/auth_sign_in.xml b/test/assets/auth_sign_in.xml
deleted file mode 100644
index 56c245b3c..000000000
--- a/test/assets/auth_sign_in.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/auth_sign_in_error.xml b/test/assets/auth_sign_in_error.xml
deleted file mode 100644
index 24c34880e..000000000
--- a/test/assets/auth_sign_in_error.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
- Signin Error
- Error signing in to Tableau Server
-
-
\ No newline at end of file
diff --git a/test/assets/auth_sign_in_impersonate.xml b/test/assets/auth_sign_in_impersonate.xml
deleted file mode 100644
index 4d385b3d9..000000000
--- a/test/assets/auth_sign_in_impersonate.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/custom_view_download.json b/test/assets/custom_view_download.json
deleted file mode 100644
index 1ba2d74b7..000000000
--- a/test/assets/custom_view_download.json
+++ /dev/null
@@ -1,47 +0,0 @@
-[
- {
- "isSourceView": true,
- "viewName": "Overview",
- "tcv": "PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0ndXRmLTgnID8-Cgo8Y3VzdG9taXplZC12aWV3IGRhc2hib2FyZD0nT3ZlcnZpZXcnIHNvdXJjZS1idWlsZD0nMjAyNC4yLjAgKDIwMjQyLjI0LjA3MTYuMTk0NCknIHZlcnNpb249JzE4LjEnIHhtbG5zOnVzZXI9J2h0dHA6Ly93d3cudGFibGVhdXNvZnR3YXJlLmNvbS94bWwvdXNlcic-CiAgPGFjdGl2ZSBpZD0nMScgLz4KICA8ZGF0YXNvdXJjZXM-CiAgICA8ZGF0YXNvdXJjZSBuYW1lPSdmZWRlcmF0ZWQuMTBubms4ZDF2Z213OHExN3l1NzZ1MDZwbmJjaic-CiAgICAgIDxjb2x1bW4gZGF0YXR5cGU9J3N0cmluZycgbmFtZT0nWzpNZWFzdXJlIE5hbWVzXScgcm9sZT0nZGltZW5zaW9uJyB0eXBlPSdub21pbmFsJz4KICAgICAgICA8YWxpYXNlcz4KICAgICAgICAgIDxhbGlhcyBrZXk9JyZxdW90O1tmZWRlcmF0ZWQuMTBubms4ZDF2Z213OHExN3l1NzZ1MDZwbmJjal0uW2N0ZDpDdXN0b21lciBOYW1lOnFrXSZxdW90OycgdmFsdWU9J0NvdW50IG9mIEN1c3RvbWVycycgLz4KICAgICAgICA8L2FsaWFzZXM-CiAgICAgIDwvY29sdW1uPgogICAgICA8Z3JvdXAgY2FwdGlvbj0nQWN0aW9uIChNT05USChPcmRlciBEYXRlKSxQcm9kdWN0IENhdGVnb3J5KScgaGlkZGVuPSd0cnVlJyBuYW1lPSdbQWN0aW9uIChNT05USChPcmRlciBEYXRlKSxQcm9kdWN0IENhdGVnb3J5KV0nIG5hbWUtc3R5bGU9J3VucXVhbGlmaWVkJyB1c2VyOmF1dG8tY29sdW1uPSdzaGVldF9saW5rJz4KICAgICAgICA8Z3JvdXBmaWx0ZXIgZnVuY3Rpb249J2Nyb3Nzam9pbic-CiAgICAgICAgICA8Z3JvdXBmaWx0ZXIgZnVuY3Rpb249J2xldmVsLW1lbWJlcnMnIGxldmVsPSdbdG1uOk9yZGVyIERhdGU6b2tdJyAvPgogICAgICAgICAgPGdyb3VwZmlsdGVyIGZ1bmN0aW9uPSdsZXZlbC1tZW1iZXJzJyBsZXZlbD0nW25vbmU6Q2F0ZWdvcnk6bmtdJyAvPgogICAgICAgIDwvZ3JvdXBmaWx0ZXI-CiAgICAgIDwvZ3JvdXA-CiAgICAgIDxjb2x1bW4gY2FwdGlvbj0nQWN0aW9uIChNT05USChPcmRlciBEYXRlKSxQcm9kdWN0IENhdGVnb3J5KScgZGF0YXR5cGU9J3R1cGxlJyBoaWRkZW49J3RydWUnIG5hbWU9J1tBY3Rpb24gKE1PTlRIKE9yZGVyIERhdGUpLFByb2R1Y3QgQ2F0ZWdvcnkpXScgcm9sZT0nZGltZW5zaW9uJyB0eXBlPSdub21pbmFsJyB1c2VyOmF1dG8tY29sdW1uPSdzaGVldF9saW5rJyAvPgogICAgICA8Z3JvdXAgY2FwdGlvbj0nQWN0aW9uIChNT05USChPcmRlciBEYXRlKSxTZWdtZW50KScgaGlkZGVuPSd0cnVlJyBuYW1lPSdbQWN0aW9uIChNT05USChPcmRlciBEYXRlKSxTZWdtZW50KV0nIG5hbWUtc3R5bGU9J3VucXVhbGlmaWVkJyB1c2VyOmF1dG8tY29sdW1uPSdzaGVldF9saW5rJz4KICAgICAgICA8Z3JvdXBmaWx0ZXIgZnVuY3Rpb249J2Nyb3Nzam9pbic-CiAgICAgICAgICA8Z3JvdXBmaWx0ZXIgZnVuY3Rpb249J2xldmVsLW1lbWJlcnMnIGxldmVsPSdbdG1uOk9yZGVyIERhdGU6b2tdJyAvPgogICAgICAgICAgPGdyb3VwZmlsdGVyIGZ1bmN0aW9uPSdsZXZlbC1tZW1iZXJzJyBsZXZlbD0nW1NlZ21lbnRdJyAvPgogICAgICAgIDwvZ3JvdXBmaWx0ZXI-CiAgICAgIDwvZ3JvdXA-CiAgICAgIDxjb2x1bW4gY2FwdGlvbj0nQWN0aW9uIChNT05USChPcmRlciBEYXRlKSxTZWdtZW50KScgZGF0YXR5cGU9J3R1cGxlJyBoaWRkZW49J3RydWUnIG5hbWU9J1tBY3Rpb24gKE1PTlRIKE9yZGVyIERhdGUpLFNlZ21lbnQpXScgcm9sZT0nZGltZW5zaW9uJyB0eXBlPSdub21pbmFsJyB1c2VyOmF1dG8tY29sdW1uPSdzaGVldF9saW5rJyAvPgogICAgICA8Z3JvdXAgY2FwdGlvbj0nQWN0aW9uIChPcmRlciBQcm9maXRhYmxlPyxDYXRlZ29yeSxNT05USChPcmRlciBEYXRlKSknIGhpZGRlbj0ndHJ1ZScgbmFtZT0nW0FjdGlvbiAoT3JkZXIgUHJvZml0YWJsZT8sQ2F0ZWdvcnksTU9OVEgoT3JkZXIgRGF0ZSkpXScgbmFtZS1zdHlsZT0ndW5xdWFsaWZpZWQnIHVzZXI6YXV0by1jb2x1bW49J3NoZWV0X2xpbmsnPgogICAgICAgIDxncm91cGZpbHRlciBmdW5jdGlvbj0nY3Jvc3Nqb2luJz4KICAgICAgICAgIDxncm91cGZpbHRlciBmdW5jdGlvbj0nbGV2ZWwtbWVtYmVycycgbGV2ZWw9J1tDYWxjdWxhdGlvbl85MDYwMTIyMTA0OTQ3NDcxXScgLz4KICAgICAgICAgIDxncm91cGZpbHRlciBmdW5jdGlvbj0nbGV2ZWwtbWVtYmVycycgbGV2ZWw9J1tDYXRlZ29yeV0nIC8-CiAgICAgICAgICA8Z3JvdXBmaWx0ZXIgZnVuY3Rpb249J2xldmVsLW1lbWJlcnMnIGxldmVsPSdbdG1uOk9yZGVyIERhdGU6b2tdJyAvPgogICAgICAgIDwvZ3JvdXBmaWx0ZXI-CiAgICAgIDwvZ3JvdXA-CiAgICAgIDxjb2x1bW4gY2FwdGlvbj0nQWN0aW9uIChPcmRlciBQcm9maXRhYmxlPyxDYXRlZ29yeSxNT05USChPcmRlciBEYXRlKSknIGRhdGF0eXBlPSd0dXBsZScgaGlkZGVuPSd0cnVlJyBuYW1lPSdbQWN0aW9uIChPcmRlciBQcm9maXRhYmxlPyxDYXRlZ29yeSxNT05USChPcmRlciBEYXRlKSldJyByb2xlPSdkaW1lbnNpb24nIHR5cGU9J25vbWluYWwnIHVzZXI6YXV0by1jb2x1bW49J3NoZWV0X2xpbmsnIC8-CiAgICAgIDxncm91cCBjYXB0aW9uPSdBY3Rpb24gKE9yZGVyIFByb2ZpdGFibGU_LE1PTlRIKE9yZGVyIERhdGUpLFNlZ21lbnQpJyBoaWRkZW49J3RydWUnIG5hbWU9J1tBY3Rpb24gKE9yZGVyIFByb2ZpdGFibGU_LE1PTlRIKE9yZGVyIERhdGUpLFNlZ21lbnQpXScgbmFtZS1zdHlsZT0ndW5xdWFsaWZpZWQnIHVzZXI6YXV0by1jb2x1bW49J3NoZWV0X2xpbmsnPgogICAgICAgIDxncm91cGZpbHRlciBmdW5jdGlvbj0nY3Jvc3Nqb2luJz4KICAgICAgICAgIDxncm91cGZpbHRlciBmdW5jdGlvbj0nbGV2ZWwtbWVtYmVycycgbGV2ZWw9J1tDYWxjdWxhdGlvbl85MDYwMTIyMTA0OTQ3NDcxXScgLz4KICAgICAgICAgIDxncm91cGZpbHRlciBmdW5jdGlvbj0nbGV2ZWwtbWVtYmVycycgbGV2ZWw9J1t0bW46T3JkZXIgRGF0ZTpva10nIC8-CiAgICAgICAgICA8Z3JvdXBmaWx0ZXIgZnVuY3Rpb249J2xldmVsLW1lbWJlcnMnIGxldmVsPSdbU2VnbWVudF0nIC8-CiAgICAgICAgPC9ncm91cGZpbHRlcj4KICAgICAgPC9ncm91cD4KICAgICAgPGNvbHVtbiBjYXB0aW9uPSdBY3Rpb24gKE9yZGVyIFByb2ZpdGFibGU_LE1PTlRIKE9yZGVyIERhdGUpLFNlZ21lbnQpJyBkYXRhdHlwZT0ndHVwbGUnIGhpZGRlbj0ndHJ1ZScgbmFtZT0nW0FjdGlvbiAoT3JkZXIgUHJvZml0YWJsZT8sTU9OVEgoT3JkZXIgRGF0ZSksU2VnbWVudCldJyByb2xlPSdkaW1lbnNpb24nIHR5cGU9J25vbWluYWwnIHVzZXI6YXV0by1jb2x1bW49J3NoZWV0X2xpbmsnIC8-CiAgICAgIDxncm91cCBjYXB0aW9uPSdBY3Rpb24gKFBvc3RhbCBDb2RlLFN0YXRlL1Byb3ZpbmNlKScgaGlkZGVuPSd0cnVlJyBuYW1lPSdbQWN0aW9uIChQb3N0YWwgQ29kZSxTdGF0ZS9Qcm92aW5jZSldJyBuYW1lLXN0eWxlPSd1bnF1YWxpZmllZCcgdXNlcjphdXRvLWNvbHVtbj0nc2hlZXRfbGluayc-CiAgICAgICAgPGdyb3VwZmlsdGVyIGZ1bmN0aW9uPSdjcm9zc2pvaW4nPgogICAgICAgICAgPGdyb3VwZmlsdGVyIGZ1bmN0aW9uPSdsZXZlbC1tZW1iZXJzJyBsZXZlbD0nW25vbmU6UG9zdGFsIENvZGU6bmtdJyAvPgogICAgICAgICAgPGdyb3VwZmlsdGVyIGZ1bmN0aW9uPSdsZXZlbC1tZW1iZXJzJyBsZXZlbD0nW1N0YXRlL1Byb3ZpbmNlXScgLz4KICAgICAgICA8L2dyb3VwZmlsdGVyPgogICAgICA8L2dyb3VwPgogICAgICA8Y29sdW1uIGNhcHRpb249J0FjdGlvbiAoUG9zdGFsIENvZGUsU3RhdGUvUHJvdmluY2UpJyBkYXRhdHlwZT0ndHVwbGUnIGhpZGRlbj0ndHJ1ZScgbmFtZT0nW0FjdGlvbiAoUG9zdGFsIENvZGUsU3RhdGUvUHJvdmluY2UpXScgcm9sZT0nZGltZW5zaW9uJyB0eXBlPSdub21pbmFsJyB1c2VyOmF1dG8tY29sdW1uPSdzaGVldF9saW5rJyAvPgogICAgICA8Z3JvdXAgY2FwdGlvbj0nQWN0aW9uIChTdGF0ZS9Qcm92aW5jZSknIGhpZGRlbj0ndHJ1ZScgbmFtZT0nW0FjdGlvbiAoU3RhdGUvUHJvdmluY2UpXScgbmFtZS1zdHlsZT0ndW5xdWFsaWZpZWQnIHVzZXI6YXV0by1jb2x1bW49J3NoZWV0X2xpbmsnPgogICAgICAgIDxncm91cGZpbHRlciBmdW5jdGlvbj0nY3Jvc3Nqb2luJz4KICAgICAgICAgIDxncm91cGZpbHRlciBmdW5jdGlvbj0nbGV2ZWwtbWVtYmVycycgbGV2ZWw9J1tTdGF0ZS9Qcm92aW5jZV0nIC8-CiAgICAgICAgPC9ncm91cGZpbHRlcj4KICAgICAgPC9ncm91cD4KICAgICAgPGNvbHVtbiBjYXB0aW9uPSdBY3Rpb24gKFN0YXRlL1Byb3ZpbmNlKScgZGF0YXR5cGU9J3R1cGxlJyBoaWRkZW49J3RydWUnIG5hbWU9J1tBY3Rpb24gKFN0YXRlL1Byb3ZpbmNlKV0nIHJvbGU9J2RpbWVuc2lvbicgdHlwZT0nbm9taW5hbCcgdXNlcjphdXRvLWNvbHVtbj0nc2hlZXRfbGluaycgLz4KICAgICAgPGNvbHVtbi1pbnN0YW5jZSBjb2x1bW49J1tDYWxjdWxhdGlvbl85MDYwMTIyMTA0OTQ3NDcxXScgZGVyaXZhdGlvbj0nTm9uZScgbmFtZT0nW25vbmU6Q2FsY3VsYXRpb25fOTA2MDEyMjEwNDk0NzQ3MTpua10nIHBpdm90PSdrZXknIHR5cGU9J25vbWluYWwnIC8-CiAgICAgIDxjb2x1bW4taW5zdGFuY2UgY29sdW1uPSdbT3JkZXIgRGF0ZV0nIGRlcml2YXRpb249J05vbmUnIG5hbWU9J1tub25lOk9yZGVyIERhdGU6cWtdJyBwaXZvdD0na2V5JyB0eXBlPSdxdWFudGl0YXRpdmUnIC8-CiAgICAgIDxjb2x1bW4taW5zdGFuY2UgY29sdW1uPSdbUmVnaW9uXScgZGVyaXZhdGlvbj0nTm9uZScgbmFtZT0nW25vbmU6UmVnaW9uOm5rXScgcGl2b3Q9J2tleScgdHlwZT0nbm9taW5hbCcgLz4KICAgICAgPGNvbHVtbi1pbnN0YW5jZSBjb2x1bW49J1tDYWxjdWxhdGlvbl85OTIxMTAzMTQ0MTAzNzQzXScgZGVyaXZhdGlvbj0nVXNlcicgbmFtZT0nW3VzcjpDYWxjdWxhdGlvbl85OTIxMTAzMTQ0MTAzNzQzOnFrXScgcGl2b3Q9J2tleScgdHlwZT0ncXVhbnRpdGF0aXZlJyAvPgogICAgPC9kYXRhc291cmNlPgogIDwvZGF0YXNvdXJjZXM-CiAgPHdvcmtzaGVldCBuYW1lPSdUb3RhbCBTYWxlcyc-CiAgICA8ZmlsdGVyIGNsYXNzPSdjYXRlZ29yaWNhbCcgY29sdW1uPSdbZmVkZXJhdGVkLjEwbm5rOGQxdmdtdzhxMTd5dTc2dTA2cG5iY2pdLltBY3Rpb24gKFN0YXRlL1Byb3ZpbmNlKV0nPgogICAgICA8Z3JvdXBmaWx0ZXIgZnVuY3Rpb249J21lbWJlcicgbGV2ZWw9J1tTdGF0ZS9Qcm92aW5jZV0nIG1lbWJlcj0nJnF1b3Q7VGV4YXMmcXVvdDsnIHVzZXI6dWktYWN0aW9uLWZpbHRlcj0nW0FjdGlvbjFdJyB1c2VyOnVpLWRvbWFpbj0nZGF0YWJhc2UnIHVzZXI6dWktZW51bWVyYXRpb249J2luY2x1c2l2ZScgdXNlcjp1aS1tYXJrZXI9J2VudW1lcmF0ZScgLz4KICAgIDwvZmlsdGVyPgogICAgPGZpbHRlciBjbGFzcz0nY2F0ZWdvcmljYWwnIGNvbHVtbj0nW2ZlZGVyYXRlZC4xMG5uazhkMXZnbXc4cTE3eXU3NnUwNnBuYmNqXS5bbm9uZTpSZWdpb246bmtdJyBmaWx0ZXItZ3JvdXA9JzE0Jz4KICAgICAgPGdyb3VwZmlsdGVyIGZ1bmN0aW9uPSdtZW1iZXInIGxldmVsPSdbbm9uZTpSZWdpb246bmtdJyBtZW1iZXI9JyZxdW90O0NlbnRyYWwmcXVvdDsnIHVzZXI6dWktZG9tYWluPSdkYXRhYmFzZScgdXNlcjp1aS1lbnVtZXJhdGlvbj0naW5jbHVzaXZlJyB1c2VyOnVpLW1hcmtlcj0nZW51bWVyYXRlJyAvPgogICAgPC9maWx0ZXI-CiAgICA8dGFibGUgLz4KICA8L3dvcmtzaGVldD4KICA8d29ya3NoZWV0IG5hbWU9J1NhbGUgTWFwJz4KICAgIDxmaWx0ZXIgY2xhc3M9J2NhdGVnb3JpY2FsJyBjb2x1bW49J1tmZWRlcmF0ZWQuMTBubms4ZDF2Z213OHExN3l1NzZ1MDZwbmJjal0uW25vbmU6UmVnaW9uOm5rXScgZmlsdGVyLWdyb3VwPScxNCc-CiAgICAgIDxncm91cGZpbHRlciBmdW5jdGlvbj0nbWVtYmVyJyBsZXZlbD0nW25vbmU6UmVnaW9uOm5rXScgbWVtYmVyPScmcXVvdDtDZW50cmFsJnF1b3Q7JyB1c2VyOnVpLWRvbWFpbj0nZGF0YWJhc2UnIHVzZXI6dWktZW51bWVyYXRpb249J2luY2x1c2l2ZScgdXNlcjp1aS1tYXJrZXI9J2VudW1lcmF0ZScgLz4KICAgIDwvZmlsdGVyPgogICAgPHRhYmxlIC8-CiAgPC93b3Jrc2hlZXQ-CiAgPHdvcmtzaGVldCBuYW1lPSdTYWxlcyBieSBTZWdtZW50Jz4KICAgIDxmaWx0ZXIgY2xhc3M9J2NhdGVnb3JpY2FsJyBjb2x1bW49J1tmZWRlcmF0ZWQuMTBubms4ZDF2Z213OHExN3l1NzZ1MDZwbmJjal0uW0FjdGlvbiAoU3RhdGUvUHJvdmluY2UpXSc-CiAgICAgIDxncm91cGZpbHRlciBmdW5jdGlvbj0nbWVtYmVyJyBsZXZlbD0nW1N0YXRlL1Byb3ZpbmNlXScgbWVtYmVyPScmcXVvdDtUZXhhcyZxdW90OycgdXNlcjp1aS1hY3Rpb24tZmlsdGVyPSdbQWN0aW9uMV0nIHVzZXI6dWktZG9tYWluPSdkYXRhYmFzZScgdXNlcjp1aS1lbnVtZXJhdGlvbj0naW5jbHVzaXZlJyB1c2VyOnVpLW1hcmtlcj0nZW51bWVyYXRlJyAvPgogICAgPC9maWx0ZXI-CiAgICA8ZmlsdGVyIGNsYXNzPSdjYXRlZ29yaWNhbCcgY29sdW1uPSdbZmVkZXJhdGVkLjEwbm5rOGQxdmdtdzhxMTd5dTc2dTA2cG5iY2pdLltub25lOlJlZ2lvbjpua10nIGZpbHRlci1ncm91cD0nMTQnPgogICAgICA8Z3JvdXBmaWx0ZXIgZnVuY3Rpb249J21lbWJlcicgbGV2ZWw9J1tub25lOlJlZ2lvbjpua10nIG1lbWJlcj0nJnF1b3Q7Q2VudHJhbCZxdW90OycgdXNlcjp1aS1kb21haW49J2RhdGFiYXNlJyB1c2VyOnVpLWVudW1lcmF0aW9uPSdpbmNsdXNpdmUnIHVzZXI6dWktbWFya2VyPSdlbnVtZXJhdGUnIC8-CiAgICA8L2ZpbHRlcj4KICAgIDx0YWJsZSAvPgogIDwvd29ya3NoZWV0PgogIDx3b3Jrc2hlZXQgbmFtZT0nU2FsZXMgYnkgUHJvZHVjdCc-CiAgICA8ZmlsdGVyIGNsYXNzPSdjYXRlZ29yaWNhbCcgY29sdW1uPSdbZmVkZXJhdGVkLjEwbm5rOGQxdmdtdzhxMTd5dTc2dTA2cG5iY2pdLltBY3Rpb24gKFN0YXRlL1Byb3ZpbmNlKV0nPgogICAgICA8Z3JvdXBmaWx0ZXIgZnVuY3Rpb249J21lbWJlcicgbGV2ZWw9J1tTdGF0ZS9Qcm92aW5jZV0nIG1lbWJlcj0nJnF1b3Q7VGV4YXMmcXVvdDsnIHVzZXI6dWktYWN0aW9uLWZpbHRlcj0nW0FjdGlvbjFdJyB1c2VyOnVpLWRvbWFpbj0nZGF0YWJhc2UnIHVzZXI6dWktZW51bWVyYXRpb249J2luY2x1c2l2ZScgdXNlcjp1aS1tYXJrZXI9J2VudW1lcmF0ZScgLz4KICAgIDwvZmlsdGVyPgogICAgPGZpbHRlciBjbGFzcz0nY2F0ZWdvcmljYWwnIGNvbHVtbj0nW2ZlZGVyYXRlZC4xMG5uazhkMXZnbXc4cTE3eXU3NnUwNnBuYmNqXS5bbm9uZTpSZWdpb246bmtdJyBmaWx0ZXItZ3JvdXA9JzE0Jz4KICAgICAgPGdyb3VwZmlsdGVyIGZ1bmN0aW9uPSdtZW1iZXInIGxldmVsPSdbbm9uZTpSZWdpb246bmtdJyBtZW1iZXI9JyZxdW90O0NlbnRyYWwmcXVvdDsnIHVzZXI6dWktZG9tYWluPSdkYXRhYmFzZScgdXNlcjp1aS1lbnVtZXJhdGlvbj0naW5jbHVzaXZlJyB1c2VyOnVpLW1hcmtlcj0nZW51bWVyYXRlJyAvPgogICAgPC9maWx0ZXI-CiAgICA8dGFibGUgLz4KICA8L3dvcmtzaGVldD4KICA8d2luZG93cz4KICAgIDx3aW5kb3cgY2xhc3M9J3dvcmtzaGVldCcgbmFtZT0nU2FsZSBNYXAnPgogICAgICA8c2VsZWN0aW9uLWNvbGxlY3Rpb24-CiAgICAgICAgPHR1cGxlLXNlbGVjdGlvbj4KICAgICAgICAgIDx0dXBsZS1yZWZlcmVuY2U-CiAgICAgICAgICAgIDx0dXBsZS1kZXNjcmlwdG9yPgogICAgICAgICAgICAgIDxwYW5lLWRlc2NyaXB0b3I-CiAgICAgICAgICAgICAgICA8eC1maWVsZHM-CiAgICAgICAgICAgICAgICAgIDxmaWVsZD5bZmVkZXJhdGVkLjEwbm5rOGQxdmdtdzhxMTd5dTc2dTA2cG5iY2pdLltMb25naXR1ZGUgKGdlbmVyYXRlZCldPC9maWVsZD4KICAgICAgICAgICAgICAgIDwveC1maWVsZHM-CiAgICAgICAgICAgICAgICA8eS1maWVsZHM-CiAgICAgICAgICAgICAgICAgIDxmaWVsZD5bZmVkZXJhdGVkLjEwbm5rOGQxdmdtdzhxMTd5dTc2dTA2cG5iY2pdLltMYXRpdHVkZSAoZ2VuZXJhdGVkKV08L2ZpZWxkPgogICAgICAgICAgICAgICAgPC95LWZpZWxkcz4KICAgICAgICAgICAgICA8L3BhbmUtZGVzY3JpcHRvcj4KICAgICAgICAgICAgICA8Y29sdW1ucz4KICAgICAgICAgICAgICAgIDxmaWVsZD5bZmVkZXJhdGVkLjEwbm5rOGQxdmdtdzhxMTd5dTc2dTA2cG5iY2pdLltub25lOkNvdW50cnkvUmVnaW9uOm5rXTwvZmllbGQ-CiAgICAgICAgICAgICAgICA8ZmllbGQ-W2ZlZGVyYXRlZC4xMG5uazhkMXZnbXc4cTE3eXU3NnUwNnBuYmNqXS5bbm9uZTpTdGF0ZS9Qcm92aW5jZTpua108L2ZpZWxkPgogICAgICAgICAgICAgICAgPGZpZWxkPltmZWRlcmF0ZWQuMTBubms4ZDF2Z213OHExN3l1NzZ1MDZwbmJjal0uW0dlb21ldHJ5IChnZW5lcmF0ZWQpXTwvZmllbGQ-CiAgICAgICAgICAgICAgICA8ZmllbGQ-W2ZlZGVyYXRlZC4xMG5uazhkMXZnbXc4cTE3eXU3NnUwNnBuYmNqXS5bTGF0aXR1ZGUgKGdlbmVyYXRlZCldPC9maWVsZD4KICAgICAgICAgICAgICAgIDxmaWVsZD5bZmVkZXJhdGVkLjEwbm5rOGQxdmdtdzhxMTd5dTc2dTA2cG5iY2pdLltMb25naXR1ZGUgKGdlbmVyYXRlZCldPC9maWVsZD4KICAgICAgICAgICAgICAgIDxmaWVsZD5bZmVkZXJhdGVkLjEwbm5rOGQxdmdtdzhxMTd5dTc2dTA2cG5iY2pdLlt1c3I6Q2FsY3VsYXRpb25fOTkyMTEwMzE0NDEwMzc0Mzpxa108L2ZpZWxkPgogICAgICAgICAgICAgIDwvY29sdW1ucz4KICAgICAgICAgICAgPC90dXBsZS1kZXNjcmlwdG9yPgogICAgICAgICAgICA8dHVwbGU-CiAgICAgICAgICAgICAgPHZhbHVlPiZxdW90O1VuaXRlZCBTdGF0ZXMmcXVvdDs8L3ZhbHVlPgogICAgICAgICAgICAgIDx2YWx1ZT4mcXVvdDtUZXhhcyZxdW90OzwvdmFsdWU-CiAgICAgICAgICAgICAgPHZhbHVlPiZxdW90O01VTFRJUE9MWUdPTigoKC05Ny4xNDYzIDI1Ljk1NTYsLTk3LjIwOCAyNS45NjM2LC05Ny4yNzcyIDI1LjkzNTQsLTk3LjM0ODkgMjUuOTMwOCwtOTcuMzc0NCAyNS45MDc0LC05Ny4zNTc2IDI1Ljg4NjksLTk3LjM3MzcgMjUuODQsLTk3LjQ1MzkgMjUuODU0NCwtOTcuNDU2NCAyNS44ODM4LC05Ny41MjE4IDI1Ljg4NjUsLTk3LjU0ODIgMjUuOTM1NSwtOTcuNTgyNiAyNS45Mzc5LC05Ny42NDQ5IDI2LjAyNzUsLTk3LjcwNjcgMjYuMDM3NCwtOTcuNzY0MSAyNi4wMjg2LC05Ny44MDEzIDI2LjA2LC05Ny44MzU1IDI2LjA0NjksLTk3Ljg2MTkgMjYuMDY5OCwtOTcuOTA5OSAyNi4wNTY5LC05Ny45NjYxIDI2LjA1MTksLTk4LjAzMDggMjYuMDY1LC05OC4wNzAxIDI2LjAzNzksLTk4LjA3OTEgMjYuMDcwNSwtOTguMTM1NSAyNi4wNzIsLTk4LjE1NzUgMjYuMDU0NCwtOTguMTk3IDI2LjA1NjIsLTk4LjMwNjUgMjYuMTA0MywtOTguMzM1MiAyNi4xMzc2LC05OC4zODY3IDI2LjE1NzksLTk4LjQ0NDMgMjYuMjAxMiwtOTguNDQ1MiAyNi4yMjQ2LC05OC41MDYxIDI2LjIwOSwtOTguNTIyNCAyNi4yMjA5LC05OC41NjE1IDI2LjIyNDUsLTk4LjU4NjcgMjYuMjU3NSwtOTguNjU0MiAyNi4yMzYsLTk4LjY3OTQgMjYuMjQ5MiwtOTguNzUzOCAyNi4zMzE3LC05OC43ODk4IDI2LjMzMTYsLTk4LjgyNjkgMjYuMzY5NiwtOTguODk2MiAyNi4zNTMyLC05OC45MjkyIDI2LjM5MzIsLTk4Ljk0NjUgMjYuMzY5OSwtOTguOTc0MiAyNi40MDExLC05OS4wMTA2IDI2LjM5MjEsLTk5LjA0IDI2LjQxMjksLTk5LjA5NDggMjYuNDEwOSwtOTkuMTEwOSAyNi40MjYzLC05OS4wOTE2IDI2LjQ3NjQsLTk5LjEyODQgMjYuNTI1NSwtOTkuMTY2NyAyNi41MzYxLC05OS4xNjk0IDI2LjU3MTcsLTk5LjIwMDIgMjYuNjU1OCwtOTkuMjA4OSAyNi43MjQ4LC05OS4yNCAyNi43NDU5LC05OS4yNDI0IDI2Ljc4ODMsLTk5LjI2ODYgMjYuODQzMiwtOTkuMzI4OSAyNi44ODAyLC05OS4zMjE4IDI2LjkwNjgsLTk5LjM4ODMgMjYuOTQ0MiwtOTkuMzc3MyAyNi45NzM4LC05OS40MTU1IDI3LjAxNzIsLTk5LjQ0NjUgMjcuMDIzLC05OS40NTEgMjcuMDY2OCwtOTkuNDMwMyAyNy4wOTQ5LC05OS40Mzk2IDI3LjE1MjEsLTk5LjQyNjQgMjcuMTc4MywtOTkuNDUzOCAyNy4yNjUxLC05OS40OTY2IDI3LjI3MTcsLTk5LjQ5NSAyNy4zMDM5LC05OS41Mzc5IDI3LjMxNzUsLTk5LjUwNDQgMjcuMzM5OSwtOTkuNDgwNCAyNy40ODE2LC05OS41MjgzIDI3LjQ5ODksLTk5LjUxMTEgMjcuNTY0NSwtOTkuNTU2OCAyNy42MTQzLC05OS41OCAyNy42MDIzLC05OS41OTQgMjcuNjM4NiwtOTkuNjM4OSAyNy42MjY4LC05OS42OTEzIDI3LjY2ODcsLTk5LjcyODQgMjcuNjc5MywtOTkuNzcwNyAyNy43MzIxLC05OS44MzMxIDI3Ljc2MjksLTk5Ljg3MjMgMjcuNzk1MywtOTkuODgxMyAyNy44NDk2LC05OS45MDE1IDI3Ljg2NDIsLTk5LjkwMDEgMjcuOTEyMSwtOTkuOTM3MSAyNy45NDA1LC05OS45MzE4IDI3Ljk4MSwtOTkuOTg5OCAyNy45OTI5LC0xMDAuMDE5IDI4LjA2NjQsLTEwMC4wNTYxIDI4LjA5MTMsLTEwMC4wODY5IDI4LjE0NjgsLTEwMC4xNTkyIDI4LjE2NzYsLTEwMC4yMTIyIDI4LjE5NjgsLTEwMC4yMjM2IDI4LjIzNTIsLTEwMC4yNTc4IDI4LjI0MDMsLTEwMC4yOTM1IDI4LjI3ODUsLTEwMC4yODg2IDI4LjMxNywtMTAwLjM0OTMgMjguNDAxNCwtMTAwLjMzNjIgMjguNDMwMiwtMTAwLjM2ODIgMjguNDc4OSwtMTAwLjMzNDcgMjguNTAwMywtMTAwLjM4NyAyOC41MTQsLTEwMC40MTA0IDI4LjU1NDMsLTEwMC4zOTg1IDI4LjU4NTIsLTEwMC40NDc2IDI4LjYxMDEsLTEwMC40NDU3IDI4LjY0MDYsLTEwMC41MDA0IDI4LjY2MiwtMTAwLjUwNzYgMjguNzQwNiwtMTAwLjUzMzYgMjguNzYxMSwtMTAwLjU0NjYgMjguODI0OSwtMTAwLjU3MDUgMjguODI2MywtMTAwLjU5MTUgMjguODg5MywtMTAwLjY0ODggMjguOTQxLC0xMDAuNjQ1OSAyOC45ODY0LC0xMDAuNjY3NSAyOS4wODQzLC0xMDAuNzc1OSAyOS4xNzMzLC0xMDAuNzY1OSAyOS4xODc1LC0xMDAuNzk0OCAyOS4yNDE2LC0xMDAuODc2MSAyOS4yNzk2LC0xMDAuODg2OCAyOS4zMDc4LC0xMDAuOTUwNyAyOS4zNDc3LC0xMDEuMDA2NiAyOS4zNjYsLTEwMS4wNjAyIDI5LjQ1ODcsLTEwMS4xNTE5IDI5LjQ3NywtMTAxLjE3MzggMjkuNTE0NiwtMTAxLjI2MTIgMjkuNTM2OCwtMTAxLjI0MSAyOS41NjUsLTEwMS4yNjIyIDI5LjYzMDYsLTEwMS4yOTEgMjkuNTcxNSwtMTAxLjMxMTYgMjkuNTg1MSwtMTAxLjMgMjkuNjQwNywtMTAxLjMxNDEgMjkuNjU5MSwtMTAxLjM2MzIgMjkuNjUyNiwtMTAxLjM3NTQgMjkuNzAxOCwtMTAxLjQxNTYgMjkuNzQ2NSwtMTAxLjQ0ODkgMjkuNzUwNywtMTAxLjQ1NTggMjkuNzg4LC0xMDEuNTM5MiAyOS43NjE4LC0xMDEuNTQxOSAyOS44MTA4LC0xMDEuNTc1OCAyOS43NjkzLC0xMDEuNzEwNiAyOS43NjE3LC0xMDEuNzYwOSAyOS43ODIxLC0xMDEuODA2MiAyOS43ODA4LC0xMDEuODUzNCAyOS44MDc5LC0xMDEuOTMzNSAyOS43ODUxLC0xMDIuMDM4MyAyOS44MDMxLC0xMDIuMDQ5IDI5Ljc4NTYsLTEwMi4xMTYxIDI5Ljc5MjUsLTEwMi4xOTQ5IDI5LjgzNzEsLTEwMi4zMjA3IDI5Ljg3ODksLTEwMi4zNjQ4IDI5Ljg0NDMsLTEwMi4zODk3IDI5Ljc4MTksLTEwMi41MTc0IDI5Ljc4MzgsLTEwMi41NDggMjkuNzQ1LC0xMDIuNTcyNCAyOS43NTYxLC0xMDIuNjIzIDI5LjczNjQsLTEwMi42NzQ5IDI5Ljc0NDMsLTEwMi42OTM0IDI5LjY3NzIsLTEwMi43NDIyIDI5LjYzMDcsLTEwMi43NDUgMjkuNTkzMiwtMTAyLjc2ODMgMjkuNTk0NywtMTAyLjc3MTQgMjkuNTQ4OSwtMTAyLjgwODQgMjkuNTIyOSwtMTAyLjgzMSAyOS40NDQzLC0xMDIuODI0NyAyOS4zOTczLC0xMDIuODM5OSAyOS4zNjA2LC0xMDIuODc4NiAyOS4zNTM5LC0xMDIuOTAzMiAyOS4yNTQsLTEwMi44NzA2IDI5LjIzNjksLTEwMi44OTAxIDI5LjIwODgsLTEwMi45NTAyIDI5LjE3MzYsLTEwMi45NzM4IDI5LjE4NTUsLTEwMy4wMzI1IDI5LjEwNDcsLTEwMy4wNzUzIDI5LjA5MjMsLTEwMy4xMDA3IDI5LjA2MDIsLTEwMy4xMTUzIDI4Ljk4NTMsLTEwMy4xNTMzIDI4Ljk3MTgsLTEwMy4yMjc0IDI4Ljk5MTUsLTEwMy4yNzkyIDI4Ljk3NzcsLTEwMy4yOTg2IDI5LjAwNjgsLTEwMy40MzM3IDI5LjA0NSwtMTAzLjQ1MDYgMjkuMDcyOCwtMTAzLjU1NDUgMjkuMTU4NSwtMTAzLjcxOTIgMjkuMTgxNCwtMTAzLjc5MjcgMjkuMjYyMywtMTAzLjgxNDcgMjkuMjczOCwtMTAzLjk2OTYgMjkuMjk3OCwtMTA0LjAxOTkgMjkuMzEyMSwtMTA0LjEwNjUgMjkuMzczMSwtMTA0LjE2MyAyOS4zOTE5LC0xMDQuMjE3NSAyOS40NTU5LC0xMDQuMjA5IDI5LjQ4MSwtMTA0LjI2NDIgMjkuNTE0LC0xMDQuMzM4MSAyOS41MiwtMTA0LjQwMDYgMjkuNTczLC0xMDQuNDY2OSAyOS42MDk2LC0xMDQuNTQ0MiAyOS42ODE2LC0xMDQuNTY2MSAyOS43NzE0LC0xMDQuNjI5NSAyOS44NTIzLC0xMDQuNjgyNSAyOS45MzQ4LC0xMDQuNjc0IDI5Ljk1NjcsLTEwNC43MDYzIDMwLjA0OTcsLTEwNC42ODc5IDMwLjA3MzksLTEwNC42OTY2IDMwLjEzNDQsLTEwNC42ODcyIDMwLjE3OSwtMTA0LjcwNjggMzAuMjM1NCwtMTA0Ljc2MzIgMzAuMjc0NCwtMTA0Ljc3MzUgMzAuMzAyNywtMTA0LjgyMjYgMzAuMzUwMywtMTA0LjgxNjMgMzAuMzc0MywtMTA0Ljg1OTUgMzAuMzkxMSwtMTA0Ljg2OTQgMzAuNDc3MywtMTA0Ljg4MjQgMzAuNTMyMywtMTA0LjkxOSAzMC41OTc3LC0xMDQuOTcyMSAzMC42MTAzLC0xMDUuMDA2NSAzMC42ODU4LC0xMDUuMDYyNSAzMC42ODY2LC0xMDUuMTE4MSAzMC43NDk1LC0xMDUuMTYxNyAzMC43NTIxLC0xMDUuMjE3NyAzMC44MDYsLTEwNS4yNTYxIDMwLjc5NDUsLTEwNS4yOTE3IDMwLjgyNjEsLTEwNS4zNjE1IDMwLjg1MDMsLTEwNS4zOTU2IDMwLjg0OSwtMTA1LjQxMzUgMzAuODk5OCwtMTA1LjQ5ODggMzAuOTUwMywtMTA1LjU3ODYgMzEuMDIwNiwtMTA1LjU4NTEgMzEuMDU2OSwtMTA1LjY0NjcgMzEuMTEzOSwtMTA1Ljc3MzkgMzEuMTY4LC0xMDUuODE4OCAzMS4yMzA3LC0xMDUuODc0NyAzMS4yOTEzLC0xMDUuOTMxMiAzMS4zMTI3LC0xMDUuOTUzOSAzMS4zNjQ3LC0xMDYuMDE2MiAzMS4zOTM1LC0xMDYuMDc1MyAzMS4zOTc2LC0xMDYuMTkxMSAzMS40NTk5LC0xMDYuMjE5NiAzMS40ODE2LC0xMDYuMjQ1MiAzMS41MzkxLC0xMDYuMjgwMSAzMS41NjE1LC0xMDYuMzA3OSAzMS42Mjk1LC0xMDYuMzgxMSAzMS43MzIxLC0xMDYuNDUxNCAzMS43NjQ0LC0xMDYuNDkwNSAzMS43NDg5LC0xMDYuNTI4MiAzMS43ODMxLC0xMDYuNTQ3MSAzMS44MDczLC0xMDYuNjA1MyAzMS44Mjc3LC0xMDYuNjQ1NSAzMS44OTg3LC0xMDYuNjExOCAzMS45MiwtMTA2LjYxODUgMzIuMDAwNSwtMTA1Ljk5OCAzMi4wMDIzLC0xMDUuMjUwNSAzMi4wMDAzLC0xMDQuODQ3OCAzMi4wMDA1LC0xMDQuMDI0NSAzMiwtMTAzLjA2NDQgMzIuMDAwNSwtMTAzLjA2NDcgMzIuOTU5MSwtMTAzLjA1NjcgMzMuMzg4NCwtMTAzLjA0NCAzMy45NzQ2LC0xMDMuMDQyNCAzNS4xODMxLC0xMDMuMDQwOCAzNi4wNTUyLC0xMDMuMDQxOSAzNi41MDA0LC0xMDMuMDAyNCAzNi41MDA0LC0xMDIuMDMyMyAzNi41MDA2LC0xMDEuNjIzOSAzNi40OTk1LC0xMDEuMDg1MiAzNi40OTkyLC0xMDAuMDAwNCAzNi40OTk3LC0xMDAuMDAwNCAzNC43NDY1LC05OS45OTc1IDM0LjU2MDYsLTk5LjkyMzIgMzQuNTc0NiwtOTkuODQ0NiAzNC41MDY5LC05OS43NTM0IDM0LjQyMDksLTk5LjY5NDUgMzQuMzc4MiwtOTkuNiAzNC4zNzQ3LC05OS41Nzk4IDM0LjQxNjksLTk5LjUxNzYgMzQuNDE0NSwtOTkuNDMzNSAzNC4zNzAyLC05OS4zOTg3IDM0LjM3NTgsLTk5LjM5NTIgMzQuNDQyLC05OS4zNzU2IDM0LjQ1ODgsLTk5LjMyMDEgMzQuNDA5MywtOTkuMjYxMyAzNC40MDM1LC05OS4yMTA4IDM0LjMzNjgsLTk5LjE4OTggMzQuMjE0NCwtOTkuMDk1MyAzNC4yMTE4LC05OS4wNDM0IDM0LjE5ODIsLTk4Ljk5MTcgMzQuMjIxNCwtOTguOTUyNCAzNC4yMTI1LC05OC44NjAxIDM0LjE0OTksLTk4LjgzMTEgMzQuMTYyMiwtOTguNzY2NyAzNC4xMzY4LC05OC42OTAxIDM0LjEzMzIsLTk4LjY0ODEgMzQuMTY0NCwtOTguNjEwMiAzNC4xNTcxLC05OC41NjAyIDM0LjEzMzIsLTk4LjQ4NyAzNC4wNjI5LC05OC40MjM1IDM0LjA4MjgsLTk4LjM5ODQgMzQuMTI4NSwtOTguMzY0IDM0LjE1NzEsLTk4LjMwMDIgMzQuMTM0NiwtOTguMjMyNSAzNC4xMzQ2LC05OC4xNjg4IDM0LjExNDMsLTk4LjEzOTEgMzQuMTQxOSwtOTguMTAxOSAzNC4xNDY4LC05OC4wOTA1IDM0LjEyMjUsLTk4LjEyMDIgMzQuMDcyMSwtOTguMDgzOCAzNC4wNDE3LC05OC4wODQ0IDM0LjAwMjksLTk4LjAxNjMgMzMuOTk0MSwtOTcuOTc0MiAzNC4wMDY3LC05Ny45NDY4IDMzLjk5MDksLTk3Ljk3MTIgMzMuOTM3MiwtOTcuOTU3MiAzMy45MTQ1LC05Ny45Nzc5IDMzLjg4OTksLTk3Ljg3MTQgMzMuODQ5LC05Ny44MzQzIDMzLjg1NzcsLTk3Ljc2MyAzMy45MzQxLC05Ny43MzIzIDMzLjkzNjcsLTk3LjY4NzcgMzMuOTg3MiwtOTcuNjYxNSAzMy45OTA4LC05Ny41ODg4IDMzLjk1MTksLTk3LjU4OTMgMzMuOTAzOSwtOTcuNTYwOSAzMy44OTk2LC05Ny40ODQyIDMzLjkxNTQsLTk3LjQ1MTEgMzMuODkxNywtOTcuNDYyOSAzMy44NDI5LC05Ny40NDM5IDMzLjgyMzcsLTk3LjM3MjkgMzMuODE5NSwtOTcuMzMxOSAzMy44ODQ1LC05Ny4yNTU2IDMzLjg2MzcsLTk3LjI0NjIgMzMuOTAwMywtOTcuMjEwMyAzMy45MTU5LC05Ny4xODU1IDMzLjkwMDcsLTk3LjE2NjggMzMuODQwNCwtOTcuMTk3NCAzMy44Mjk4LC05Ny4xOTM0IDMzLjc2MDYsLTk3LjE1MTMgMzMuNzIyNiwtOTcuMTExMSAzMy43MTk0LC05Ny4wODg3IDMzLjczODcsLTk3LjA4OCAzMy44MDg3LC05Ny4wNDggMzMuODE3OSwtOTcuMDg3MyAzMy44Mzk4LC05Ny4wNTczIDMzLjg1NjksLTk3LjAyMzUgMzMuODQ0NSwtOTYuOTg1NiAzMy44ODY1LC05Ni45OTYzIDMzLjk0MjcsLTk2LjkzNDggMzMuOTU0NSwtOTYuODk5NCAzMy45MzM3LC05Ni44ODMgMzMuODY4LC05Ni44NTA2IDMzLjg0NzIsLTk2LjgzMjIgMzMuODc0OCwtOTYuNzc5NiAzMy44NTc5LC05Ni43Njk0IDMzLjgyNzUsLTk2LjcxMzcgMzMuODMxMywtOTYuNjkwNyAzMy44NSwtOTYuNjczNCAzMy45MTIzLC05Ni41ODg1IDMzLjg5NSwtOTYuNjI5IDMzLjg1MjQsLTk2LjU3MzIgMzMuODE5MiwtOTYuNTMyOSAzMy44MjMsLTk2LjUwMDcgMzMuNzcyNiwtOTYuNDIyNiAzMy43NzYsLTk2LjM3OTUgMzMuNzI1OCwtOTYuMzYyMiAzMy42OTE4LC05Ni4zMTg0IDMzLjY5NzEsLTk2LjMwMyAzMy43NTA5LC05Ni4yNzczIDMzLjc2OTcsLTk2LjIzMDQgMzMuNzQ4NSwtOTYuMTc4MSAzMy43NjA1LC05Ni4xNDkyIDMzLjgzNzEsLTk2LjEwMTUgMzMuODQ2NywtOTYuMDQ4OCAzMy44MzY1LC05NS45NDE5IDMzLjg2MSwtOTUuOTMyMSAzMy44ODY1LC05NS44NDMzIDMzLjgzODMsLTk1LjgwNDUgMzMuODYyMiwtOTUuNzY3OSAzMy44NDY4LC05NS43NTY2IDMzLjg5MiwtOTUuNjk0OSAzMy44ODY4LC05NS42Njg2IDMzLjkwNywtOTUuNjI3MyAzMy45MDc4LC05NS41OTc1IDMzLjk0MjMsLTk1LjU1NzcgMzMuOTMwNCwtOTUuNTQzNCAzMy44ODA1LC05NS40NTk4IDMzLjg4OCwtOTUuNDM4MiAzMy44NjcxLC05NS4zMTA1IDMzLjg3NzIsLTk1LjI4MjIgMzMuODc1OSwtOTUuMjcxNCAzMy45MTI2LC05NS4yMTk0IDMzLjk2MTYsLTk1LjE1NTkgMzMuOTM2OCwtOTUuMTI5NiAzMy45MzY3LC05NS4xMTc2IDMzLjkwNDYsLTk1LjA4MjQgMzMuODc5OSwtOTUuMDYwMSAzMy45MDE5LC05NS4wNDkgMzMuODY0MSwtOTQuOTY4OSAzMy44NjA5LC05NC45NTM1IDMzLjgxNjUsLTk0LjkyMzMgMzMuODA4NywtOTQuOTExNSAzMy43Nzg0LC05NC44NDkzIDMzLjczOTYsLTk0LjgyMzQgMzMuNzY5MiwtOTQuODAyMyAzMy43MzI4LC05NC43NzEzIDMzLjc2MDcsLTk0Ljc0NjEgMzMuNzAzLC05NC42ODQ4IDMzLjY4NDQsLTk0LjY2NzkgMzMuNjk0NiwtOTQuNjM5MiAzMy42NjM3LC05NC42MjE0IDMzLjY4MjYsLTk0LjU5MDggMzMuNjQ1NiwtOTQuNTQ2NCAzMy42NiwtOTQuNTIwNCAzMy42MTc1LC05NC40ODU5IDMzLjYzNzksLTk0LjM4OTUgMzMuNTQ2NywtOTQuMzUzNiAzMy41NDQsLTk0LjM0NTUgMzMuNTY3MywtOTQuMzA5NiAzMy41NTE3LC05NC4yNzU5IDMzLjU1OCwtOTQuMjE5MiAzMy41NTYxLC05NC4xODQzIDMzLjU5NDYsLTk0LjE0NzQgMzMuNTY1MiwtOTQuMDgyNCAzMy41NzU3LC05NC4wNDM0IDMzLjU1MjMsLTk0LjA0MyAzMy4wMTkyLC05NC4wNDI3IDMxLjk5OTMsLTk0LjAxNTYgMzEuOTc5OSwtOTMuOTcwOCAzMS45MiwtOTMuOTI5OSAzMS45MTI3LC05My44OTY3IDMxLjg4NTMsLTkzLjg3NDggMzEuODIyMywtOTMuODIyNiAzMS43NzM2LC05My44MzY5IDMxLjc1MDIsLTkzLjc5NDUgMzEuNzAyMSwtOTMuODIxNyAzMS42NzQsLTkzLjgxODcgMzEuNjE0NiwtOTMuODM0OSAzMS41ODYyLC05My43ODUgMzEuNTI2LC05My43MTI1IDMxLjUxMzQsLTkzLjc0OTUgMzEuNDY4NywtOTMuNjkyNiAzMS40MzcyLC05My43MDQ5IDMxLjQxMDksLTkzLjY3NDEgMzEuMzk3NywtOTMuNjY5MSAzMS4zNjU0LC05My42ODc1IDMxLjMxMDgsLTkzLjU5ODQgMzEuMjMxMSwtOTMuNjAwMyAzMS4xNzYyLC05My41NTI2IDMxLjE4NTYsLTkzLjUzOTQgMzEuMTE1MiwtOTMuNTYzMiAzMS4wOTcsLTkzLjUyNzYgMzEuMDc0NSwtOTMuNTA4OSAzMS4wMjkzLC05My41NTYzIDMxLjAwNDEsLTkzLjU2ODQgMzAuOTY5MSwtOTMuNTMyMSAzMC45NTc5LC05My41MjYzIDMwLjkyOTcsLTkzLjU1ODYgMzAuOTEzMiwtOTMuNTUzNiAzMC44MzUxLC05My42MTQ4IDMwLjc1NiwtOTMuNjA3NyAzMC43MTU2LC05My42MzE1IDMwLjY3OCwtOTMuNjgzMSAzMC42NDA4LC05My42Nzg4IDMwLjU5ODYsLTkzLjcyNzUgMzAuNTc0NywtOTMuNzMzOCAzMC41MzE3LC05My42OTc4IDMwLjQ0MzgsLTkzLjc0MTcgMzAuNDAyMywtOTMuNzYyMyAzMC4zNTM3LC05My43NDIxIDMwLjMwMSwtOTMuNzA0NyAzMC4yODk5LC05My43MDcgMzAuMjQzNywtOTMuNzIxIDMwLjIxMDQsLTkzLjY5MjggMzAuMTM1MiwtOTMuNzMyOCAzMC4wODI5LC05My43MjI1IDMwLjA1MDksLTkzLjc1NTEgMzAuMDE1MywtOTMuODcxNyAyOS45ODEsLTkzLjg2OTIgMjkuOTM4LC05My45NTA2IDI5Ljg0OTMsLTkzLjk0NjYgMjkuNzgwMSwtOTMuODM3NyAyOS42NzksLTk0LjAxNDMgMjkuNjc5OCwtOTQuMzU0MyAyOS41NjEsLTk0LjQ5OTEgMjkuNTA2OCwtOTQuNDcwMiAyOS41NTcxLC05NC41NDU5IDI5LjU3MjUsLTk0Ljc2MjUgMjkuNTI0MSwtOTQuNzAzOSAyOS42MzI1LC05NC42OTU3IDI5Ljc1NjUsLTk0LjczODkgMjkuNzkwNiwtOTQuODE0MSAyOS43NTksLTk0Ljg3MjggMjkuNjcxNCwtOTQuOTMwMyAyOS42NzM3LC05NS4wMTY2IDI5LjcyMDUsLTk1LjA3MjYgMjkuODI2MiwtOTUuMDk1NSAyOS43NTc2LC05NC45ODMzIDI5LjY4MjMsLTk0Ljk5ODUgMjkuNjE2NCwtOTUuMDc4OSAyOS41MzUzLC05NS4wMTcgMjkuNTQ4LC05NC45MDk2IDI5LjQ5NjEsLTk0Ljk1MDQgMjkuNDY2NywtOTQuODg1NCAyOS4zODk3LC05NS4wNTc0IDI5LjIwMTMsLTk1LjE0OTYgMjkuMTgwNSwtOTUuMjM0MiAyOC45OTI2LC05NS4zODU2IDI4Ljg2NDYsLTk1LjUwNzIgMjguODI1NCwtOTUuNjUzNyAyOC43NDk5LC05NS42NzI3IDI4Ljc0OTUsLTk1Ljc4NCAyOC42Nzk0LC05NS45MTQ5IDI4LjYzODgsLTk1LjY3NzYgMjguNzQ5NCwtOTUuNzg1MyAyOC43NDcxLC05NS45MjM2IDI4LjcwMTUsLTk1Ljk2MDggMjguNjE1MiwtOTYuMzM1NSAyOC40MzgxLC05Ni4xNDYzIDI4LjU0MjcsLTk1Ljk5MDYgMjguNjAxNiwtOTYuMDM4OCAyOC42NTI4LC05Ni4xNTI0IDI4LjYxMzUsLTk2LjIzNTQgMjguNjQyNywtOTYuMjA3OCAyOC42OTgxLC05Ni4zMjI5IDI4LjY0MTksLTk2LjM4NiAyOC42NzQ4LC05Ni40Mjg0IDI4LjcwNzEsLTk2LjQzNDggMjguNjAzLC05Ni41NjE1IDI4LjY0NTQsLTk2LjU3MzYgMjguNzA1NSwtOTYuNjU5NiAyOC43MjI2LC05Ni42NjE0IDI4LjcwMjYsLTk2LjYxMjEgMjguNjM5NCwtOTYuNjM4NSAyOC41NzE5LC05Ni41NjY3IDI4LjU4MjUsLTk2LjQxNTMgMjguNDYzNywtOTYuNDMyMiAyOC40MzI1LC05Ni42NTAzIDI4LjMzMjUsLTk2LjcwODQgMjguNDA3NSwtOTYuNzg1NyAyOC40NDc2LC05Ni43ODMyIDI4LjQwMDQsLTk2Ljg1ODkgMjguNDE3NiwtOTYuNzkwNSAyOC4zMTkyLC05Ni44MDk1IDI4LjIxOTksLTk2LjkxMTEgMjguMTM1NywtOTYuOTg2OCAyOC4xMjg3LC05Ny4wMzczIDI4LjIwMTMsLTk3LjI0MTUgMjguMDYyMywtOTcuMTUgMjguMDMzOCwtOTcuMTM1NCAyOC4wNDcyLC05Ny4wMjQ2IDI4LjExMzMsLTk3LjAzMSAyOC4wNDg2LC05Ny4xMzM4IDI3LjkwMDksLTk3LjE1NjkgMjcuODcyOCwtOTcuMjEzNCAyNy44MjEsLTk3LjI1MDEgMjcuODc2NCwtOTcuMzU0OCAyNy44NTAyLC05Ny4zMzEyIDI3Ljg3MzgsLTk3LjUyODEgMjcuODQ3NCwtOTcuMzgyOSAyNy44Mzg3LC05Ny4zNjE3IDI3LjczNTEsLTk3LjI0NSAyNy42OTMxLC05Ny4zMjQ4IDI3LjU2MSwtOTcuNDEyMyAyNy4zMjI0LC05Ny41MDExIDI3LjI5MTUsLTk3LjQ3MzcgMjcuNDAyOSwtOTcuNTMzOSAyNy4zMzk4LC05Ny42Mzc0IDI3LjMwMSwtOTcuNzM1MiAyNy40MTgyLC05Ny42NjE5IDI3LjI4NzUsLTk3Ljc5NjYgMjcuMjcyNiwtOTcuNjU3NCAyNy4yNzM3LC05Ny41MzQxIDI3LjIyNTMsLTk3LjQ0ODcgMjcuMjYzMSwtOTcuNDUxMSAyNy4xMjE2LC05Ny41MDUyIDI3LjA4NTYsLTk3LjQ3OSAyNi45OTkxLC05Ny41NjE0IDI2Ljk5OCwtOTcuNTYyOSAyNi44Mzg5LC05Ny40NzEgMjYuNzUwMSwtOTcuNDQ2NCAyNi41OTk5LC05Ny40MTc3IDI2LjM3MDIsLTk3LjM0MDYgMjYuMzMxOCwtOTcuMjk1NSAyNi4xOTA4LC05Ny4zMTIxIDI2LjEyMTYsLTk3LjIzNjUgMjYuMDY0NiwtOTcuMjUxNiAyNS45NjQzLC05Ny4xNTI3IDI2LjAyNzUsLTk3LjE0NjMgMjUuOTU1NikpLCgoLTk0LjUxMTcgMjkuNTE1OCwtOTQuNjU5MiAyOS40Mzc1LC05NC43MjgyIDI5LjM3MTYsLTk0Ljc3NzQgMjkuMzc1OSwtOTQuNjg1MiAyOS40NTEzLC05NC41MTE3IDI5LjUxNTgpKSwoKC05NC43NTE4IDI5LjMzMjksLTk0LjgwNDkgMjkuMjc4NywtOTUuMDU2MiAyOS4xMjk5LC05NC44NjEzIDI5LjI5NTMsLTk0Ljc1MTggMjkuMzMyOSkpLCgoLTk2LjgyMDEgMjguMTY0NSwtOTYuNzAzNyAyOC4xOTgsLTk2LjM4NzUgMjguMzc2MiwtOTYuNDQwMyAyOC4zMTg4LC05Ni42ODc4IDI4LjE4NTksLTk2Ljg0NzkgMjguMDY1MSwtOTYuODIwMSAyOC4xNjQ1KSksKCgtOTYuODcyMiAyOC4xMzE1LC05Ni44NSAyOC4wNjM4LC05Ny4wNTU0IDI3Ljg0NzIsLTk2Ljk2MzIgMjguMDIyOSwtOTYuODcyMiAyOC4xMzE1KSksKCgtOTcuMjk0MyAyNi42MDAzLC05Ny4zMjU0IDI2LjYwMDMsLTk3LjMwOTQgMjYuNjI5OCwtOTcuMzkyMSAyNi45MzY3LC05Ny4zOTE2IDI3LjEyNTgsLTk3LjM2NjEgMjcuMjc4MSwtOTcuMzcxMiAyNy4yNzgxLC05Ny4zMzAyIDI3LjQzNTIsLTk3LjI0NzIgMjcuNTgxNSwtOTcuMTk2NCAyNy42ODM3LC05Ny4wOTI1IDI3LjgxMTQsLTk3LjA0NDYgMjcuODM0NCwtOTcuMTUwNCAyNy43MDI3LC05Ny4yMjI3IDI3LjU3NjUsLTk3LjM0NzIgMjcuMjc4LC05Ny4zNzkzIDI3LjA0MDIsLTk3LjM3MDUgMjYuOTA4MSwtOTcuMjkwMSAyNi42MDAzLC05Ny4yOTQzIDI2LjYwMDMpKSkmcXVvdDs8L3ZhbHVlPgogICAgICAgICAgICAgIDx2YWx1ZT4zMS4yNTwvdmFsdWU-CiAgICAgICAgICAgICAgPHZhbHVlPi05OS4yNTwvdmFsdWU-CiAgICAgICAgICAgICAgPHZhbHVlPi0wLjE1MTE4MTkyNDU1MzI0NTk0PC92YWx1ZT4KICAgICAgICAgICAgPC90dXBsZT4KICAgICAgICAgIDwvdHVwbGUtcmVmZXJlbmNlPgogICAgICAgIDwvdHVwbGUtc2VsZWN0aW9uPgogICAgICA8L3NlbGVjdGlvbi1jb2xsZWN0aW9uPgogICAgPC93aW5kb3c-CiAgPC93aW5kb3dzPgo8L2N1c3RvbWl6ZWQtdmlldz4K"
- },
- {
- "isSourceView": false,
- "viewName": "Product",
- "tcv": "PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0ndXRmLTgnID8-Cgo8Y3VzdG9taXplZC12aWV3IGRhc2hib2FyZD0nUHJvZHVjdCcgc291cmNlLWJ1aWxkPScyMDI0LjIuMCAoMjAyNDIuMjQuMDcxNi4xOTQ0KScgdmVyc2lvbj0nMTguMScgeG1sbnM6dXNlcj0naHR0cDovL3d3dy50YWJsZWF1c29mdHdhcmUuY29tL3htbC91c2VyJz4KICA8YWN0aXZlIGlkPSctMScgLz4KICA8ZGF0YXNvdXJjZXM-CiAgICA8ZGF0YXNvdXJjZSBuYW1lPSdmZWRlcmF0ZWQuMTBubms4ZDF2Z213OHExN3l1NzZ1MDZwbmJjaic-CiAgICAgIDxncm91cCBjYXB0aW9uPSdBY3Rpb24gKENhdGVnb3J5LFlFQVIoT3JkZXIgRGF0ZSksTU9OVEgoT3JkZXIgRGF0ZSkpJyBoaWRkZW49J3RydWUnIG5hbWU9J1tBY3Rpb24gKENhdGVnb3J5LFlFQVIoT3JkZXIgRGF0ZSksTU9OVEgoT3JkZXIgRGF0ZSkpXScgbmFtZS1zdHlsZT0ndW5xdWFsaWZpZWQnIHVzZXI6YXV0by1jb2x1bW49J3NoZWV0X2xpbmsnPgogICAgICAgIDxncm91cGZpbHRlciBmdW5jdGlvbj0nY3Jvc3Nqb2luJz4KICAgICAgICAgIDxncm91cGZpbHRlciBmdW5jdGlvbj0nbGV2ZWwtbWVtYmVycycgbGV2ZWw9J1tDYXRlZ29yeV0nIC8-CiAgICAgICAgICA8Z3JvdXBmaWx0ZXIgZnVuY3Rpb249J2xldmVsLW1lbWJlcnMnIGxldmVsPSdbeXI6T3JkZXIgRGF0ZTpva10nIC8-CiAgICAgICAgICA8Z3JvdXBmaWx0ZXIgZnVuY3Rpb249J2xldmVsLW1lbWJlcnMnIGxldmVsPSdbbW46T3JkZXIgRGF0ZTpva10nIC8-CiAgICAgICAgPC9ncm91cGZpbHRlcj4KICAgICAgPC9ncm91cD4KICAgICAgPGNvbHVtbiBjYXB0aW9uPSdBY3Rpb24gKENhdGVnb3J5LFlFQVIoT3JkZXIgRGF0ZSksTU9OVEgoT3JkZXIgRGF0ZSkpJyBkYXRhdHlwZT0ndHVwbGUnIGhpZGRlbj0ndHJ1ZScgbmFtZT0nW0FjdGlvbiAoQ2F0ZWdvcnksWUVBUihPcmRlciBEYXRlKSxNT05USChPcmRlciBEYXRlKSldJyByb2xlPSdkaW1lbnNpb24nIHR5cGU9J25vbWluYWwnIHVzZXI6YXV0by1jb2x1bW49J3NoZWV0X2xpbmsnIC8-CiAgICAgIDxncm91cCBjYXB0aW9uPSdBY3Rpb24gKFlFQVIoT3JkZXIgRGF0ZSksTU9OVEgoT3JkZXIgRGF0ZSkpJyBoaWRkZW49J3RydWUnIG5hbWU9J1tBY3Rpb24gKFlFQVIoT3JkZXIgRGF0ZSksTU9OVEgoT3JkZXIgRGF0ZSkpXScgbmFtZS1zdHlsZT0ndW5xdWFsaWZpZWQnIHVzZXI6YXV0by1jb2x1bW49J3NoZWV0X2xpbmsnPgogICAgICAgIDxncm91cGZpbHRlciBmdW5jdGlvbj0nY3Jvc3Nqb2luJz4KICAgICAgICAgIDxncm91cGZpbHRlciBmdW5jdGlvbj0nbGV2ZWwtbWVtYmVycycgbGV2ZWw9J1t5cjpPcmRlciBEYXRlOm9rXScgLz4KICAgICAgICAgIDxncm91cGZpbHRlciBmdW5jdGlvbj0nbGV2ZWwtbWVtYmVycycgbGV2ZWw9J1ttbjpPcmRlciBEYXRlOm9rXScgLz4KICAgICAgICA8L2dyb3VwZmlsdGVyPgogICAgICA8L2dyb3VwPgogICAgICA8Y29sdW1uIGNhcHRpb249J0FjdGlvbiAoWUVBUihPcmRlciBEYXRlKSxNT05USChPcmRlciBEYXRlKSknIGRhdGF0eXBlPSd0dXBsZScgaGlkZGVuPSd0cnVlJyBuYW1lPSdbQWN0aW9uIChZRUFSKE9yZGVyIERhdGUpLE1PTlRIKE9yZGVyIERhdGUpKV0nIHJvbGU9J2RpbWVuc2lvbicgdHlwZT0nbm9taW5hbCcgdXNlcjphdXRvLWNvbHVtbj0nc2hlZXRfbGluaycgLz4KICAgICAgPGdyb3VwIGNhcHRpb249J0FjdGlvbiAoWUVBUihPcmRlciBEYXRlKSxNT05USChPcmRlciBEYXRlKSxQcm9kdWN0IENhdGVnb3J5KScgaGlkZGVuPSd0cnVlJyBuYW1lPSdbQWN0aW9uIChZRUFSKE9yZGVyIERhdGUpLE1PTlRIKE9yZGVyIERhdGUpLFByb2R1Y3QgQ2F0ZWdvcnkpXScgbmFtZS1zdHlsZT0ndW5xdWFsaWZpZWQnIHVzZXI6YXV0by1jb2x1bW49J3NoZWV0X2xpbmsnPgogICAgICAgIDxncm91cGZpbHRlciBmdW5jdGlvbj0nY3Jvc3Nqb2luJz4KICAgICAgICAgIDxncm91cGZpbHRlciBmdW5jdGlvbj0nbGV2ZWwtbWVtYmVycycgbGV2ZWw9J1t5cjpPcmRlciBEYXRlOm9rXScgLz4KICAgICAgICAgIDxncm91cGZpbHRlciBmdW5jdGlvbj0nbGV2ZWwtbWVtYmVycycgbGV2ZWw9J1ttbjpPcmRlciBEYXRlOm9rXScgLz4KICAgICAgICAgIDxncm91cGZpbHRlciBmdW5jdGlvbj0nbGV2ZWwtbWVtYmVycycgbGV2ZWw9J1tub25lOkNhdGVnb3J5Om5rXScgLz4KICAgICAgICA8L2dyb3VwZmlsdGVyPgogICAgICA8L2dyb3VwPgogICAgICA8Y29sdW1uIGNhcHRpb249J0FjdGlvbiAoWUVBUihPcmRlciBEYXRlKSxNT05USChPcmRlciBEYXRlKSxQcm9kdWN0IENhdGVnb3J5KScgZGF0YXR5cGU9J3R1cGxlJyBoaWRkZW49J3RydWUnIG5hbWU9J1tBY3Rpb24gKFlFQVIoT3JkZXIgRGF0ZSksTU9OVEgoT3JkZXIgRGF0ZSksUHJvZHVjdCBDYXRlZ29yeSldJyByb2xlPSdkaW1lbnNpb24nIHR5cGU9J25vbWluYWwnIHVzZXI6YXV0by1jb2x1bW49J3NoZWV0X2xpbmsnIC8-CiAgICAgIDxjb2x1bW4taW5zdGFuY2UgY29sdW1uPSdbT3JkZXIgRGF0ZV0nIGRlcml2YXRpb249J01vbnRoJyBuYW1lPSdbbW46T3JkZXIgRGF0ZTpva10nIHBpdm90PSdrZXknIHR5cGU9J29yZGluYWwnIC8-CiAgICAgIDxjb2x1bW4taW5zdGFuY2UgY29sdW1uPSdbQ2F0ZWdvcnldJyBkZXJpdmF0aW9uPSdOb25lJyBuYW1lPSdbbm9uZTpDYXRlZ29yeTpua10nIHBpdm90PSdrZXknIHR5cGU9J25vbWluYWwnIC8-CiAgICAgIDxjb2x1bW4taW5zdGFuY2UgY29sdW1uPSdbT3JkZXIgRGF0ZV0nIGRlcml2YXRpb249J05vbmUnIG5hbWU9J1tub25lOk9yZGVyIERhdGU6cWtdJyBwaXZvdD0na2V5JyB0eXBlPSdxdWFudGl0YXRpdmUnIC8-CiAgICAgIDxjb2x1bW4taW5zdGFuY2UgY29sdW1uPSdbUmVnaW9uXScgZGVyaXZhdGlvbj0nTm9uZScgbmFtZT0nW25vbmU6UmVnaW9uOm5rXScgcGl2b3Q9J2tleScgdHlwZT0nbm9taW5hbCcgLz4KICAgICAgPGNvbHVtbi1pbnN0YW5jZSBjb2x1bW49J1tPcmRlciBEYXRlXScgZGVyaXZhdGlvbj0nWWVhcicgbmFtZT0nW3lyOk9yZGVyIERhdGU6b2tdJyBwaXZvdD0na2V5JyB0eXBlPSdvcmRpbmFsJyAvPgogICAgPC9kYXRhc291cmNlPgogIDwvZGF0YXNvdXJjZXM-CiAgPHdvcmtzaGVldCBuYW1lPSdQcm9kdWN0Vmlldyc-CiAgICA8dGFibGUgLz4KICA8L3dvcmtzaGVldD4KICA8d29ya3NoZWV0IG5hbWU9J1Byb2R1Y3REZXRhaWxzJz4KICAgIDx0YWJsZSAvPgogIDwvd29ya3NoZWV0Pgo8L2N1c3RvbWl6ZWQtdmlldz4K"
- },
- {
- "isSourceView": false,
- "viewName": "Customers",
- "tcv": "PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0ndXRmLTgnID8-Cgo8Y3VzdG9taXplZC12aWV3IGRhc2hib2FyZD0nQ3VzdG9tZXJzJyBzb3VyY2UtYnVpbGQ9JzIwMjQuMi4wICgyMDI0Mi4yNC4wNzE2LjE5NDQpJyB2ZXJzaW9uPScxOC4xJyB4bWxuczp1c2VyPSdodHRwOi8vd3d3LnRhYmxlYXVzb2Z0d2FyZS5jb20veG1sL3VzZXInPgogIDxhY3RpdmUgaWQ9Jy0xJyAvPgogIDxkYXRhc291cmNlcz4KICAgIDxkYXRhc291cmNlIG5hbWU9J2ZlZGVyYXRlZC4xMG5uazhkMXZnbXc4cTE3eXU3NnUwNnBuYmNqJz4KICAgICAgPGNvbHVtbiBkYXRhdHlwZT0nc3RyaW5nJyBuYW1lPSdbOk1lYXN1cmUgTmFtZXNdJyByb2xlPSdkaW1lbnNpb24nIHR5cGU9J25vbWluYWwnPgogICAgICAgIDxhbGlhc2VzPgogICAgICAgICAgPGFsaWFzIGtleT0nJnF1b3Q7W2ZlZGVyYXRlZC4xMG5uazhkMXZnbXc4cTE3eXU3NnUwNnBuYmNqXS5bY3RkOkN1c3RvbWVyIE5hbWU6cWtdJnF1b3Q7JyB2YWx1ZT0nQ291bnQgb2YgQ3VzdG9tZXJzJyAvPgogICAgICAgIDwvYWxpYXNlcz4KICAgICAgPC9jb2x1bW4-CiAgICAgIDxncm91cCBjYXB0aW9uPSdBY3Rpb24gKFJlZ2lvbiknIGhpZGRlbj0ndHJ1ZScgbmFtZT0nW0FjdGlvbiAoUmVnaW9uKV0nIG5hbWUtc3R5bGU9J3VucXVhbGlmaWVkJyB1c2VyOmF1dG8tY29sdW1uPSdzaGVldF9saW5rJz4KICAgICAgICA8Z3JvdXBmaWx0ZXIgZnVuY3Rpb249J2Nyb3Nzam9pbic-CiAgICAgICAgICA8Z3JvdXBmaWx0ZXIgZnVuY3Rpb249J2xldmVsLW1lbWJlcnMnIGxldmVsPSdbUmVnaW9uXScgLz4KICAgICAgICA8L2dyb3VwZmlsdGVyPgogICAgICA8L2dyb3VwPgogICAgICA8Y29sdW1uIGNhcHRpb249J0FjdGlvbiAoUmVnaW9uKScgZGF0YXR5cGU9J3R1cGxlJyBoaWRkZW49J3RydWUnIG5hbWU9J1tBY3Rpb24gKFJlZ2lvbildJyByb2xlPSdkaW1lbnNpb24nIHR5cGU9J25vbWluYWwnIHVzZXI6YXV0by1jb2x1bW49J3NoZWV0X2xpbmsnIC8-CiAgICAgIDxjb2x1bW4taW5zdGFuY2UgY29sdW1uPSdbQ2F0ZWdvcnldJyBkZXJpdmF0aW9uPSdOb25lJyBuYW1lPSdbbm9uZTpDYXRlZ29yeTpua10nIHBpdm90PSdrZXknIHR5cGU9J25vbWluYWwnIC8-CiAgICAgIDxjb2x1bW4taW5zdGFuY2UgY29sdW1uPSdbT3JkZXIgRGF0ZV0nIGRlcml2YXRpb249J05vbmUnIG5hbWU9J1tub25lOk9yZGVyIERhdGU6cWtdJyBwaXZvdD0na2V5JyB0eXBlPSdxdWFudGl0YXRpdmUnIC8-CiAgICAgIDxjb2x1bW4taW5zdGFuY2UgY29sdW1uPSdbUmVnaW9uXScgZGVyaXZhdGlvbj0nTm9uZScgbmFtZT0nW25vbmU6UmVnaW9uOm5rXScgcGl2b3Q9J2tleScgdHlwZT0nbm9taW5hbCcgLz4KICAgICAgPGNvbHVtbi1pbnN0YW5jZSBjb2x1bW49J1tTZWdtZW50XScgZGVyaXZhdGlvbj0nTm9uZScgbmFtZT0nW25vbmU6U2VnbWVudDpua10nIHBpdm90PSdrZXknIHR5cGU9J25vbWluYWwnIC8-CiAgICAgIDxjb2x1bW4taW5zdGFuY2UgY29sdW1uPSdbT3JkZXIgRGF0ZV0nIGRlcml2YXRpb249J1F1YXJ0ZXInIG5hbWU9J1txcjpPcmRlciBEYXRlOm9rXScgcGl2b3Q9J2tleScgdHlwZT0nb3JkaW5hbCcgLz4KICAgICAgPGNvbHVtbi1pbnN0YW5jZSBjb2x1bW49J1tPcmRlciBEYXRlXScgZGVyaXZhdGlvbj0nWWVhcicgbmFtZT0nW3lyOk9yZGVyIERhdGU6b2tdJyBwaXZvdD0na2V5JyB0eXBlPSdvcmRpbmFsJyAvPgogICAgPC9kYXRhc291cmNlPgogIDwvZGF0YXNvdXJjZXM-CiAgPHdvcmtzaGVldCBuYW1lPSdDdXN0b21lclNjYXR0ZXInPgogICAgPHRhYmxlIC8-CiAgPC93b3Jrc2hlZXQ-CiAgPHdvcmtzaGVldCBuYW1lPSdDdXN0b21lclJhbmsnPgogICAgPHRhYmxlIC8-CiAgPC93b3Jrc2hlZXQ-CiAgPHdvcmtzaGVldCBuYW1lPSdDdXN0b21lck92ZXJ2aWV3Jz4KICAgIDx0YWJsZSAvPgogIDwvd29ya3NoZWV0Pgo8L2N1c3RvbWl6ZWQtdmlldz4K"
- },
- {
- "isSourceView": false,
- "viewName": "Shipping",
- "tcv": "PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0ndXRmLTgnID8-Cgo8Y3VzdG9taXplZC12aWV3IGRhc2hib2FyZD0nU2hpcHBpbmcnIHNvdXJjZS1idWlsZD0nMjAyNC4yLjAgKDIwMjQyLjI0LjA3MTYuMTk0NCknIHZlcnNpb249JzE4LjEnIHhtbG5zOnVzZXI9J2h0dHA6Ly93d3cudGFibGVhdXNvZnR3YXJlLmNvbS94bWwvdXNlcic-CiAgPGFjdGl2ZSBpZD0nLTEnIC8-CiAgPGRhdGFzb3VyY2VzPgogICAgPGRhdGFzb3VyY2UgbmFtZT0nZmVkZXJhdGVkLjEwbm5rOGQxdmdtdzhxMTd5dTc2dTA2cG5iY2onPgogICAgICA8Z3JvdXAgY2FwdGlvbj0nQWN0aW9uIChEZWxheWVkPyknIGhpZGRlbj0ndHJ1ZScgbmFtZT0nW0FjdGlvbiAoRGVsYXllZD8pXScgbmFtZS1zdHlsZT0ndW5xdWFsaWZpZWQnIHVzZXI6YXV0by1jb2x1bW49J3NoZWV0X2xpbmsnPgogICAgICAgIDxncm91cGZpbHRlciBmdW5jdGlvbj0nY3Jvc3Nqb2luJz4KICAgICAgICAgIDxncm91cGZpbHRlciBmdW5jdGlvbj0nbGV2ZWwtbWVtYmVycycgbGV2ZWw9J1tDYWxjdWxhdGlvbl82NDAxMTAzMTcxMjU5NzIzXScgLz4KICAgICAgICA8L2dyb3VwZmlsdGVyPgogICAgICA8L2dyb3VwPgogICAgICA8Y29sdW1uIGNhcHRpb249J0FjdGlvbiAoRGVsYXllZD8pJyBkYXRhdHlwZT0ndHVwbGUnIGhpZGRlbj0ndHJ1ZScgbmFtZT0nW0FjdGlvbiAoRGVsYXllZD8pXScgcm9sZT0nZGltZW5zaW9uJyB0eXBlPSdub21pbmFsJyB1c2VyOmF1dG8tY29sdW1uPSdzaGVldF9saW5rJyAvPgogICAgICA8Z3JvdXAgY2FwdGlvbj0nQWN0aW9uIChTaGlwIFN0YXR1cyknIGhpZGRlbj0ndHJ1ZScgbmFtZT0nW0FjdGlvbiAoU2hpcCBTdGF0dXMpXScgbmFtZS1zdHlsZT0ndW5xdWFsaWZpZWQnIHVzZXI6YXV0by1jb2x1bW49J3NoZWV0X2xpbmsnPgogICAgICAgIDxncm91cGZpbHRlciBmdW5jdGlvbj0nY3Jvc3Nqb2luJz4KICAgICAgICAgIDxncm91cGZpbHRlciBmdW5jdGlvbj0nbGV2ZWwtbWVtYmVycycgbGV2ZWw9J1tDYWxjdWxhdGlvbl82NDAxMTAzMTcxMjU5NzIzXScgLz4KICAgICAgICA8L2dyb3VwZmlsdGVyPgogICAgICA8L2dyb3VwPgogICAgICA8Y29sdW1uIGNhcHRpb249J0FjdGlvbiAoU2hpcCBTdGF0dXMpJyBkYXRhdHlwZT0ndHVwbGUnIGhpZGRlbj0ndHJ1ZScgbmFtZT0nW0FjdGlvbiAoU2hpcCBTdGF0dXMpXScgcm9sZT0nZGltZW5zaW9uJyB0eXBlPSdub21pbmFsJyB1c2VyOmF1dG8tY29sdW1uPSdzaGVldF9saW5rJyAvPgogICAgICA8Z3JvdXAgY2FwdGlvbj0nQWN0aW9uIChTaGlwIFN0YXR1cyxZRUFSKE9yZGVyIERhdGUpLFdFRUsoT3JkZXIgRGF0ZSkpJyBoaWRkZW49J3RydWUnIG5hbWU9J1tBY3Rpb24gKFNoaXAgU3RhdHVzLFlFQVIoT3JkZXIgRGF0ZSksV0VFSyhPcmRlciBEYXRlKSldJyBuYW1lLXN0eWxlPSd1bnF1YWxpZmllZCcgdXNlcjphdXRvLWNvbHVtbj0nc2hlZXRfbGluayc-CiAgICAgICAgPGdyb3VwZmlsdGVyIGZ1bmN0aW9uPSdjcm9zc2pvaW4nPgogICAgICAgICAgPGdyb3VwZmlsdGVyIGZ1bmN0aW9uPSdsZXZlbC1tZW1iZXJzJyBsZXZlbD0nW0NhbGN1bGF0aW9uXzY0MDExMDMxNzEyNTk3MjNdJyAvPgogICAgICAgICAgPGdyb3VwZmlsdGVyIGZ1bmN0aW9uPSdsZXZlbC1tZW1iZXJzJyBsZXZlbD0nW3lyOk9yZGVyIERhdGU6b2tdJyAvPgogICAgICAgICAgPGdyb3VwZmlsdGVyIGZ1bmN0aW9uPSdsZXZlbC1tZW1iZXJzJyBsZXZlbD0nW3R3azpPcmRlciBEYXRlOm9rXScgLz4KICAgICAgICA8L2dyb3VwZmlsdGVyPgogICAgICA8L2dyb3VwPgogICAgICA8Y29sdW1uIGNhcHRpb249J0FjdGlvbiAoU2hpcCBTdGF0dXMsWUVBUihPcmRlciBEYXRlKSxXRUVLKE9yZGVyIERhdGUpKScgZGF0YXR5cGU9J3R1cGxlJyBoaWRkZW49J3RydWUnIG5hbWU9J1tBY3Rpb24gKFNoaXAgU3RhdHVzLFlFQVIoT3JkZXIgRGF0ZSksV0VFSyhPcmRlciBEYXRlKSldJyByb2xlPSdkaW1lbnNpb24nIHR5cGU9J25vbWluYWwnIHVzZXI6YXV0by1jb2x1bW49J3NoZWV0X2xpbmsnIC8-CiAgICAgIDxjb2x1bW4taW5zdGFuY2UgY29sdW1uPSdbQ2FsY3VsYXRpb25fNjQwMTEwMzE3MTI1OTcyM10nIGRlcml2YXRpb249J05vbmUnIG5hbWU9J1tub25lOkNhbGN1bGF0aW9uXzY0MDExMDMxNzEyNTk3MjM6bmtdJyBwaXZvdD0na2V5JyB0eXBlPSdub21pbmFsJyAvPgogICAgICA8Y29sdW1uLWluc3RhbmNlIGNvbHVtbj0nW1JlZ2lvbl0nIGRlcml2YXRpb249J05vbmUnIG5hbWU9J1tub25lOlJlZ2lvbjpua10nIHBpdm90PSdrZXknIHR5cGU9J25vbWluYWwnIC8-CiAgICAgIDxjb2x1bW4taW5zdGFuY2UgY29sdW1uPSdbU2hpcCBNb2RlXScgZGVyaXZhdGlvbj0nTm9uZScgbmFtZT0nW25vbmU6U2hpcCBNb2RlOm5rXScgcGl2b3Q9J2tleScgdHlwZT0nbm9taW5hbCcgLz4KICAgICAgPGNvbHVtbi1pbnN0YW5jZSBjb2x1bW49J1tPcmRlciBEYXRlXScgZGVyaXZhdGlvbj0nUXVhcnRlcicgbmFtZT0nW3FyOk9yZGVyIERhdGU6b2tdJyBwaXZvdD0na2V5JyB0eXBlPSdvcmRpbmFsJyAvPgogICAgICA8Y29sdW1uLWluc3RhbmNlIGNvbHVtbj0nW09yZGVyIERhdGVdJyBkZXJpdmF0aW9uPSdZZWFyJyBuYW1lPSdbeXI6T3JkZXIgRGF0ZTpva10nIHBpdm90PSdrZXknIHR5cGU9J29yZGluYWwnIC8-CiAgICA8L2RhdGFzb3VyY2U-CiAgPC9kYXRhc291cmNlcz4KICA8d29ya3NoZWV0IG5hbWU9J1NoaXBTdW1tYXJ5Jz4KICAgIDx0YWJsZSAvPgogIDwvd29ya3NoZWV0PgogIDx3b3Jrc2hlZXQgbmFtZT0nU2hpcHBpbmdUcmVuZCc-CiAgICA8dGFibGUgLz4KICA8L3dvcmtzaGVldD4KICA8d29ya3NoZWV0IG5hbWU9J0RheXN0b1NoaXAnPgogICAgPHRhYmxlIC8-CiAgPC93b3Jrc2hlZXQ-CjwvY3VzdG9taXplZC12aWV3Pgo="
- },
- {
- "isSourceView": false,
- "viewName": "Performance",
- "tcv": "PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0ndXRmLTgnID8-Cgo8Y3VzdG9taXplZC12aWV3IHNvdXJjZS1idWlsZD0nMjAyNC4yLjAgKDIwMjQyLjI0LjA3MTYuMTk0NCknIHZlcnNpb249JzE4LjEnIHhtbG5zOnVzZXI9J2h0dHA6Ly93d3cudGFibGVhdXNvZnR3YXJlLmNvbS94bWwvdXNlcic-CiAgPGRhdGFzb3VyY2VzPgogICAgPGRhdGFzb3VyY2UgbmFtZT0nZmVkZXJhdGVkLjEwbm5rOGQxdmdtdzhxMTd5dTc2dTA2cG5iY2onPgogICAgICA8Y29sdW1uLWluc3RhbmNlIGNvbHVtbj0nW1JlZ2lvbl0nIGRlcml2YXRpb249J05vbmUnIG5hbWU9J1tub25lOlJlZ2lvbjpua10nIHBpdm90PSdrZXknIHR5cGU9J25vbWluYWwnIC8-CiAgICAgIDxjb2x1bW4taW5zdGFuY2UgY29sdW1uPSdbT3JkZXIgRGF0ZV0nIGRlcml2YXRpb249J1llYXInIG5hbWU9J1t5cjpPcmRlciBEYXRlOm9rXScgcGl2b3Q9J2tleScgdHlwZT0nb3JkaW5hbCcgLz4KICAgIDwvZGF0YXNvdXJjZT4KICA8L2RhdGFzb3VyY2VzPgogIDx3b3Jrc2hlZXQgbmFtZT0nUGVyZm9ybWFuY2UnPgogICAgPHRhYmxlIC8-CiAgPC93b3Jrc2hlZXQ-CjwvY3VzdG9taXplZC12aWV3Pgo="
- },
- {
- "isSourceView": false,
- "viewName": "Commission Model",
- "tcv": "PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0ndXRmLTgnID8-Cgo8Y3VzdG9taXplZC12aWV3IGRhc2hib2FyZD0nQ29tbWlzc2lvbiBNb2RlbCcgc291cmNlLWJ1aWxkPScyMDI0LjIuMCAoMjAyNDIuMjQuMDcxNi4xOTQ0KScgdmVyc2lvbj0nMTguMScgeG1sbnM6dXNlcj0naHR0cDovL3d3dy50YWJsZWF1c29mdHdhcmUuY29tL3htbC91c2VyJz4KICA8YWN0aXZlIGlkPSctMScgLz4KICA8ZGF0YXNvdXJjZXM-CiAgICA8ZGF0YXNvdXJjZSBuYW1lPSdmZWRlcmF0ZWQuMGEwMWNvZDFveGw4M2wxZjV5dmVzMWNmY2lxbyc-CiAgICAgIDxjb2x1bW4gZGF0YXR5cGU9J3N0cmluZycgbmFtZT0nWzpNZWFzdXJlIE5hbWVzXScgcm9sZT0nZGltZW5zaW9uJyB0eXBlPSdub21pbmFsJyAvPgogICAgPC9kYXRhc291cmNlPgogIDwvZGF0YXNvdXJjZXM-CiAgPHdvcmtzaGVldCBuYW1lPSdRdW90YUF0dGFpbm1lbnQnPgogICAgPHRhYmxlIC8-CiAgPC93b3Jrc2hlZXQ-CiAgPHdvcmtzaGVldCBuYW1lPSdDb21taXNzaW9uUHJvamVjdGlvbic-CiAgICA8dGFibGUgLz4KICA8L3dvcmtzaGVldD4KICA8d29ya3NoZWV0IG5hbWU9J1NhbGVzJz4KICAgIDx0YWJsZSAvPgogIDwvd29ya3NoZWV0PgogIDx3b3Jrc2hlZXQgbmFtZT0nT1RFJz4KICAgIDx0YWJsZSAvPgogIDwvd29ya3NoZWV0Pgo8L2N1c3RvbWl6ZWQtdmlldz4K"
- },
- {
- "isSourceView": false,
- "viewName": "Order Details",
- "tcv": "PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0ndXRmLTgnID8-Cgo8Y3VzdG9taXplZC12aWV3IGRhc2hib2FyZD0nT3JkZXIgRGV0YWlscycgc291cmNlLWJ1aWxkPScyMDI0LjIuMCAoMjAyNDIuMjQuMDcxNi4xOTQ0KScgdmVyc2lvbj0nMTguMScgeG1sbnM6dXNlcj0naHR0cDovL3d3dy50YWJsZWF1c29mdHdhcmUuY29tL3htbC91c2VyJz4KICA8YWN0aXZlIGlkPSctMScgLz4KICA8ZGF0YXNvdXJjZXM-CiAgICA8ZGF0YXNvdXJjZSBuYW1lPSdmZWRlcmF0ZWQuMTBubms4ZDF2Z213OHExN3l1NzZ1MDZwbmJjaic-CiAgICAgIDxjb2x1bW4gZGF0YXR5cGU9J3N0cmluZycgbmFtZT0nWzpNZWFzdXJlIE5hbWVzXScgcm9sZT0nZGltZW5zaW9uJyB0eXBlPSdub21pbmFsJz4KICAgICAgICA8YWxpYXNlcz4KICAgICAgICAgIDxhbGlhcyBrZXk9JyZxdW90O1tmZWRlcmF0ZWQuMTBubms4ZDF2Z213OHExN3l1NzZ1MDZwbmJjal0uW2N0ZDpDdXN0b21lciBOYW1lOnFrXSZxdW90OycgdmFsdWU9J0NvdW50IG9mIEN1c3RvbWVycycgLz4KICAgICAgICA8L2FsaWFzZXM-CiAgICAgIDwvY29sdW1uPgogICAgICA8Z3JvdXAgY2FwdGlvbj0nQWN0aW9uIChPcmRlciBQcm9maXRhYmxlPyxNT05USChPcmRlciBEYXRlKSxTZWdtZW50KScgaGlkZGVuPSd0cnVlJyBuYW1lPSdbQWN0aW9uIChPcmRlciBQcm9maXRhYmxlPyxNT05USChPcmRlciBEYXRlKSxTZWdtZW50KV0nIG5hbWUtc3R5bGU9J3VucXVhbGlmaWVkJyB1c2VyOmF1dG8tY29sdW1uPSdzaGVldF9saW5rJz4KICAgICAgICA8Z3JvdXBmaWx0ZXIgZnVuY3Rpb249J2Nyb3Nzam9pbic-CiAgICAgICAgICA8Z3JvdXBmaWx0ZXIgZnVuY3Rpb249J2xldmVsLW1lbWJlcnMnIGxldmVsPSdbQ2FsY3VsYXRpb25fOTA2MDEyMjEwNDk0NzQ3MV0nIC8-CiAgICAgICAgICA8Z3JvdXBmaWx0ZXIgZnVuY3Rpb249J2xldmVsLW1lbWJlcnMnIGxldmVsPSdbdG1uOk9yZGVyIERhdGU6b2tdJyAvPgogICAgICAgICAgPGdyb3VwZmlsdGVyIGZ1bmN0aW9uPSdsZXZlbC1tZW1iZXJzJyBsZXZlbD0nW1NlZ21lbnRdJyAvPgogICAgICAgIDwvZ3JvdXBmaWx0ZXI-CiAgICAgIDwvZ3JvdXA-CiAgICAgIDxjb2x1bW4gY2FwdGlvbj0nQWN0aW9uIChPcmRlciBQcm9maXRhYmxlPyxNT05USChPcmRlciBEYXRlKSxTZWdtZW50KScgZGF0YXR5cGU9J3R1cGxlJyBoaWRkZW49J3RydWUnIG5hbWU9J1tBY3Rpb24gKE9yZGVyIFByb2ZpdGFibGU_LE1PTlRIKE9yZGVyIERhdGUpLFNlZ21lbnQpXScgcm9sZT0nZGltZW5zaW9uJyB0eXBlPSdub21pbmFsJyB1c2VyOmF1dG8tY29sdW1uPSdzaGVldF9saW5rJyAvPgogICAgICA8Z3JvdXAgY2FwdGlvbj0nQWN0aW9uIChQb3N0YWwgQ29kZSxTdGF0ZS9Qcm92aW5jZSkgMScgaGlkZGVuPSd0cnVlJyBuYW1lPSdbQWN0aW9uIChQb3N0YWwgQ29kZSxTdGF0ZS9Qcm92aW5jZSkgMV0nIG5hbWUtc3R5bGU9J3VucXVhbGlmaWVkJyB1c2VyOmF1dG8tY29sdW1uPSdzaGVldF9saW5rJz4KICAgICAgICA8Z3JvdXBmaWx0ZXIgZnVuY3Rpb249J2Nyb3Nzam9pbic-CiAgICAgICAgICA8Z3JvdXBmaWx0ZXIgZnVuY3Rpb249J2xldmVsLW1lbWJlcnMnIGxldmVsPSdbUG9zdGFsIENvZGVdJyAvPgogICAgICAgICAgPGdyb3VwZmlsdGVyIGZ1bmN0aW9uPSdsZXZlbC1tZW1iZXJzJyBsZXZlbD0nW1N0YXRlL1Byb3ZpbmNlXScgLz4KICAgICAgICA8L2dyb3VwZmlsdGVyPgogICAgICA8L2dyb3VwPgogICAgICA8Y29sdW1uIGNhcHRpb249J0FjdGlvbiAoUG9zdGFsIENvZGUsU3RhdGUvUHJvdmluY2UpIDEnIGRhdGF0eXBlPSd0dXBsZScgaGlkZGVuPSd0cnVlJyBuYW1lPSdbQWN0aW9uIChQb3N0YWwgQ29kZSxTdGF0ZS9Qcm92aW5jZSkgMV0nIHJvbGU9J2RpbWVuc2lvbicgdHlwZT0nbm9taW5hbCcgdXNlcjphdXRvLWNvbHVtbj0nc2hlZXRfbGluaycgLz4KICAgICAgPGNvbHVtbi1pbnN0YW5jZSBjb2x1bW49J1tDYXRlZ29yeV0nIGRlcml2YXRpb249J05vbmUnIG5hbWU9J1tub25lOkNhdGVnb3J5Om5rXScgcGl2b3Q9J2tleScgdHlwZT0nbm9taW5hbCcgLz4KICAgICAgPGNvbHVtbi1pbnN0YW5jZSBjb2x1bW49J1tDaXR5XScgZGVyaXZhdGlvbj0nTm9uZScgbmFtZT0nW25vbmU6Q2l0eTpua10nIHBpdm90PSdrZXknIHR5cGU9J25vbWluYWwnIC8-CiAgICAgIDxjb2x1bW4taW5zdGFuY2UgY29sdW1uPSdbT3JkZXIgRGF0ZV0nIGRlcml2YXRpb249J05vbmUnIG5hbWU9J1tub25lOk9yZGVyIERhdGU6b2tdJyBwaXZvdD0na2V5JyB0eXBlPSdvcmRpbmFsJyAvPgogICAgICA8Y29sdW1uLWluc3RhbmNlIGNvbHVtbj0nW09yZGVyIERhdGVdJyBkZXJpdmF0aW9uPSdOb25lJyBuYW1lPSdbbm9uZTpPcmRlciBEYXRlOnFrXScgcGl2b3Q9J2tleScgdHlwZT0ncXVhbnRpdGF0aXZlJyAvPgogICAgICA8Y29sdW1uLWluc3RhbmNlIGNvbHVtbj0nW1JlZ2lvbl0nIGRlcml2YXRpb249J05vbmUnIG5hbWU9J1tub25lOlJlZ2lvbjpua10nIHBpdm90PSdrZXknIHR5cGU9J25vbWluYWwnIC8-CiAgICAgIDxjb2x1bW4taW5zdGFuY2UgY29sdW1uPSdbU2VnbWVudF0nIGRlcml2YXRpb249J05vbmUnIG5hbWU9J1tub25lOlNlZ21lbnQ6bmtdJyBwaXZvdD0na2V5JyB0eXBlPSdub21pbmFsJyAvPgogICAgICA8Y29sdW1uLWluc3RhbmNlIGNvbHVtbj0nW1N0YXRlL1Byb3ZpbmNlXScgZGVyaXZhdGlvbj0nTm9uZScgbmFtZT0nW25vbmU6U3RhdGUvUHJvdmluY2U6bmtdJyBwaXZvdD0na2V5JyB0eXBlPSdub21pbmFsJyAvPgogICAgPC9kYXRhc291cmNlPgogIDwvZGF0YXNvdXJjZXM-CiAgPHdvcmtzaGVldCBuYW1lPSdQcm9kdWN0IERldGFpbCBTaGVldCc-CiAgICA8dGFibGUgLz4KICA8L3dvcmtzaGVldD4KPC9jdXN0b21pemVkLXZpZXc-Cg=="
- },
- {
- "isSourceView": false,
- "viewName": "Forecast",
- "tcv": "PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0ndXRmLTgnID8-Cgo8Y3VzdG9taXplZC12aWV3IHNvdXJjZS1idWlsZD0nMjAyNC4yLjAgKDIwMjQyLjI0LjA3MTYuMTk0NCknIHZlcnNpb249JzE4LjEnIHhtbG5zOnVzZXI9J2h0dHA6Ly93d3cudGFibGVhdXNvZnR3YXJlLmNvbS94bWwvdXNlcic-CiAgPGRhdGFzb3VyY2VzPgogICAgPGRhdGFzb3VyY2UgbmFtZT0nZmVkZXJhdGVkLjEwbm5rOGQxdmdtdzhxMTd5dTc2dTA2cG5iY2onPgogICAgICA8Y29sdW1uLWluc3RhbmNlIGNvbHVtbj0nW09yZGVyIERhdGVdJyBkZXJpdmF0aW9uPSdOb25lJyBuYW1lPSdbbm9uZTpPcmRlciBEYXRlOnFrXScgcGl2b3Q9J2tleScgdHlwZT0ncXVhbnRpdGF0aXZlJyAvPgogICAgICA8Y29sdW1uLWluc3RhbmNlIGNvbHVtbj0nW1JlZ2lvbl0nIGRlcml2YXRpb249J05vbmUnIG5hbWU9J1tub25lOlJlZ2lvbjpua10nIHBpdm90PSdrZXknIHR5cGU9J25vbWluYWwnIC8-CiAgICA8L2RhdGFzb3VyY2U-CiAgPC9kYXRhc291cmNlcz4KICA8d29ya3NoZWV0IG5hbWU9J0ZvcmVjYXN0Jz4KICAgIDx0YWJsZSAvPgogIDwvd29ya3NoZWV0Pgo8L2N1c3RvbWl6ZWQtdmlldz4K"
- },
- {
- "isSourceView": false,
- "viewName": "What If Forecast",
- "tcv": "PD94bWwgdmVyc2lvbj0nMS4wJyBlbmNvZGluZz0ndXRmLTgnID8-Cgo8Y3VzdG9taXplZC12aWV3IHNvdXJjZS1idWlsZD0nMjAyNC4yLjAgKDIwMjQyLjI0LjA3MTYuMTk0NCknIHZlcnNpb249JzE4LjEnIHhtbG5zOnVzZXI9J2h0dHA6Ly93d3cudGFibGVhdXNvZnR3YXJlLmNvbS94bWwvdXNlcic-CiAgPGRhdGFzb3VyY2VzPgogICAgPGRhdGFzb3VyY2UgbmFtZT0nZmVkZXJhdGVkLjEwbm5rOGQxdmdtdzhxMTd5dTc2dTA2cG5iY2onPgogICAgICA8Y29sdW1uIGRhdGF0eXBlPSdzdHJpbmcnIG5hbWU9J1s6TWVhc3VyZSBOYW1lc10nIHJvbGU9J2RpbWVuc2lvbicgdHlwZT0nbm9taW5hbCc-CiAgICAgICAgPGFsaWFzZXM-CiAgICAgICAgICA8YWxpYXMga2V5PScmcXVvdDtbZmVkZXJhdGVkLjEwbm5rOGQxdmdtdzhxMTd5dTc2dTA2cG5iY2pdLltjdGQ6Q3VzdG9tZXIgTmFtZTpxa10mcXVvdDsnIHZhbHVlPSdDb3VudCBvZiBDdXN0b21lcnMnIC8-CiAgICAgICAgPC9hbGlhc2VzPgogICAgICA8L2NvbHVtbj4KICAgICAgPGNvbHVtbi1pbnN0YW5jZSBjb2x1bW49J1tPcmRlciBEYXRlXScgZGVyaXZhdGlvbj0nTm9uZScgbmFtZT0nW25vbmU6T3JkZXIgRGF0ZTpxa10nIHBpdm90PSdrZXknIHR5cGU9J3F1YW50aXRhdGl2ZScgLz4KICAgICAgPGNvbHVtbi1pbnN0YW5jZSBjb2x1bW49J1tSZWdpb25dJyBkZXJpdmF0aW9uPSdOb25lJyBuYW1lPSdbbm9uZTpSZWdpb246bmtdJyBwaXZvdD0na2V5JyB0eXBlPSdub21pbmFsJyAvPgogICAgICA8Y29sdW1uLWluc3RhbmNlIGNvbHVtbj0nW09yZGVyIERhdGVdJyBkZXJpdmF0aW9uPSdZZWFyJyBuYW1lPSdbeXI6T3JkZXIgRGF0ZTpva10nIHBpdm90PSdrZXknIHR5cGU9J29yZGluYWwnIC8-CiAgICA8L2RhdGFzb3VyY2U-CiAgPC9kYXRhc291cmNlcz4KICA8d29ya3NoZWV0IG5hbWU9J1doYXQgSWYgRm9yZWNhc3QnPgogICAgPHRhYmxlIC8-CiAgPC93b3Jrc2hlZXQ-CjwvY3VzdG9taXplZC12aWV3Pgo="
- }
-]
\ No newline at end of file
diff --git a/test/assets/custom_view_get.xml b/test/assets/custom_view_get.xml
deleted file mode 100644
index 67e342f30..000000000
--- a/test/assets/custom_view_get.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/custom_view_get_id.xml b/test/assets/custom_view_get_id.xml
deleted file mode 100644
index 14e589b8d..000000000
--- a/test/assets/custom_view_get_id.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/test/assets/custom_view_update.xml b/test/assets/custom_view_update.xml
deleted file mode 100644
index 5ab85bc05..000000000
--- a/test/assets/custom_view_update.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/test/assets/data_acceleration_report.xml b/test/assets/data_acceleration_report.xml
deleted file mode 100644
index 51b86a691..000000000
--- a/test/assets/data_acceleration_report.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/data_alerts_add_user.xml b/test/assets/data_alerts_add_user.xml
deleted file mode 100644
index 2a367a7f1..000000000
--- a/test/assets/data_alerts_add_user.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/data_alerts_get.xml b/test/assets/data_alerts_get.xml
deleted file mode 100644
index 78a55d4ca..000000000
--- a/test/assets/data_alerts_get.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/data_alerts_get_by_id.xml b/test/assets/data_alerts_get_by_id.xml
deleted file mode 100644
index 1a7456545..000000000
--- a/test/assets/data_alerts_get_by_id.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/data_alerts_update.xml b/test/assets/data_alerts_update.xml
deleted file mode 100644
index 78a55d4ca..000000000
--- a/test/assets/data_alerts_update.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/database_get.xml b/test/assets/database_get.xml
deleted file mode 100644
index 7d22daf4c..000000000
--- a/test/assets/database_get.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/database_populate_permissions.xml b/test/assets/database_populate_permissions.xml
deleted file mode 100644
index 21f30fea9..000000000
--- a/test/assets/database_populate_permissions.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/database_update.xml b/test/assets/database_update.xml
deleted file mode 100644
index b2cbd68c9..000000000
--- a/test/assets/database_update.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/datasource_add_tags.xml b/test/assets/datasource_add_tags.xml
deleted file mode 100644
index d7ffbd680..000000000
--- a/test/assets/datasource_add_tags.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/datasource_connection_update.xml b/test/assets/datasource_connection_update.xml
deleted file mode 100644
index 5b84616dd..000000000
--- a/test/assets/datasource_connection_update.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/test/assets/datasource_connections_update.xml b/test/assets/datasource_connections_update.xml
deleted file mode 100644
index d726aad25..000000000
--- a/test/assets/datasource_connections_update.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
diff --git a/test/assets/datasource_connections_update_no_auth.xml b/test/assets/datasource_connections_update_no_auth.xml
deleted file mode 100644
index b9d1bf3f0..000000000
--- a/test/assets/datasource_connections_update_no_auth.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
diff --git a/test/assets/datasource_data_update.xml b/test/assets/datasource_data_update.xml
deleted file mode 100644
index 305caaf0b..000000000
--- a/test/assets/datasource_data_update.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
- 7ecaccd8-39b0-4875-a77d-094f6e930019
-
-
-
diff --git a/test/assets/datasource_get.xml b/test/assets/datasource_get.xml
deleted file mode 100644
index 1c420d116..000000000
--- a/test/assets/datasource_get.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/datasource_get_all_fields.xml b/test/assets/datasource_get_all_fields.xml
deleted file mode 100644
index 46c4396d3..000000000
--- a/test/assets/datasource_get_all_fields.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/datasource_get_by_id.xml b/test/assets/datasource_get_by_id.xml
deleted file mode 100644
index 53434b8cc..000000000
--- a/test/assets/datasource_get_by_id.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/datasource_get_empty.xml b/test/assets/datasource_get_empty.xml
deleted file mode 100644
index 9f71f991d..000000000
--- a/test/assets/datasource_get_empty.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/datasource_get_no_owner.xml b/test/assets/datasource_get_no_owner.xml
deleted file mode 100644
index a0149d5ee..000000000
--- a/test/assets/datasource_get_no_owner.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/datasource_populate_connections.xml b/test/assets/datasource_populate_connections.xml
deleted file mode 100644
index f6fbd13f6..000000000
--- a/test/assets/datasource_populate_connections.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/datasource_populate_permissions.xml b/test/assets/datasource_populate_permissions.xml
deleted file mode 100644
index db967f4a9..000000000
--- a/test/assets/datasource_populate_permissions.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/datasource_publish.xml b/test/assets/datasource_publish.xml
deleted file mode 100644
index 2c05ef684..000000000
--- a/test/assets/datasource_publish.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/datasource_publish_async.xml b/test/assets/datasource_publish_async.xml
deleted file mode 100644
index a32fccd2a..000000000
--- a/test/assets/datasource_publish_async.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/test/assets/datasource_refresh.xml b/test/assets/datasource_refresh.xml
deleted file mode 100644
index 61b4b7601..000000000
--- a/test/assets/datasource_refresh.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/test/assets/datasource_refresh_duplicate.xml b/test/assets/datasource_refresh_duplicate.xml
deleted file mode 100644
index 38a258933..000000000
--- a/test/assets/datasource_refresh_duplicate.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-Resource Conflict Job for \'extract\' is already queued. Not queuing a duplicate.
diff --git a/test/assets/datasource_revision.xml b/test/assets/datasource_revision.xml
deleted file mode 100644
index 8cadafc8f..000000000
--- a/test/assets/datasource_revision.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/datasource_update.xml b/test/assets/datasource_update.xml
deleted file mode 100644
index 3b4c36e58..000000000
--- a/test/assets/datasource_update.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/dqw_by_content_type.xml b/test/assets/dqw_by_content_type.xml
deleted file mode 100644
index c65deb6d9..000000000
--- a/test/assets/dqw_by_content_type.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/extensions_server_settings_false.xml b/test/assets/extensions_server_settings_false.xml
deleted file mode 100644
index 16fd3e85d..000000000
--- a/test/assets/extensions_server_settings_false.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
- false
-
-
diff --git a/test/assets/extensions_server_settings_true.xml b/test/assets/extensions_server_settings_true.xml
deleted file mode 100644
index c562d4719..000000000
--- a/test/assets/extensions_server_settings_true.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
- true
- https://test.com
- https://example.com
-
-
diff --git a/test/assets/extensions_site_settings.xml b/test/assets/extensions_site_settings.xml
deleted file mode 100644
index e5f963ca9..000000000
--- a/test/assets/extensions_site_settings.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
- true
- false
- true
- false
- false
- false
-
- http://localhost:9123/Dynamic.html
- true
- true
-
-
-
diff --git a/test/assets/favorites_add_datasource.xml b/test/assets/favorites_add_datasource.xml
deleted file mode 100644
index a1f47ab4f..000000000
--- a/test/assets/favorites_add_datasource.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/favorites_add_project.xml b/test/assets/favorites_add_project.xml
deleted file mode 100644
index 699e6a4cd..000000000
--- a/test/assets/favorites_add_project.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/favorites_add_view.xml b/test/assets/favorites_add_view.xml
deleted file mode 100644
index 0f5c6d166..000000000
--- a/test/assets/favorites_add_view.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/favorites_add_workbook.xml b/test/assets/favorites_add_workbook.xml
deleted file mode 100644
index c8008c9b8..000000000
--- a/test/assets/favorites_add_workbook.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/favorites_get.xml b/test/assets/favorites_get.xml
deleted file mode 100644
index 8fd780b1d..000000000
--- a/test/assets/favorites_get.xml
+++ /dev/null
@@ -1,59 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/fileupload_append.xml b/test/assets/fileupload_append.xml
deleted file mode 100644
index 325ee66a9..000000000
--- a/test/assets/fileupload_append.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/test/assets/fileupload_initialize.xml b/test/assets/fileupload_initialize.xml
deleted file mode 100644
index 073ad0edc..000000000
--- a/test/assets/fileupload_initialize.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/test/assets/flow_get.xml b/test/assets/flow_get.xml
deleted file mode 100644
index 406cded8e..000000000
--- a/test/assets/flow_get.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/flow_get_by_id.xml b/test/assets/flow_get_by_id.xml
deleted file mode 100644
index d1c626105..000000000
--- a/test/assets/flow_get_by_id.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/flow_populate_connections.xml b/test/assets/flow_populate_connections.xml
deleted file mode 100644
index 5c013770c..000000000
--- a/test/assets/flow_populate_connections.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/flow_populate_permissions.xml b/test/assets/flow_populate_permissions.xml
deleted file mode 100644
index ce3a22f97..000000000
--- a/test/assets/flow_populate_permissions.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/flow_publish.xml b/test/assets/flow_publish.xml
deleted file mode 100644
index 55af88d11..000000000
--- a/test/assets/flow_publish.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/flow_refresh.xml b/test/assets/flow_refresh.xml
deleted file mode 100644
index b2bb97a5d..000000000
--- a/test/assets/flow_refresh.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/flow_refresh_duplicate.xml b/test/assets/flow_refresh_duplicate.xml
deleted file mode 100644
index 38a258933..000000000
--- a/test/assets/flow_refresh_duplicate.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-Resource Conflict Job for \'extract\' is already queued. Not queuing a duplicate.
diff --git a/test/assets/flow_runs_get.xml b/test/assets/flow_runs_get.xml
deleted file mode 100644
index 489e8ac63..000000000
--- a/test/assets/flow_runs_get.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
diff --git a/test/assets/flow_runs_get_by_id.xml b/test/assets/flow_runs_get_by_id.xml
deleted file mode 100644
index 3a768fab4..000000000
--- a/test/assets/flow_runs_get_by_id.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/flow_runs_get_by_id_failed.xml b/test/assets/flow_runs_get_by_id_failed.xml
deleted file mode 100644
index 9e766680b..000000000
--- a/test/assets/flow_runs_get_by_id_failed.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/flow_runs_get_by_id_inprogress.xml b/test/assets/flow_runs_get_by_id_inprogress.xml
deleted file mode 100644
index 42e1a77f9..000000000
--- a/test/assets/flow_runs_get_by_id_inprogress.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/flow_update.xml b/test/assets/flow_update.xml
deleted file mode 100644
index 5ab69f583..000000000
--- a/test/assets/flow_update.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/group_add_user.xml b/test/assets/group_add_user.xml
deleted file mode 100644
index c628952d8..000000000
--- a/test/assets/group_add_user.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/group_add_users.xml b/test/assets/group_add_users.xml
deleted file mode 100644
index 23fd7bd9f..000000000
--- a/test/assets/group_add_users.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/test/assets/group_create.xml b/test/assets/group_create.xml
deleted file mode 100644
index face05cf0..000000000
--- a/test/assets/group_create.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/group_create_ad.xml b/test/assets/group_create_ad.xml
deleted file mode 100644
index 26ddd94b0..000000000
--- a/test/assets/group_create_ad.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/group_create_async.xml b/test/assets/group_create_async.xml
deleted file mode 100644
index 8c7ac1c22..000000000
--- a/test/assets/group_create_async.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/group_get.xml b/test/assets/group_get.xml
deleted file mode 100644
index 36f52ee31..000000000
--- a/test/assets/group_get.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/group_get_all_fields.xml b/test/assets/group_get_all_fields.xml
deleted file mode 100644
index 0118250e1..000000000
--- a/test/assets/group_get_all_fields.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/group_populate_users.xml b/test/assets/group_populate_users.xml
deleted file mode 100644
index be8395efd..000000000
--- a/test/assets/group_populate_users.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/group_populate_users_empty.xml b/test/assets/group_populate_users_empty.xml
deleted file mode 100644
index 5d921df03..000000000
--- a/test/assets/group_populate_users_empty.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/group_update.xml b/test/assets/group_update.xml
deleted file mode 100644
index 3c54524c0..000000000
--- a/test/assets/group_update.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/group_update_async.xml b/test/assets/group_update_async.xml
deleted file mode 100644
index ea6b47eaa..000000000
--- a/test/assets/group_update_async.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
diff --git a/test/assets/group_users_added.xml b/test/assets/group_users_added.xml
deleted file mode 100644
index 41768d1c9..000000000
--- a/test/assets/group_users_added.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/groupsets_create.xml b/test/assets/groupsets_create.xml
deleted file mode 100644
index 233b0f939..000000000
--- a/test/assets/groupsets_create.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/test/assets/groupsets_get.xml b/test/assets/groupsets_get.xml
deleted file mode 100644
index ff3bec1fb..000000000
--- a/test/assets/groupsets_get.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/groupsets_get_by_id.xml b/test/assets/groupsets_get_by_id.xml
deleted file mode 100644
index 558e4d870..000000000
--- a/test/assets/groupsets_get_by_id.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/groupsets_update.xml b/test/assets/groupsets_update.xml
deleted file mode 100644
index b64fa6ea1..000000000
--- a/test/assets/groupsets_update.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/job_get.xml b/test/assets/job_get.xml
deleted file mode 100644
index 4a9f271cc..000000000
--- a/test/assets/job_get.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/job_get_by_id.xml b/test/assets/job_get_by_id.xml
deleted file mode 100644
index b142dfe2f..000000000
--- a/test/assets/job_get_by_id.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
- Job detail notes
-
-
- More detail
-
-
-
diff --git a/test/assets/job_get_by_id_completed.xml b/test/assets/job_get_by_id_completed.xml
deleted file mode 100644
index 95ca29b49..000000000
--- a/test/assets/job_get_by_id_completed.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
- Job detail notes
-
-
- More detail
-
-
-
\ No newline at end of file
diff --git a/test/assets/job_get_by_id_failed.xml b/test/assets/job_get_by_id_failed.xml
deleted file mode 100644
index c7456008e..000000000
--- a/test/assets/job_get_by_id_failed.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
- c569ee62-9204-416f-843d-5ccfebc0231b
-
-
-
\ No newline at end of file
diff --git a/test/assets/job_get_by_id_failed_workbook.xml b/test/assets/job_get_by_id_failed_workbook.xml
deleted file mode 100644
index bf81d896e..000000000
--- a/test/assets/job_get_by_id_failed_workbook.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
- java.lang.RuntimeException: [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Login failed for user.\nIntegrated authentication failed.
-
-
-
diff --git a/test/assets/job_get_by_id_inprogress.xml b/test/assets/job_get_by_id_inprogress.xml
deleted file mode 100644
index 7a23fb99d..000000000
--- a/test/assets/job_get_by_id_inprogress.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
- c569ee62-9204-416f-843d-5ccfebc0231b
-
-
-
\ No newline at end of file
diff --git a/test/assets/linked_tasks_get.xml b/test/assets/linked_tasks_get.xml
deleted file mode 100644
index 23b7bbbbc..000000000
--- a/test/assets/linked_tasks_get.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/linked_tasks_run_now.xml b/test/assets/linked_tasks_run_now.xml
deleted file mode 100644
index 63cef73b1..000000000
--- a/test/assets/linked_tasks_run_now.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
diff --git a/test/assets/metadata_paged_1.json b/test/assets/metadata_paged_1.json
deleted file mode 100644
index c1cc0318e..000000000
--- a/test/assets/metadata_paged_1.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "data": {
- "publishedDatasourcesConnection": {
- "pageInfo": {
- "hasNextPage": true,
- "endCursor": "eyJ0eXBlIjoiUHVibGlzaGVkRGF0YXNvdXJjZSIsInNjb3BlIjoic2l0ZXMvMSIsInNvcnRPcmRlclZhbHVlIjp7Imxhc3RJZCI6IjAwMzllNWQ1LTI1ZmEtMTk2Yi1jNjZlLWMwNjc1ODM5ZTBiMCJ9fQ=="
- },
- "nodes": [
- {
- "id": "0039e5d5-25fa-196b-c66e-c0675839e0b0"
- }
- ]
- }
- }
-}
\ No newline at end of file
diff --git a/test/assets/metadata_paged_2.json b/test/assets/metadata_paged_2.json
deleted file mode 100644
index af9601d59..000000000
--- a/test/assets/metadata_paged_2.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "data": {
- "publishedDatasourcesConnection": {
- "pageInfo": {
- "hasNextPage": true,
- "endCursor": "eyJ0eXBlIjoiUHVibGlzaGVkRGF0YXNvdXJjZSIsInNjb3BlIjoic2l0ZXMvMSIsInNvcnRPcmRlclZhbHVlIjp7Imxhc3RJZCI6IjAwYjE5MWNlLTYwNTUtYWZmNS1lMjc1LWMyNjYxMGM4YzRkNiJ9fQ=="
- },
- "nodes": [
- {
- "id": "00b191ce-6055-aff5-e275-c26610c8c4d6"
- }
- ]
- }
- }
-}
\ No newline at end of file
diff --git a/test/assets/metadata_paged_3.json b/test/assets/metadata_paged_3.json
deleted file mode 100644
index 958a408ea..000000000
--- a/test/assets/metadata_paged_3.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
- "data": {
- "publishedDatasourcesConnection": {
- "pageInfo": {
- "hasNextPage": false,
- "endCursor": "eyJ0eXBlIjoiUHVibGlzaGVkRGF0YXNvdXJjZSIsInNjb3BlIjoic2l0ZXMvMSIsInNvcnRPcmRlclZhbHVlIjp7Imxhc3RJZCI6IjAyZjNlNGQ4LTg1NmEtZGEzNi1mNmM1LWM5MDA5NDVjNTdiOSJ9fQ=="
- },
- "nodes": [
- {
- "id": "02f3e4d8-856a-da36-f6c5-c900945c57b9"
- }
- ]
- }
- }
-}
\ No newline at end of file
diff --git a/test/assets/metadata_query_error.json b/test/assets/metadata_query_error.json
deleted file mode 100644
index 1c575ee23..000000000
--- a/test/assets/metadata_query_error.json
+++ /dev/null
@@ -1,29 +0,0 @@
-{
- "data": {
- "publishedDatasources": [
- {
- "id": "01cf92b2-2d17-b656-fc48-5c25ef6d5352",
- "name": "Batters (TestV1)"
- },
- {
- "id": "020ae1cd-c356-f1ad-a846-b0094850d22a",
- "name": "SharePoint_List_sharepoint2010.test.tsi.lan"
- },
- {
- "id": "061493a0-c3b2-6f39-d08c-bc3f842b44af",
- "name": "Batters_mongodb"
- },
- {
- "id": "089fe515-ad2f-89bc-94bd-69f55f69a9c2",
- "name": "Sample - Superstore"
- }
- ]
- },
- "errors": [
- {
- "message": "Reached time limit of PT5S for query execution.",
- "path": null,
- "extensions": null
- }
- ]
-}
\ No newline at end of file
diff --git a/test/assets/metadata_query_expected_dict.dict b/test/assets/metadata_query_expected_dict.dict
deleted file mode 100644
index 241b333d4..000000000
--- a/test/assets/metadata_query_expected_dict.dict
+++ /dev/null
@@ -1,9 +0,0 @@
-{'pages': [{'data': {'publishedDatasourcesConnection': {'nodes': [{'id': '0039e5d5-25fa-196b-c66e-c0675839e0b0'}],
- 'pageInfo': {'endCursor': 'eyJ0eXBlIjoiUHVibGlzaGVkRGF0YXNvdXJjZSIsInNjb3BlIjoic2l0ZXMvMSIsInNvcnRPcmRlclZhbHVlIjp7Imxhc3RJZCI6IjAwMzllNWQ1LTI1ZmEtMTk2Yi1jNjZlLWMwNjc1ODM5ZTBiMCJ9fQ==',
- 'hasNextPage': True}}}},
- {'data': {'publishedDatasourcesConnection': {'nodes': [{'id': '00b191ce-6055-aff5-e275-c26610c8c4d6'}],
- 'pageInfo': {'endCursor': 'eyJ0eXBlIjoiUHVibGlzaGVkRGF0YXNvdXJjZSIsInNjb3BlIjoic2l0ZXMvMSIsInNvcnRPcmRlclZhbHVlIjp7Imxhc3RJZCI6IjAwYjE5MWNlLTYwNTUtYWZmNS1lMjc1LWMyNjYxMGM4YzRkNiJ9fQ==',
- 'hasNextPage': True}}}},
- {'data': {'publishedDatasourcesConnection': {'nodes': [{'id': '02f3e4d8-856a-da36-f6c5-c900945c57b9'}],
- 'pageInfo': {'endCursor': 'eyJ0eXBlIjoiUHVibGlzaGVkRGF0YXNvdXJjZSIsInNjb3BlIjoic2l0ZXMvMSIsInNvcnRPcmRlclZhbHVlIjp7Imxhc3RJZCI6IjAyZjNlNGQ4LTg1NmEtZGEzNi1mNmM1LWM5MDA5NDVjNTdiOSJ9fQ==',
- 'hasNextPage': False}}}}]}
\ No newline at end of file
diff --git a/test/assets/metadata_query_success.json b/test/assets/metadata_query_success.json
deleted file mode 100644
index 056f29fb6..000000000
--- a/test/assets/metadata_query_success.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
- "data": {
- "publishedDatasources": [
- {
- "id": "01cf92b2-2d17-b656-fc48-5c25ef6d5352",
- "name": "Batters (TestV1)"
- },
- {
- "id": "020ae1cd-c356-f1ad-a846-b0094850d22a",
- "name": "SharePoint_List_sharepoint2010.test.tsi.lan"
- },
- {
- "id": "061493a0-c3b2-6f39-d08c-bc3f842b44af",
- "name": "Batters_mongodb"
- },
- {
- "id": "089fe515-ad2f-89bc-94bd-69f55f69a9c2",
- "name": "Sample - Superstore"
- }
- ]
- }
- }
\ No newline at end of file
diff --git a/test/assets/metrics_get.xml b/test/assets/metrics_get.xml
deleted file mode 100644
index 566af1074..000000000
--- a/test/assets/metrics_get.xml
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/metrics_get_by_id.xml b/test/assets/metrics_get_by_id.xml
deleted file mode 100644
index 30652da0f..000000000
--- a/test/assets/metrics_get_by_id.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/metrics_update.xml b/test/assets/metrics_update.xml
deleted file mode 100644
index 30652da0f..000000000
--- a/test/assets/metrics_update.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/odata_connection.xml b/test/assets/odata_connection.xml
deleted file mode 100644
index 0c16fcca6..000000000
--- a/test/assets/odata_connection.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
diff --git a/test/assets/oidc_create.xml b/test/assets/oidc_create.xml
deleted file mode 100644
index cbe632f3b..000000000
--- a/test/assets/oidc_create.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
diff --git a/test/assets/oidc_get.xml b/test/assets/oidc_get.xml
deleted file mode 100644
index cbe632f3b..000000000
--- a/test/assets/oidc_get.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
diff --git a/test/assets/oidc_update.xml b/test/assets/oidc_update.xml
deleted file mode 100644
index cbe632f3b..000000000
--- a/test/assets/oidc_update.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
diff --git a/test/assets/populate_csv.csv b/test/assets/populate_csv.csv
deleted file mode 100644
index b12de4304..000000000
--- a/test/assets/populate_csv.csv
+++ /dev/null
@@ -1,25 +0,0 @@
-Measure Names,Region,Profit Ratio,Sales per Customer,Distinct count of Customer Name,Measure Values,Profit,Quantity,Sales
-Count of Customers,South,14.4%,$711.83,438,438,"$45,047","5,004","$311,784"
-Sales,South,14.4%,$711.83,438,"311,783.644","$45,047","5,004","$311,784"
-Quantity,South,14.4%,$711.83,438,"5,004","$45,047","5,004","$311,784"
-Sales per Customer,South,14.4%,$711.83,438,711.834803653,"$45,047","5,004","$311,784"
-Profit,South,14.4%,$711.83,438,"45,047.2231","$45,047","5,004","$311,784"
-Profit Ratio,South,14.4%,$711.83,438,0.144482316,"$45,047","5,004","$311,784"
-Count of Customers,Central,9.3%,$746.66,566,566,"$39,176","6,990","$422,611"
-Sales,Central,9.3%,$746.66,566,"422,610.558800001","$39,176","6,990","$422,611"
-Quantity,Central,9.3%,$746.66,566,"6,990","$39,176","6,990","$422,611"
-Sales per Customer,Central,9.3%,$746.66,566,746.661764664,"$39,176","6,990","$422,611"
-Profit,Central,9.3%,$746.66,566,"39,176.1836","$39,176","6,990","$422,611"
-Profit Ratio,Central,9.3%,$746.66,566,0.092700437,"$39,176","6,990","$422,611"
-Count of Customers,East,12.7%,$825.74,624,624,"$65,476","8,255","$515,262"
-Sales,East,12.7%,$825.74,624,"515,261.598000001","$65,476","8,255","$515,262"
-Quantity,East,12.7%,$825.74,624,"8,255","$65,476","8,255","$515,262"
-Sales per Customer,East,12.7%,$825.74,624,825.739740385,"$65,476","8,255","$515,262"
-Profit,East,12.7%,$825.74,624,"65,475.852700000","$65,476","8,255","$515,262"
-Profit Ratio,East,12.7%,$825.74,624,0.127073030,"$65,476","8,255","$515,262"
-Count of Customers,West,14.4%,$906.73,630,630,"$82,264","9,544","$571,239"
-Sales,West,14.4%,$906.73,630,"571,239.036500001","$82,264","9,544","$571,239"
-Quantity,West,14.4%,$906.73,630,"9,544","$82,264","9,544","$571,239"
-Sales per Customer,West,14.4%,$906.73,630,906.728629365,"$82,264","9,544","$571,239"
-Profit,West,14.4%,$906.73,630,"82,263.903800000","$82,264","9,544","$571,239"
-Profit Ratio,West,14.4%,$906.73,630,0.144009598,"$82,264","9,544","$571,239"
diff --git a/test/assets/populate_excel.xlsx b/test/assets/populate_excel.xlsx
deleted file mode 100644
index 3cf6115c7..000000000
Binary files a/test/assets/populate_excel.xlsx and /dev/null differ
diff --git a/test/assets/populate_pdf.pdf b/test/assets/populate_pdf.pdf
deleted file mode 100644
index 4d3319442..000000000
Binary files a/test/assets/populate_pdf.pdf and /dev/null differ
diff --git a/test/assets/populate_powerpoint.pptx b/test/assets/populate_powerpoint.pptx
deleted file mode 100644
index dbf979c06..000000000
Binary files a/test/assets/populate_powerpoint.pptx and /dev/null differ
diff --git a/test/assets/project_content_permission.xml b/test/assets/project_content_permission.xml
deleted file mode 100644
index 18341e2ac..000000000
--- a/test/assets/project_content_permission.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/test/assets/project_create.xml b/test/assets/project_create.xml
deleted file mode 100644
index 5cd29d954..000000000
--- a/test/assets/project_create.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/test/assets/project_get.xml b/test/assets/project_get.xml
deleted file mode 100644
index 7898c8c13..000000000
--- a/test/assets/project_get.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/project_get_all_fields.xml b/test/assets/project_get_all_fields.xml
deleted file mode 100644
index d71ebd922..000000000
--- a/test/assets/project_get_all_fields.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/project_populate_permissions.xml b/test/assets/project_populate_permissions.xml
deleted file mode 100644
index 7a49391af..000000000
--- a/test/assets/project_populate_permissions.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/project_populate_virtualconnection_default_permissions.xml b/test/assets/project_populate_virtualconnection_default_permissions.xml
deleted file mode 100644
index 10678f794..000000000
--- a/test/assets/project_populate_virtualconnection_default_permissions.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/project_populate_workbook_default_permissions.xml b/test/assets/project_populate_workbook_default_permissions.xml
deleted file mode 100644
index e6f3804be..000000000
--- a/test/assets/project_populate_workbook_default_permissions.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/project_update.xml b/test/assets/project_update.xml
deleted file mode 100644
index f2485c898..000000000
--- a/test/assets/project_update.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/test/assets/project_update_datasource_default_permissions.xml b/test/assets/project_update_datasource_default_permissions.xml
deleted file mode 100644
index 3a70031ce..000000000
--- a/test/assets/project_update_datasource_default_permissions.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/project_update_virtualconnection_default_permissions.xml b/test/assets/project_update_virtualconnection_default_permissions.xml
deleted file mode 100644
index 10b5ba6ec..000000000
--- a/test/assets/project_update_virtualconnection_default_permissions.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/queryset_slicing_page_1.xml b/test/assets/queryset_slicing_page_1.xml
deleted file mode 100644
index be3df91f8..000000000
--- a/test/assets/queryset_slicing_page_1.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/queryset_slicing_page_2.xml b/test/assets/queryset_slicing_page_2.xml
deleted file mode 100644
index 058bbd5c0..000000000
--- a/test/assets/queryset_slicing_page_2.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/request_option_filter_equals.xml b/test/assets/request_option_filter_equals.xml
deleted file mode 100644
index 95c3043ed..000000000
--- a/test/assets/request_option_filter_equals.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/request_option_filter_name_in.xml b/test/assets/request_option_filter_name_in.xml
deleted file mode 100644
index 9ec42b8ab..000000000
--- a/test/assets/request_option_filter_name_in.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/request_option_filter_tags_in.xml b/test/assets/request_option_filter_tags_in.xml
deleted file mode 100644
index 5c32c0df6..000000000
--- a/test/assets/request_option_filter_tags_in.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/request_option_page_number.xml b/test/assets/request_option_page_number.xml
deleted file mode 100644
index c6e79ad1d..000000000
--- a/test/assets/request_option_page_number.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/request_option_page_size.xml b/test/assets/request_option_page_size.xml
deleted file mode 100644
index d9736ac52..000000000
--- a/test/assets/request_option_page_size.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/request_option_pagination.xml b/test/assets/request_option_pagination.xml
deleted file mode 100644
index 68eea1bf3..000000000
--- a/test/assets/request_option_pagination.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/request_option_slicing_queryset.xml b/test/assets/request_option_slicing_queryset.xml
deleted file mode 100644
index 34708c911..000000000
--- a/test/assets/request_option_slicing_queryset.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/schedule_add_datasource.xml b/test/assets/schedule_add_datasource.xml
deleted file mode 100644
index e57d2c8d2..000000000
--- a/test/assets/schedule_add_datasource.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/schedule_add_flow.xml b/test/assets/schedule_add_flow.xml
deleted file mode 100644
index 9934c38e5..000000000
--- a/test/assets/schedule_add_flow.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/schedule_add_workbook.xml b/test/assets/schedule_add_workbook.xml
deleted file mode 100644
index a6adb005e..000000000
--- a/test/assets/schedule_add_workbook.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/schedule_add_workbook_with_warnings.xml b/test/assets/schedule_add_workbook_with_warnings.xml
deleted file mode 100644
index 1eac2ceef..000000000
--- a/test/assets/schedule_add_workbook_with_warnings.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/schedule_batch_update_state.xml b/test/assets/schedule_batch_update_state.xml
deleted file mode 100644
index 7749a3eeb..000000000
--- a/test/assets/schedule_batch_update_state.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
- 593d2ebf-0d18-4deb-9d21-b113a4902583
- cecbb71e-def0-4030-8068-5ae50f51db1c
- f39a6e7d-405e-4c07-8c18-95845f9da80e
-
-
diff --git a/test/assets/schedule_create_daily.xml b/test/assets/schedule_create_daily.xml
deleted file mode 100644
index fe1eda485..000000000
--- a/test/assets/schedule_create_daily.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/schedule_create_hourly.xml b/test/assets/schedule_create_hourly.xml
deleted file mode 100644
index b1c3b73c3..000000000
--- a/test/assets/schedule_create_hourly.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/schedule_create_monthly.xml b/test/assets/schedule_create_monthly.xml
deleted file mode 100644
index 408ff428d..000000000
--- a/test/assets/schedule_create_monthly.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/schedule_create_weekly.xml b/test/assets/schedule_create_weekly.xml
deleted file mode 100644
index a12a6eace..000000000
--- a/test/assets/schedule_create_weekly.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/schedule_get.xml b/test/assets/schedule_get.xml
deleted file mode 100644
index db5e1a05e..000000000
--- a/test/assets/schedule_get.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/schedule_get_by_id.xml b/test/assets/schedule_get_by_id.xml
deleted file mode 100644
index 943416beb..000000000
--- a/test/assets/schedule_get_by_id.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/schedule_get_customized_monthly_id.xml b/test/assets/schedule_get_customized_monthly_id.xml
deleted file mode 100644
index cc2bf5606..000000000
--- a/test/assets/schedule_get_customized_monthly_id.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/schedule_get_daily_id.xml b/test/assets/schedule_get_daily_id.xml
deleted file mode 100644
index 99467a391..000000000
--- a/test/assets/schedule_get_daily_id.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/schedule_get_empty.xml b/test/assets/schedule_get_empty.xml
deleted file mode 100644
index c40943303..000000000
--- a/test/assets/schedule_get_empty.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/schedule_get_extract_refresh_tasks.xml b/test/assets/schedule_get_extract_refresh_tasks.xml
deleted file mode 100644
index 48906dde6..000000000
--- a/test/assets/schedule_get_extract_refresh_tasks.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/schedule_get_hourly_id.xml b/test/assets/schedule_get_hourly_id.xml
deleted file mode 100644
index 27c374ccf..000000000
--- a/test/assets/schedule_get_hourly_id.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/schedule_get_monthly_id.xml b/test/assets/schedule_get_monthly_id.xml
deleted file mode 100644
index 3fc32cc57..000000000
--- a/test/assets/schedule_get_monthly_id.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/schedule_get_monthly_id_2.xml b/test/assets/schedule_get_monthly_id_2.xml
deleted file mode 100644
index ca84297e7..000000000
--- a/test/assets/schedule_get_monthly_id_2.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/schedule_update.xml b/test/assets/schedule_update.xml
deleted file mode 100644
index 7b814fdbc..000000000
--- a/test/assets/schedule_update.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/server_info_25.xml b/test/assets/server_info_25.xml
deleted file mode 100644
index 1e6eaffa6..000000000
--- a/test/assets/server_info_25.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-10.1.0
-2.5
-
-
\ No newline at end of file
diff --git a/test/assets/server_info_404.xml b/test/assets/server_info_404.xml
deleted file mode 100644
index a23abf9ae..000000000
--- a/test/assets/server_info_404.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
- Resource Not Found
- Unknown resource '/2.4/serverInfo' specified in URI.
-
-
diff --git a/test/assets/server_info_auth_info.xml b/test/assets/server_info_auth_info.xml
deleted file mode 100644
index 58d9c5baf..000000000
--- a/test/assets/server_info_auth_info.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-0.31
-0.31
-9.2
-9.3
-9.3.4
-hello.16.1106.2025
-unrestricted
-2.6
-
-
diff --git a/test/assets/server_info_get.xml b/test/assets/server_info_get.xml
deleted file mode 100644
index 94218502a..000000000
--- a/test/assets/server_info_get.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-10.1.0
-3.10
-
-
diff --git a/test/assets/server_info_wrong_site.html b/test/assets/server_info_wrong_site.html
deleted file mode 100644
index e92daeb2d..000000000
--- a/test/assets/server_info_wrong_site.html
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
-
-
-
- Example website
-
-
-
-
-
- A
- B
- C
- D
- E
-
-
- 1
- 2
- 3
- 4
- 5
-
-
- 2
- 3
- 4
- 5
- 6
-
-
- 3
- 4
- 5
- 6
- 7
-
-
- 4
- 5
- 6
- 7
- 8
-
-
- 5
- 6
- 7
- 8
- 9
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/site_auth_configurations.xml b/test/assets/site_auth_configurations.xml
deleted file mode 100644
index c81d179ac..000000000
--- a/test/assets/site_auth_configurations.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
diff --git a/test/assets/site_create.xml b/test/assets/site_create.xml
deleted file mode 100644
index 9d9c4a009..000000000
--- a/test/assets/site_create.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/site_get.xml b/test/assets/site_get.xml
deleted file mode 100644
index 7ffa91eb7..000000000
--- a/test/assets/site_get.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/site_get_by_id.xml b/test/assets/site_get_by_id.xml
deleted file mode 100644
index a8a1e9a5c..000000000
--- a/test/assets/site_get_by_id.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/test/assets/site_get_by_name.xml b/test/assets/site_get_by_name.xml
deleted file mode 100644
index b7ae2b595..000000000
--- a/test/assets/site_get_by_name.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/test/assets/site_update.xml b/test/assets/site_update.xml
deleted file mode 100644
index 1661a426b..000000000
--- a/test/assets/site_update.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/test/assets/subscription_create.xml b/test/assets/subscription_create.xml
deleted file mode 100644
index 48f391416..000000000
--- a/test/assets/subscription_create.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/subscription_get.xml b/test/assets/subscription_get.xml
deleted file mode 100644
index b66ffc927..000000000
--- a/test/assets/subscription_get.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/subscription_get_by_id.xml b/test/assets/subscription_get_by_id.xml
deleted file mode 100644
index 0677da63d..000000000
--- a/test/assets/subscription_get_by_id.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/table_get.xml b/test/assets/table_get.xml
deleted file mode 100644
index 0bd2763d5..000000000
--- a/test/assets/table_get.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/table_update.xml b/test/assets/table_update.xml
deleted file mode 100644
index 975f0cedb..000000000
--- a/test/assets/table_update.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/tasks_create_extract_task.xml b/test/assets/tasks_create_extract_task.xml
deleted file mode 100644
index 9e6310fba..000000000
--- a/test/assets/tasks_create_extract_task.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/tasks_create_flow_task.xml b/test/assets/tasks_create_flow_task.xml
deleted file mode 100644
index 11c9a4ff0..000000000
--- a/test/assets/tasks_create_flow_task.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/tasks_no_workbook_or_datasource.xml b/test/assets/tasks_no_workbook_or_datasource.xml
deleted file mode 100644
index da84194bf..000000000
--- a/test/assets/tasks_no_workbook_or_datasource.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/tasks_run_now_response.xml b/test/assets/tasks_run_now_response.xml
deleted file mode 100644
index 6a8860cd7..000000000
--- a/test/assets/tasks_run_now_response.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
diff --git a/test/assets/tasks_with_dataacceleration_task.xml b/test/assets/tasks_with_dataacceleration_task.xml
deleted file mode 100644
index beb5d59eb..000000000
--- a/test/assets/tasks_with_dataacceleration_task.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
- 2019-12-09T20:45:04Z
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/tasks_with_datasource.xml b/test/assets/tasks_with_datasource.xml
deleted file mode 100644
index 097161bf7..000000000
--- a/test/assets/tasks_with_datasource.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/tasks_with_interval.xml b/test/assets/tasks_with_interval.xml
deleted file mode 100644
index a317408fb..000000000
--- a/test/assets/tasks_with_interval.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/tasks_with_workbook.xml b/test/assets/tasks_with_workbook.xml
deleted file mode 100644
index 81e974e78..000000000
--- a/test/assets/tasks_with_workbook.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/tasks_with_workbook_and_datasource.xml b/test/assets/tasks_with_workbook_and_datasource.xml
deleted file mode 100644
index 81777bb46..000000000
--- a/test/assets/tasks_with_workbook_and_datasource.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/tasks_without_schedule.xml b/test/assets/tasks_without_schedule.xml
deleted file mode 100644
index e669bf67f..000000000
--- a/test/assets/tasks_without_schedule.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/user_add.xml b/test/assets/user_add.xml
deleted file mode 100644
index 6a19ab0e6..000000000
--- a/test/assets/user_add.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/user_add_favorite.xml b/test/assets/user_add_favorite.xml
deleted file mode 100644
index 7bfe442d8..000000000
--- a/test/assets/user_add_favorite.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/user_get.xml b/test/assets/user_get.xml
deleted file mode 100644
index 83557b2eb..000000000
--- a/test/assets/user_get.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/user_get_all_fields.xml b/test/assets/user_get_all_fields.xml
deleted file mode 100644
index 7e9a62568..000000000
--- a/test/assets/user_get_all_fields.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/user_get_by_id.xml b/test/assets/user_get_by_id.xml
deleted file mode 100644
index 6caba72f9..000000000
--- a/test/assets/user_get_by_id.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/user_get_empty.xml b/test/assets/user_get_empty.xml
deleted file mode 100644
index 3d9320432..000000000
--- a/test/assets/user_get_empty.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/user_populate_groups.xml b/test/assets/user_populate_groups.xml
deleted file mode 100644
index 567f1dbf8..000000000
--- a/test/assets/user_populate_groups.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/user_populate_workbooks.xml b/test/assets/user_populate_workbooks.xml
deleted file mode 100644
index 2a817c87e..000000000
--- a/test/assets/user_populate_workbooks.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/user_update.xml b/test/assets/user_update.xml
deleted file mode 100644
index a6982c536..000000000
--- a/test/assets/user_update.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/users_bulk_add_job.xml b/test/assets/users_bulk_add_job.xml
deleted file mode 100644
index 7301ac7d3..000000000
--- a/test/assets/users_bulk_add_job.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/test/assets/view_add_tags.xml b/test/assets/view_add_tags.xml
deleted file mode 100644
index d7ffbd680..000000000
--- a/test/assets/view_add_tags.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/view_get.xml b/test/assets/view_get.xml
deleted file mode 100644
index 283488a4b..000000000
--- a/test/assets/view_get.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/view_get_all_fields.xml b/test/assets/view_get_all_fields.xml
deleted file mode 100644
index 236ebd726..000000000
--- a/test/assets/view_get_all_fields.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/view_get_id.xml b/test/assets/view_get_id.xml
deleted file mode 100644
index 6110a0a3a..000000000
--- a/test/assets/view_get_id.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/view_get_id_usage.xml b/test/assets/view_get_id_usage.xml
deleted file mode 100644
index a0cdd98db..000000000
--- a/test/assets/view_get_id_usage.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/view_get_usage.xml b/test/assets/view_get_usage.xml
deleted file mode 100644
index 741e607e7..000000000
--- a/test/assets/view_get_usage.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/view_populate_permissions.xml b/test/assets/view_populate_permissions.xml
deleted file mode 100644
index e73616f46..000000000
--- a/test/assets/view_populate_permissions.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/view_update_permissions.xml b/test/assets/view_update_permissions.xml
deleted file mode 100644
index 2e78a4a90..000000000
--- a/test/assets/view_update_permissions.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/virtual_connection_add_permissions.xml b/test/assets/virtual_connection_add_permissions.xml
deleted file mode 100644
index d8b052848..000000000
--- a/test/assets/virtual_connection_add_permissions.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/virtual_connection_database_connection_update.xml b/test/assets/virtual_connection_database_connection_update.xml
deleted file mode 100644
index a6135d604..000000000
--- a/test/assets/virtual_connection_database_connection_update.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
diff --git a/test/assets/virtual_connection_populate_connections.xml b/test/assets/virtual_connection_populate_connections.xml
deleted file mode 100644
index 0835e478f..000000000
--- a/test/assets/virtual_connection_populate_connections.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/test/assets/virtual_connection_populate_connections2.xml b/test/assets/virtual_connection_populate_connections2.xml
deleted file mode 100644
index 78ff90f65..000000000
--- a/test/assets/virtual_connection_populate_connections2.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
diff --git a/test/assets/virtual_connections_download.xml b/test/assets/virtual_connections_download.xml
deleted file mode 100644
index 889e70ce7..000000000
--- a/test/assets/virtual_connections_download.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
- {"policyCollection":{"luid":"34ae5eb9-ceac-4158-86f1-a5d8163d5261","policies":[]},"revision":{"luid":"1b2e2aae-b904-4f5a-aa4d-9f114b8e5f57","revisableProperties":{}}}
-
-
diff --git a/test/assets/virtual_connections_get.xml b/test/assets/virtual_connections_get.xml
deleted file mode 100644
index f1f410e4c..000000000
--- a/test/assets/virtual_connections_get.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
diff --git a/test/assets/virtual_connections_publish.xml b/test/assets/virtual_connections_publish.xml
deleted file mode 100644
index 889e70ce7..000000000
--- a/test/assets/virtual_connections_publish.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
- {"policyCollection":{"luid":"34ae5eb9-ceac-4158-86f1-a5d8163d5261","policies":[]},"revision":{"luid":"1b2e2aae-b904-4f5a-aa4d-9f114b8e5f57","revisableProperties":{}}}
-
-
diff --git a/test/assets/virtual_connections_revisions.xml b/test/assets/virtual_connections_revisions.xml
deleted file mode 100644
index 374113427..000000000
--- a/test/assets/virtual_connections_revisions.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/virtual_connections_update.xml b/test/assets/virtual_connections_update.xml
deleted file mode 100644
index 60d5d1697..000000000
--- a/test/assets/virtual_connections_update.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
diff --git a/test/assets/webhook_create.xml b/test/assets/webhook_create.xml
deleted file mode 100644
index 24a5ca99b..000000000
--- a/test/assets/webhook_create.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/webhook_create_request.xml b/test/assets/webhook_create_request.xml
deleted file mode 100644
index 0578c2c48..000000000
--- a/test/assets/webhook_create_request.xml
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/test/assets/webhook_get.xml b/test/assets/webhook_get.xml
deleted file mode 100644
index 7d527fc00..000000000
--- a/test/assets/webhook_get.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/webhook_get_new_event.xml b/test/assets/webhook_get_new_event.xml
deleted file mode 100644
index 776a7ac99..000000000
--- a/test/assets/webhook_get_new_event.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/workbook_add_tags.xml b/test/assets/workbook_add_tags.xml
deleted file mode 100644
index 8af59ecc9..000000000
--- a/test/assets/workbook_add_tags.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/workbook_get.xml b/test/assets/workbook_get.xml
deleted file mode 100644
index 873ca3848..000000000
--- a/test/assets/workbook_get.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/workbook_get_all_fields.xml b/test/assets/workbook_get_all_fields.xml
deleted file mode 100644
index 007b79338..000000000
--- a/test/assets/workbook_get_all_fields.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/workbook_get_by_id.xml b/test/assets/workbook_get_by_id.xml
deleted file mode 100644
index 98dfc4a75..000000000
--- a/test/assets/workbook_get_by_id.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/workbook_get_by_id_acceleration_status.xml b/test/assets/workbook_get_by_id_acceleration_status.xml
deleted file mode 100644
index 0d1f9b93d..000000000
--- a/test/assets/workbook_get_by_id_acceleration_status.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/workbook_get_by_id_personal.xml b/test/assets/workbook_get_by_id_personal.xml
deleted file mode 100644
index 90cc65e73..000000000
--- a/test/assets/workbook_get_by_id_personal.xml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/workbook_get_empty.xml b/test/assets/workbook_get_empty.xml
deleted file mode 100644
index 688638634..000000000
--- a/test/assets/workbook_get_empty.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/workbook_get_invalid_date.xml b/test/assets/workbook_get_invalid_date.xml
deleted file mode 100644
index c580f9eb6..000000000
--- a/test/assets/workbook_get_invalid_date.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/workbook_get_page_1.xml b/test/assets/workbook_get_page_1.xml
deleted file mode 100644
index a5dfdcf89..000000000
--- a/test/assets/workbook_get_page_1.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/workbook_get_page_2.xml b/test/assets/workbook_get_page_2.xml
deleted file mode 100644
index 456cc1bcf..000000000
--- a/test/assets/workbook_get_page_2.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/workbook_get_page_3.xml b/test/assets/workbook_get_page_3.xml
deleted file mode 100644
index e2fad1f2b..000000000
--- a/test/assets/workbook_get_page_3.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/workbook_populate_connections.xml b/test/assets/workbook_populate_connections.xml
deleted file mode 100644
index 6362d81fd..000000000
--- a/test/assets/workbook_populate_connections.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/workbook_populate_permissions.xml b/test/assets/workbook_populate_permissions.xml
deleted file mode 100644
index 57517d719..000000000
--- a/test/assets/workbook_populate_permissions.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/workbook_populate_views.xml b/test/assets/workbook_populate_views.xml
deleted file mode 100644
index 06eb3e989..000000000
--- a/test/assets/workbook_populate_views.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/workbook_populate_views_usage.xml b/test/assets/workbook_populate_views_usage.xml
deleted file mode 100644
index a75e4037f..000000000
--- a/test/assets/workbook_populate_views_usage.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/workbook_publish.xml b/test/assets/workbook_publish.xml
deleted file mode 100644
index 3e23bda71..000000000
--- a/test/assets/workbook_publish.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/workbook_publish_async.xml b/test/assets/workbook_publish_async.xml
deleted file mode 100644
index 21e4e83ed..000000000
--- a/test/assets/workbook_publish_async.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/test/assets/workbook_refresh.xml b/test/assets/workbook_refresh.xml
deleted file mode 100644
index 6f5da8283..000000000
--- a/test/assets/workbook_refresh.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/workbook_refresh_duplicate.xml b/test/assets/workbook_refresh_duplicate.xml
deleted file mode 100644
index eca4b4bcc..000000000
--- a/test/assets/workbook_refresh_duplicate.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-Resource Conflict Job for \'extract\' is already queued. Not queuing a duplicate.
\ No newline at end of file
diff --git a/test/assets/workbook_revision.xml b/test/assets/workbook_revision.xml
deleted file mode 100644
index 8cadafc8f..000000000
--- a/test/assets/workbook_revision.xml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/workbook_update.xml b/test/assets/workbook_update.xml
deleted file mode 100644
index 6e5d36105..000000000
--- a/test/assets/workbook_update.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/workbook_update_acceleration_status.xml b/test/assets/workbook_update_acceleration_status.xml
deleted file mode 100644
index 7c3366fee..000000000
--- a/test/assets/workbook_update_acceleration_status.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/workbook_update_connections.xml b/test/assets/workbook_update_connections.xml
deleted file mode 100644
index ce6ca227f..000000000
--- a/test/assets/workbook_update_connections.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
diff --git a/test/assets/workbook_update_connections_no_auth.xml b/test/assets/workbook_update_connections_no_auth.xml
deleted file mode 100644
index 21860fa06..000000000
--- a/test/assets/workbook_update_connections_no_auth.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
diff --git a/test/assets/workbook_update_data_freshness_policy.xml b/test/assets/workbook_update_data_freshness_policy.xml
deleted file mode 100644
index a69a097ba..000000000
--- a/test/assets/workbook_update_data_freshness_policy.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/workbook_update_data_freshness_policy2.xml b/test/assets/workbook_update_data_freshness_policy2.xml
deleted file mode 100644
index 384f79ec0..000000000
--- a/test/assets/workbook_update_data_freshness_policy2.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/workbook_update_data_freshness_policy3.xml b/test/assets/workbook_update_data_freshness_policy3.xml
deleted file mode 100644
index 195013517..000000000
--- a/test/assets/workbook_update_data_freshness_policy3.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/workbook_update_data_freshness_policy4.xml b/test/assets/workbook_update_data_freshness_policy4.xml
deleted file mode 100644
index 8208d986a..000000000
--- a/test/assets/workbook_update_data_freshness_policy4.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/workbook_update_data_freshness_policy5.xml b/test/assets/workbook_update_data_freshness_policy5.xml
deleted file mode 100644
index b6e0358b6..000000000
--- a/test/assets/workbook_update_data_freshness_policy5.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/workbook_update_data_freshness_policy6.xml b/test/assets/workbook_update_data_freshness_policy6.xml
deleted file mode 100644
index c8be8f6c1..000000000
--- a/test/assets/workbook_update_data_freshness_policy6.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/assets/workbook_update_permissions.xml b/test/assets/workbook_update_permissions.xml
deleted file mode 100644
index fffd90491..000000000
--- a/test/assets/workbook_update_permissions.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/test/assets/workbook_update_views_acceleration_status.xml b/test/assets/workbook_update_views_acceleration_status.xml
deleted file mode 100644
index f2055fb79..000000000
--- a/test/assets/workbook_update_views_acceleration_status.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/test/http/__init__.py b/test/http/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/test/http/test_http_requests.py b/test/http/test_http_requests.py
deleted file mode 100644
index 494d458ea..000000000
--- a/test/http/test_http_requests.py
+++ /dev/null
@@ -1,131 +0,0 @@
-import pytest
-import tableauserverclient as TSC
-import requests
-import requests_mock
-
-from unittest import mock
-from requests.exceptions import MissingSchema
-
-
-# This method will be used by the mock to replace requests.get
-def mocked_requests_get(*args, **kwargs):
- class MockResponse:
- def __init__(self, status_code):
- self.headers = {}
- self.encoding = None
- self.content = (
- ""
- ""
- "0.31 "
- "0.31 "
- "2022.3 "
- " "
- " "
- )
- self.status_code = status_code
-
- return MockResponse(200)
-
-
-def test_init_server_model_empty_throws():
- with pytest.raises(TypeError):
- server = TSC.Server()
-
-
-def test_init_server_model_no_protocol_defaults_htt():
- server = TSC.Server("fake-url")
-
-
-def test_init_server_model_valid_server_name_works():
- server = TSC.Server("http://fake-url")
-
-
-def test_init_server_model_valid_https_server_name_works():
- # by default, it will just set the version to 2.3
- server = TSC.Server("https://fake-url")
-
-
-def test_init_server_model_bad_server_name_not_version_check():
- server = TSC.Server("fake-url", use_server_version=False)
-
-
-def test_init_server_model_bad_server_name_do_version_check():
- # use_server_version=True no longer makes a network call at construction;
- # version detection is deferred until sign_in
- server = TSC.Server("fake-url", use_server_version=True)
- assert server._use_server_version is True
-
-
-def test_init_server_model_bad_server_name_not_version_check_random_options():
- server = TSC.Server("fake-url", use_server_version=False, http_options={"foo": 1})
-
-
-def test_init_server_model_bad_server_name_not_version_check_real_options():
- server = TSC.Server("fake-url", use_server_version=False, http_options={"verify": False})
-
-
-def test_http_options_skip_ssl_works():
- http_options = {"verify": False}
- server = TSC.Server("http://fake-url")
- server.add_http_options(http_options)
-
-
-def test_http_options_multiple_options_works():
- http_options = {"verify": False, "birdname": "Parrot"}
- server = TSC.Server("http://fake-url")
- server.add_http_options(http_options)
-
-
-# ValueError: dictionary update sequence element #0 has length 1; 2 is required
-def test_http_options_multiple_dicts_fails():
- http_options_1 = {"verify": False}
- http_options_2 = {"birdname": "Parrot"}
- server = TSC.Server("http://fake-url")
- with pytest.raises(ValueError):
- server.add_http_options([http_options_1, http_options_2])
-
-
-# TypeError: cannot convert dictionary update sequence element #0 to a sequence
-def test_http_options_not_sequence_fails():
- server = TSC.Server("http://fake-url")
- with pytest.raises(ValueError):
- server.add_http_options({1, 2, 3})
-
-
-def test_validate_connection_http():
- url = "http://cookies.com"
- server = TSC.Server(url)
- server.validate_connection_settings()
- assert url == server.server_address
-
-
-def test_validate_connection_https():
- url = "https://cookies.com"
- server = TSC.Server(url)
- server.validate_connection_settings()
- assert url == server.server_address
-
-
-def test_validate_connection_no_protocol():
- url = "cookies.com"
- fixed_url = "http://cookies.com"
- server = TSC.Server(url)
- server.validate_connection_settings()
- assert fixed_url == server.server_address
-
-
-test_header = {"x-test": "true"}
-
-
-@pytest.fixture
-def session_factory() -> requests.Session:
- session = requests.session()
- session.headers.update(test_header)
- return session
-
-
-def test_session_factory_adds_headers(session_factory):
- test_request_bin = "http://capture-this-with-mock.com"
- with requests_mock.mock() as m:
- m.get(url="http://capture-this-with-mock.com/api/2.4/serverInfo", request_headers=test_header)
- server = TSC.Server(test_request_bin, use_server_version=True, session_factory=lambda: session_factory)
diff --git a/test/models/__init__.py b/test/models/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/test/models/_models.py b/test/models/_models.py
deleted file mode 100644
index 9be97a87b..000000000
--- a/test/models/_models.py
+++ /dev/null
@@ -1,37 +0,0 @@
-from tableauserverclient import *
-
-# TODO why aren't these available in the tsc namespace? Probably a bug.
-from tableauserverclient.models import (
- DataAccelerationReportItem,
- Credentials,
- ServerInfoItem,
- Resource,
- TableauItem,
-)
-
-
-def get_unimplemented_models():
- return [
- # these items should have repr , please fix
- CollectionItem,
- DQWItem,
- ExtensionsServer,
- ExtensionsSiteSettings,
- FileuploadItem,
- FlowRunItem,
- LinkedTaskFlowRunItem,
- LinkedTaskItem,
- LinkedTaskStepItem,
- SafeExtension,
- # these should be implemented together for consistency
- CSVRequestOptions,
- ExcelRequestOptions,
- ImageRequestOptions,
- PDFRequestOptions,
- PPTXRequestOptions,
- RequestOptions,
- # these don't need it
- FavoriteItem, # no repr because there is no state
- Resource, # list of type names
- TableauItem, # should be an interface
- ]
diff --git a/test/models/test_repr.py b/test/models/test_repr.py
deleted file mode 100644
index 34f8509a7..000000000
--- a/test/models/test_repr.py
+++ /dev/null
@@ -1,48 +0,0 @@
-import inspect
-from typing import Any
-from test.models._models import get_unimplemented_models
-import tableauserverclient as TSC
-
-import pytest
-
-
-def is_concrete(obj: Any):
- return inspect.isclass(obj) and not inspect.isabstract(obj)
-
-
-@pytest.mark.parametrize("class_name, obj", inspect.getmembers(TSC, is_concrete))
-def test_by_reflection(class_name, obj):
- instance = try_instantiate_class(class_name, obj)
- if instance:
- class_type = type(instance)
- if class_type in get_unimplemented_models():
- print(f"Class '{class_name}' has no repr defined, skipping test")
- return
- else:
- assert type(instance.__repr__).__name__ == "method"
- print(instance.__repr__.__name__)
-
-
-# Instantiate a class if it doesn't require any parameters
-def try_instantiate_class(name: str, obj: Any) -> Any | None:
- # Get the constructor (init) of the class
- constructor = getattr(obj, "__init__", None)
- if constructor:
- # Get the parameters of the constructor (excluding 'self')
- parameters = inspect.signature(constructor).parameters.values()
- required_parameters = [
- param for param in parameters if param.default == inspect.Parameter.empty and param.name != "self"
- ]
- if required_parameters:
- print(f"Class '{name}' requires the following parameters for instantiation:")
- for param in required_parameters:
- print(f"- {param.name}")
- return None
- else:
- print(f"Class '{name}' does not require any parameters for instantiation.")
- # Instantiate the class
- instance = obj()
- return instance
- else:
- print(f"Class '{name}' does not have a constructor (__init__ method).")
- return None
diff --git a/test/request_factory/__init__.py b/test/request_factory/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/test/request_factory/test_datasource_requests.py b/test/request_factory/test_datasource_requests.py
deleted file mode 100644
index 66b7a373e..000000000
--- a/test/request_factory/test_datasource_requests.py
+++ /dev/null
@@ -1,13 +0,0 @@
-import tableauserverclient as TSC
-import tableauserverclient.server.request_factory as TSC_RF
-from tableauserverclient import DatasourceItem
-
-
-def test_generate_xml():
- datasource_item: TSC.DatasourceItem = TSC.DatasourceItem("name")
- datasource_item.name = "a ds"
- datasource_item.description = "described"
- datasource_item.use_remote_query_agent = False
- datasource_item.ask_data_enablement = DatasourceItem.AskDataEnablement.Enabled
- datasource_item.project_id = "testval"
- TSC_RF.RequestFactory.Datasource._generate_xml(datasource_item)
diff --git a/test/request_factory/test_task_requests.py b/test/request_factory/test_task_requests.py
deleted file mode 100644
index bf2ccd5fe..000000000
--- a/test/request_factory/test_task_requests.py
+++ /dev/null
@@ -1,61 +0,0 @@
-import xml.etree.ElementTree as ET
-from unittest.mock import Mock
-
-import pytest
-
-from tableauserverclient.server.request_factory import TaskRequest
-
-
-@pytest.fixture
-def task_request() -> TaskRequest:
- return TaskRequest()
-
-
-@pytest.fixture
-def xml_request() -> ET.Element:
- return ET.Element("tsRequest")
-
-
-def test_refresh_req_default(task_request: TaskRequest, xml_request: ET.Element) -> None:
- result = task_request.refresh_req()
- assert result == ET.tostring(xml_request)
-
-
-def test_refresh_req_incremental(task_request: TaskRequest) -> None:
- with pytest.raises(ValueError):
- task_request.refresh_req(incremental=True)
-
-
-def test_refresh_req_with_parent_srv_version_3_25(task_request: TaskRequest) -> None:
- parent_srv = Mock()
- parent_srv.check_at_least_version.return_value = True
- result = task_request.refresh_req(incremental=True, parent_srv=parent_srv)
- expected_xml = ET.Element("tsRequest")
- task_element = ET.SubElement(expected_xml, "extractRefresh")
- task_element.attrib["incremental"] = "true"
- assert result == ET.tostring(expected_xml)
-
-
-def test_refresh_req_with_parent_srv_version_3_25_non_incremental(task_request: TaskRequest) -> None:
- parent_srv = Mock()
- parent_srv.check_at_least_version.return_value = True
- result = task_request.refresh_req(incremental=False, parent_srv=parent_srv)
- expected_xml = ET.Element("tsRequest")
- ET.SubElement(expected_xml, "extractRefresh")
- assert result == ET.tostring(expected_xml)
-
-
-def test_refresh_req_with_parent_srv_version_below_3_25(task_request: TaskRequest) -> None:
- parent_srv = Mock()
- parent_srv.check_at_least_version.return_value = False
- with pytest.raises(ValueError):
- task_request.refresh_req(incremental=True, parent_srv=parent_srv)
-
-
-def test_refresh_req_with_parent_srv_version_below_3_25_non_incremental(
- task_request: TaskRequest, xml_request: ET.Element
-) -> None:
- parent_srv = Mock()
- parent_srv.check_at_least_version.return_value = False
- result = task_request.refresh_req(incremental=False, parent_srv=parent_srv)
- assert result == ET.tostring(xml_request)
diff --git a/test/request_factory/test_workbook_requests.py b/test/request_factory/test_workbook_requests.py
deleted file mode 100644
index b114e04a0..000000000
--- a/test/request_factory/test_workbook_requests.py
+++ /dev/null
@@ -1,58 +0,0 @@
-import tableauserverclient as TSC
-import tableauserverclient.server.request_factory as TSC_RF
-from tableauserverclient.helpers.strings import redact_xml
-import pytest
-import sys
-
-
-def test_embedded_extract_req() -> None:
- include_all = True
- embedded_datasources = None
- xml_result = TSC_RF.RequestFactory.Workbook.embedded_extract_req(include_all, embedded_datasources)
-
-
-def test_generate_xml() -> None:
- workbook_item: TSC.WorkbookItem = TSC.WorkbookItem("name", "project_id")
- TSC_RF.RequestFactory.Workbook._generate_xml(workbook_item)
-
-
-def test_generate_xml_invalid_connection() -> None:
- workbook_item: TSC.WorkbookItem = TSC.WorkbookItem("name", "project_id")
- conn = TSC.ConnectionItem()
- with pytest.raises(ValueError):
- request = TSC_RF.RequestFactory.Workbook._generate_xml(workbook_item, connections=[conn])
-
-
-def test_generate_xml_invalid_connection_credentials() -> None:
- workbook_item: TSC.WorkbookItem = TSC.WorkbookItem("name", "project_id")
- conn = TSC.ConnectionItem()
- conn.server_address = "address"
- creds = TSC.ConnectionCredentials("username", "password")
- creds.name = None
- conn.connection_credentials = creds
- with pytest.raises(ValueError):
- request = TSC_RF.RequestFactory.Workbook._generate_xml(workbook_item, connections=[conn])
-
-
-def test_generate_xml_valid_connection_credentials() -> None:
- workbook_item: TSC.WorkbookItem = TSC.WorkbookItem("name", "project_id")
- conn = TSC.ConnectionItem()
- conn.server_address = "address"
- creds = TSC.ConnectionCredentials("username", "DELETEME")
- conn.connection_credentials = creds
- request = TSC_RF.RequestFactory.Workbook._generate_xml(workbook_item, connections=[conn])
- assert request.find(b"DELETEME") > 0
-
-
-def test_redact_passwords_in_xml() -> None:
- if sys.version_info < (3, 7):
- pytest.skip("Redaction is only implemented for 3.7+.")
- workbook_item: TSC.WorkbookItem = TSC.WorkbookItem("name", "project_id")
- conn = TSC.ConnectionItem()
- conn.server_address = "address"
- creds = TSC.ConnectionCredentials("username", "DELETEME")
- conn.connection_credentials = creds
- request = TSC_RF.RequestFactory.Workbook._generate_xml(workbook_item, connections=[conn])
- redacted = redact_xml(request)
- assert request.find(b"DELETEME") > 0, request
- assert redacted.find(b"DELETEME") == -1, redacted
diff --git a/test/test_auth.py b/test/test_auth.py
deleted file mode 100644
index c50f4d29b..000000000
--- a/test/test_auth.py
+++ /dev/null
@@ -1,146 +0,0 @@
-from pathlib import Path
-
-import pytest
-import requests_mock
-
-import tableauserverclient as TSC
-
-TEST_ASSET_DIR = Path(__file__).parent / "assets"
-
-SIGN_IN_XML = TEST_ASSET_DIR / "auth_sign_in.xml"
-SIGN_IN_IMPERSONATE_XML = TEST_ASSET_DIR / "auth_sign_in_impersonate.xml"
-SIGN_IN_ERROR_XML = TEST_ASSET_DIR / "auth_sign_in_error.xml"
-
-
-@pytest.fixture(scope="function")
-def server() -> TSC.Server:
- """Fixture to create a Tableau Server instance for testing."""
- server_instance = TSC.Server("http://test", False)
- return server_instance
-
-
-def test_sign_in(server: TSC.Server) -> None:
- with open(SIGN_IN_XML, "rb") as f:
- response_xml = f.read().decode("utf-8")
- with requests_mock.mock() as m:
- m.post(server.auth.baseurl + "/signin", text=response_xml)
- tableau_auth = TSC.TableauAuth("testuser", "password", site_id="Samples")
- server.auth.sign_in(tableau_auth)
-
- assert "eIX6mvFsqyansa4KqEI1UwOpS8ggRs2l" == server.auth_token
- assert "6b7179ba-b82b-4f0f-91ed-812074ac5da6" == server.site_id
- assert "Samples" == server.site_url
- assert "1a96d216-e9b8-497b-a82a-0b899a965e01" == server.user_id
-
-
-def test_sign_in_with_personal_access_tokens(server: TSC.Server) -> None:
- with open(SIGN_IN_XML, "rb") as f:
- response_xml = f.read().decode("utf-8")
- with requests_mock.mock() as m:
- m.post(server.auth.baseurl + "/signin", text=response_xml)
- tableau_auth = TSC.PersonalAccessTokenAuth(
- token_name="mytoken", personal_access_token="Random123Generated", site_id="Samples"
- )
- server.auth.sign_in(tableau_auth)
-
- assert "eIX6mvFsqyansa4KqEI1UwOpS8ggRs2l" == server.auth_token
- assert "6b7179ba-b82b-4f0f-91ed-812074ac5da6" == server.site_id
- assert "Samples" == server.site_url
- assert "1a96d216-e9b8-497b-a82a-0b899a965e01" == server.user_id
-
-
-def test_sign_in_impersonate(server: TSC.Server) -> None:
- with open(SIGN_IN_IMPERSONATE_XML, "rb") as f:
- response_xml = f.read().decode("utf-8")
- with requests_mock.mock() as m:
- m.post(server.auth.baseurl + "/signin", text=response_xml)
- tableau_auth = TSC.TableauAuth(
- "testuser", "password", user_id_to_impersonate="dd2239f6-ddf1-4107-981a-4cf94e415794"
- )
- server.auth.sign_in(tableau_auth)
-
- assert "MJonFA6HDyy2C3oqR13fRGqE6cmgzwq3" == server.auth_token
- assert "dad65087-b08b-4603-af4e-2887b8aafc67" == server.site_id
- assert "dd2239f6-ddf1-4107-981a-4cf94e415794" == server.user_id
-
-
-def test_sign_in_error(server: TSC.Server) -> None:
- with open(SIGN_IN_ERROR_XML, "rb") as f:
- response_xml = f.read().decode("utf-8")
- with requests_mock.mock() as m:
- m.post(server.auth.baseurl + "/signin", text=response_xml, status_code=401)
- tableau_auth = TSC.TableauAuth("testuser", "wrongpassword")
- with pytest.raises(TSC.FailedSignInError):
- server.auth.sign_in(tableau_auth)
-
-
-def test_sign_in_invalid_token(server: TSC.Server) -> None:
- with open(SIGN_IN_ERROR_XML, "rb") as f:
- response_xml = f.read().decode("utf-8")
- with requests_mock.mock() as m:
- m.post(server.auth.baseurl + "/signin", text=response_xml, status_code=401)
- tableau_auth = TSC.PersonalAccessTokenAuth(token_name="mytoken", personal_access_token="invalid")
- with pytest.raises(TSC.FailedSignInError):
- server.auth.sign_in(tableau_auth)
-
-
-def test_sign_in_without_auth(server: TSC.Server) -> None:
- with open(SIGN_IN_ERROR_XML, "rb") as f:
- response_xml = f.read().decode("utf-8")
- with requests_mock.mock() as m:
- m.post(server.auth.baseurl + "/signin", text=response_xml, status_code=401)
- tableau_auth = TSC.TableauAuth("", "")
- with pytest.raises(TSC.FailedSignInError):
- server.auth.sign_in(tableau_auth)
-
-
-def test_sign_out(server: TSC.Server) -> None:
- with open(SIGN_IN_XML, "rb") as f:
- response_xml = f.read().decode("utf-8")
- with requests_mock.mock() as m:
- m.post(server.auth.baseurl + "/signin", text=response_xml)
- m.post(server.auth.baseurl + "/signout", text="")
- tableau_auth = TSC.TableauAuth("testuser", "password")
- server.auth.sign_in(tableau_auth)
- server.auth.sign_out()
-
- assert server._auth_token is None
- assert server._site_id is None
- assert server._site_url is None
- assert server._user_id is None
-
-
-def test_switch_site(server: TSC.Server) -> None:
- server.version = "2.6"
- baseurl = server.auth.baseurl
- site_id, user_id, auth_token = list("123")
- server._set_auth(site_id, user_id, auth_token)
- with open(SIGN_IN_XML, "rb") as f:
- response_xml = f.read().decode("utf-8")
- with requests_mock.mock() as m:
- m.post(baseurl + "/switchSite", text=response_xml)
- site = TSC.SiteItem("Samples", "Samples")
- server.auth.switch_site(site)
-
- assert "eIX6mvFsqyansa4KqEI1UwOpS8ggRs2l" == server.auth_token
- assert "6b7179ba-b82b-4f0f-91ed-812074ac5da6" == server.site_id
- assert "Samples" == server.site_url
- assert "1a96d216-e9b8-497b-a82a-0b899a965e01" == server.user_id
-
-
-def test_revoke_all_server_admin_tokens(server: TSC.Server) -> None:
- server.version = "3.10"
- baseurl = server.auth.baseurl
- with open(SIGN_IN_XML, "rb") as f:
- response_xml = f.read().decode("utf-8")
- with requests_mock.mock() as m:
- m.post(baseurl + "/signin", text=response_xml)
- m.post(baseurl + "/revokeAllServerAdminTokens", text="")
- tableau_auth = TSC.TableauAuth("testuser", "password")
- server.auth.sign_in(tableau_auth)
- server.auth.revoke_all_server_admin_tokens()
-
- assert "eIX6mvFsqyansa4KqEI1UwOpS8ggRs2l" == server.auth_token
- assert "6b7179ba-b82b-4f0f-91ed-812074ac5da6" == server.site_id
- assert "Samples" == server.site_url
- assert "1a96d216-e9b8-497b-a82a-0b899a965e01" == server.user_id
diff --git a/test/test_connection_.py b/test/test_connection_.py
deleted file mode 100644
index d15cd969b..000000000
--- a/test/test_connection_.py
+++ /dev/null
@@ -1,107 +0,0 @@
-import xml.etree.ElementTree as ET
-from pathlib import Path
-
-import tableauserverclient as TSC
-
-import pytest
-
-ASSETS_DIR = Path(__file__).parent / "assets"
-NS = {"t": "http://tableau.com/api"}
-
-
-def test_require_boolean_query_tag_fails() -> None:
- conn = TSC.ConnectionItem()
- conn._connection_type = "postgres"
- with pytest.raises(ValueError):
- conn.query_tagging = "no" # type: ignore[assignment]
-
-
-def test_set_query_tag_normal_conn() -> None:
- conn = TSC.ConnectionItem()
- conn._connection_type = "postgres"
- conn.query_tagging = True
- assert conn.query_tagging
-
-
-@pytest.mark.parametrize("conn_type", ["hyper", "teradata", "snowflake"])
-def test_ignore_query_tag(conn_type: str) -> None:
- conn = TSC.ConnectionItem()
- conn._connection_type = conn_type
- conn.query_tagging = True
- assert conn.query_tagging is None
-
-
-def test_database_name_default_none() -> None:
- conn = TSC.ConnectionItem()
- assert conn.database_name is None
-
-
-def test_database_name_getter_setter() -> None:
- conn = TSC.ConnectionItem()
- conn.database_name = "my_database"
- assert conn.database_name == "my_database"
-
-
-def test_database_name_from_response_parses_db_name() -> None:
- xml = """
-
-
-
-
- """
- connections = TSC.ConnectionItem.from_response(xml, NS)
- assert len(connections) == 1
- assert connections[0].database_name == "SalesDB"
-
-
-def test_database_name_from_response_none_when_absent() -> None:
- xml = """
-
-
-
-
- """
- connections = TSC.ConnectionItem.from_response(xml, NS)
- assert len(connections) == 1
- assert connections[0].database_name is None
-
-
-def test_database_name_parsed_from_xml_asset() -> None:
- response_xml = (ASSETS_DIR / "datasource_populate_connections.xml").read_text()
- connections = TSC.ConnectionItem.from_response(response_xml, NS)
- assert len(connections) == 2
- conn_with_db = next(c for c in connections if c.id == "be786ae0-d2bf-4a4b-9b34-e2de8d2d4488")
- conn_without_db = next(c for c in connections if c.id == "970e24bc-e200-4841-a3e9-66e7d122d77e")
- assert conn_with_db.database_name == "SalesDB"
- assert conn_without_db.database_name is None
-
-
-def test_database_name_round_trip() -> None:
- """database_name parsed from GET response (dbName) round-trips through
- _add_connections_element which emits databaseName in the publish request."""
- import xml.etree.ElementTree as ET
- from tableauserverclient.server.request_factory import _add_connections_element
-
- # Parse from a GET-style response (attribute name: dbName)
- xml = """
-
-
-
-
- """
- connections = TSC.ConnectionItem.from_response(xml, NS)
- assert len(connections) == 1
- conn = connections[0]
- assert conn.database_name == "Northwind"
-
- # Now emit as a publish request element and confirm databaseName is used
- conn.server_address = "db.example.com" # already set, but make it explicit
- parent_elem = ET.Element("connections")
- _add_connections_element(parent_elem, conn)
- connection_elem = parent_elem.find("connection")
- assert connection_elem is not None
- assert connection_elem.attrib.get("databaseName") == "Northwind"
- assert "dbName" not in connection_elem.attrib
diff --git a/test/test_custom_view.py b/test/test_custom_view.py
deleted file mode 100644
index 6cbe4b454..000000000
--- a/test/test_custom_view.py
+++ /dev/null
@@ -1,390 +0,0 @@
-from contextlib import ExitStack
-import io
-import os
-from pathlib import Path
-from tempfile import TemporaryDirectory
-
-import pytest
-import requests_mock
-
-import tableauserverclient as TSC
-from tableauserverclient.config import BYTES_PER_MB
-from tableauserverclient.datetime_helpers import format_datetime
-
-TEST_ASSET_DIR = Path(__file__).parent / "assets"
-
-GET_XML = TEST_ASSET_DIR / "custom_view_get.xml"
-GET_XML_ID = TEST_ASSET_DIR / "custom_view_get_id.xml"
-POPULATE_PREVIEW_IMAGE = TEST_ASSET_DIR / "Sample View Image.png"
-CUSTOM_VIEW_UPDATE_XML = TEST_ASSET_DIR / "custom_view_update.xml"
-CUSTOM_VIEW_POPULATE_PDF = TEST_ASSET_DIR / "populate_pdf.pdf"
-CUSTOM_VIEW_POPULATE_CSV = TEST_ASSET_DIR / "populate_csv.csv"
-CUSTOM_VIEW_DOWNLOAD = TEST_ASSET_DIR / "custom_view_download.json"
-FILE_UPLOAD_INIT = TEST_ASSET_DIR / "fileupload_initialize.xml"
-FILE_UPLOAD_APPEND = TEST_ASSET_DIR / "fileupload_append.xml"
-
-
-@pytest.fixture(scope="function")
-def server() -> TSC.Server:
- server = TSC.Server("http://test", False)
- server.version = "3.21" # custom views only introduced in 3.19
-
- # Fake sign in
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
-
- return server
-
-
-def test_get(server: TSC.Server) -> None:
- response_xml = GET_XML.read_text()
- print(response_xml)
- with requests_mock.mock() as m:
- m.get(server.custom_views.baseurl, text=response_xml)
- all_views, pagination_item = server.custom_views.get()
-
- assert 2 == pagination_item.total_available
- assert "d79634e1-6063-4ec9-95ff-50acbf609ff5" == all_views[0].id
- assert "ENDANGERED SAFARI" == all_views[0].name
- assert "SafariSample/sheets/ENDANGEREDSAFARI" == all_views[0].content_url
- assert "3cc6cd06-89ce-4fdc-b935-5294135d6d42" == all_views[0].workbook.id
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == all_views[0].owner.id
- assert all_views[0].created_at is None
- assert all_views[0].updated_at is None
- assert not all_views[0].shared
-
- assert "fd252f73-593c-4c4e-8584-c032b8022adc" == all_views[1].id
- assert "Overview" == all_views[1].name
- assert "6d13b0ca-043d-4d42-8c9d-3f3313ea3a00" == all_views[1].workbook.id
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == all_views[1].owner.id
- assert "2002-05-30T09:00:00Z" == format_datetime(all_views[1].created_at)
- assert "2002-06-05T08:00:59Z" == format_datetime(all_views[1].updated_at)
- assert all_views[1].shared
-
-
-def test_get_by_id(server: TSC.Server) -> None:
- response_xml = GET_XML_ID.read_text()
- with requests_mock.mock() as m:
- m.get(server.custom_views.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5", text=response_xml)
- view: TSC.CustomViewItem = server.custom_views.get_by_id("d79634e1-6063-4ec9-95ff-50acbf609ff5")
-
- assert "d79634e1-6063-4ec9-95ff-50acbf609ff5" == view.id
- assert "ENDANGERED SAFARI" == view.name
- assert "SafariSample/sheets/ENDANGEREDSAFARI" == view.content_url
- if view.workbook:
- assert "3cc6cd06-89ce-4fdc-b935-5294135d6d42" == view.workbook.id
- if view.owner:
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == view.owner.id
- if view.view:
- assert "5241e88d-d384-4fd7-9c2f-648b5247efc5" == view.view.id
- assert "2002-05-30T09:00:00Z" == format_datetime(view.created_at)
- assert "2002-06-05T08:00:59Z" == format_datetime(view.updated_at)
-
-
-def test_get_by_id_missing_id(server: TSC.Server) -> None:
- with pytest.raises(TSC.MissingRequiredFieldError):
- server.custom_views.get_by_id(None)
-
-
-def test_get_before_signin(server: TSC.Server) -> None:
- server._auth_token = None
- with pytest.raises(TSC.NotSignedInError):
- server.custom_views.get()
-
-
-def test_populate_image(server: TSC.Server) -> None:
- response = POPULATE_PREVIEW_IMAGE.read_bytes()
- with requests_mock.mock() as m:
- m.get(server.custom_views.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/image", content=response)
- single_view = TSC.CustomViewItem()
- single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5"
- server.custom_views.populate_image(single_view)
- assert response == single_view.image
-
-
-def test_populate_image_with_options(server: TSC.Server) -> None:
- response = POPULATE_PREVIEW_IMAGE.read_bytes()
- with requests_mock.mock() as m:
- m.get(
- server.custom_views.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/image?resolution=high&maxAge=10",
- content=response,
- )
- single_view = TSC.CustomViewItem()
- single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5"
- req_option = TSC.ImageRequestOptions(imageresolution=TSC.ImageRequestOptions.Resolution.High, maxage=10)
- server.custom_views.populate_image(single_view, req_option)
- assert response == single_view.image
-
-
-def test_populate_image_svg_format(server: TSC.Server) -> None:
- server.version = "3.29"
- response = b"test "
- with requests_mock.mock() as m:
- m.get(
- server.custom_views.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/image?format=SVG",
- content=response,
- )
- single_view = TSC.CustomViewItem()
- single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5"
- req_option = TSC.ImageRequestOptions(format=TSC.ImageRequestOptions.Format.SVG)
- server.custom_views.populate_image(single_view, req_option)
- assert response == single_view.image
-
-
-def test_populate_image_png_format(server: TSC.Server) -> None:
- server.version = "3.29"
- response = POPULATE_PREVIEW_IMAGE.read_bytes()
- with requests_mock.mock() as m:
- m.get(
- server.custom_views.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/image?format=PNG",
- content=response,
- )
- single_view = TSC.CustomViewItem()
- single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5"
- req_option = TSC.ImageRequestOptions(format=TSC.ImageRequestOptions.Format.PNG)
- server.custom_views.populate_image(single_view, req_option)
- assert response == single_view.image
-
-
-def test_populate_image_format_unsupported_version(server: TSC.Server) -> None:
- from tableauserverclient.server.endpoint.exceptions import UnsupportedAttributeError
-
- server.version = "3.28"
- response = POPULATE_PREVIEW_IMAGE.read_bytes()
- with requests_mock.mock() as m:
- m.get(
- server.custom_views.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/image?format=SVG",
- content=response,
- )
- single_view = TSC.CustomViewItem()
- single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5"
- req_option = TSC.ImageRequestOptions(format=TSC.ImageRequestOptions.Format.SVG)
-
- with pytest.raises(UnsupportedAttributeError):
- server.custom_views.populate_image(single_view, req_option)
-
-
-def test_populate_image_missing_id(server: TSC.Server) -> None:
- single_view = TSC.CustomViewItem()
- single_view._id = None
- with pytest.raises(TSC.MissingRequiredFieldError):
- server.custom_views.populate_image(single_view)
-
-
-def test_delete(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.delete(server.custom_views.baseurl + "/3cc6cd06-89ce-4fdc-b935-5294135d6d42", status_code=204)
- server.custom_views.delete("3cc6cd06-89ce-4fdc-b935-5294135d6d42")
-
-
-def test_delete_missing_id(server: TSC.Server) -> None:
- with pytest.raises(ValueError):
- server.custom_views.delete("")
-
-
-def test_update(server: TSC.Server) -> None:
- response_xml = CUSTOM_VIEW_UPDATE_XML.read_text()
- with requests_mock.mock() as m:
- m.put(server.custom_views.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2", text=response_xml)
- the_custom_view = TSC.CustomViewItem("1d0304cd-3796-429f-b815-7258370b9b74", name="Best test ever")
- the_custom_view._id = "1f951daf-4061-451a-9df1-69a8062664f2"
- the_custom_view.owner = TSC.UserItem()
- assert the_custom_view.owner is not None # for mypy
- the_custom_view.owner.id = "dd2239f6-ddf1-4107-981a-4cf94e415794"
- the_custom_view = server.custom_views.update(the_custom_view)
-
- assert isinstance(the_custom_view, TSC.CustomViewItem)
- assert "1f951daf-4061-451a-9df1-69a8062664f2" == the_custom_view.id
- if the_custom_view.owner:
- assert "dd2239f6-ddf1-4107-981a-4cf94e415794" == the_custom_view.owner.id
- assert "Best test ever" == the_custom_view.name
-
-
-def test_update_missing_id(server: TSC.Server) -> None:
- cv = TSC.CustomViewItem(name="test")
- with pytest.raises(TSC.MissingRequiredFieldError):
- server.custom_views.update(cv)
-
-
-def test_download(server: TSC.Server) -> None:
- cv = TSC.CustomViewItem(name="test")
- cv._id = "1f951daf-4061-451a-9df1-69a8062664f2"
- content = CUSTOM_VIEW_DOWNLOAD.read_bytes()
- data = io.BytesIO()
- with requests_mock.mock() as m:
- m.get(f"{server.custom_views.expurl}/1f951daf-4061-451a-9df1-69a8062664f2/content", content=content)
- server.custom_views.download(cv, data)
-
- assert data.getvalue() == content
-
-
-def test_publish_filepath(server: TSC.Server) -> None:
- cv = TSC.CustomViewItem(name="test")
- cv._owner = TSC.UserItem()
- cv._owner._id = "dd2239f6-ddf1-4107-981a-4cf94e415794"
- cv.workbook = TSC.WorkbookItem()
- assert cv.workbook is not None
- cv.workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
- with requests_mock.mock() as m:
- m.post(server.custom_views.expurl, status_code=201, text=GET_XML.read_text())
- view = server.custom_views.publish(cv, CUSTOM_VIEW_DOWNLOAD)
-
- assert view is not None
- assert isinstance(view, TSC.CustomViewItem)
- assert view.id is not None
- assert view.name is not None
-
-
-def test_publish_file_str(server: TSC.Server) -> None:
- cv = TSC.CustomViewItem(name="test")
- cv._owner = TSC.UserItem()
- cv._owner._id = "dd2239f6-ddf1-4107-981a-4cf94e415794"
- cv.workbook = TSC.WorkbookItem()
- assert cv.workbook is not None
- cv.workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
- with requests_mock.mock() as m:
- m.post(server.custom_views.expurl, status_code=201, text=GET_XML.read_text())
- view = server.custom_views.publish(cv, str(CUSTOM_VIEW_DOWNLOAD))
-
- assert view is not None
- assert isinstance(view, TSC.CustomViewItem)
- assert view.id is not None
- assert view.name is not None
-
-
-def test_publish_file_io(server: TSC.Server) -> None:
- cv = TSC.CustomViewItem(name="test")
- cv._owner = TSC.UserItem()
- cv._owner._id = "dd2239f6-ddf1-4107-981a-4cf94e415794"
- cv.workbook = TSC.WorkbookItem()
- assert cv.workbook is not None
- cv.workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
- data = io.BytesIO(CUSTOM_VIEW_DOWNLOAD.read_bytes())
- with requests_mock.mock() as m:
- m.post(server.custom_views.expurl, status_code=201, text=GET_XML.read_text())
- view = server.custom_views.publish(cv, data)
-
- assert view is not None
- assert isinstance(view, TSC.CustomViewItem)
- assert view.id is not None
- assert view.name is not None
-
-
-def test_publish_missing_owner_id(server: TSC.Server) -> None:
- cv = TSC.CustomViewItem(name="test")
- cv._owner = TSC.UserItem()
- cv.workbook = TSC.WorkbookItem()
- assert cv.workbook is not None
- cv.workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
- with requests_mock.mock() as m:
- m.post(server.custom_views.expurl, status_code=201, text=GET_XML.read_text())
- with pytest.raises(ValueError):
- server.custom_views.publish(cv, CUSTOM_VIEW_DOWNLOAD)
-
-
-def test_publish_missing_wb_id(server: TSC.Server) -> None:
- cv = TSC.CustomViewItem(name="test")
- cv._owner = TSC.UserItem()
- cv._owner._id = "dd2239f6-ddf1-4107-981a-4cf94e415794"
- cv.workbook = TSC.WorkbookItem()
- with requests_mock.mock() as m:
- m.post(server.custom_views.expurl, status_code=201, text=GET_XML.read_text())
- with pytest.raises(ValueError):
- server.custom_views.publish(cv, CUSTOM_VIEW_DOWNLOAD)
-
-
-def test_large_publish(server: TSC.Server):
- cv = TSC.CustomViewItem(name="test")
- cv._owner = TSC.UserItem()
- cv._owner._id = "dd2239f6-ddf1-4107-981a-4cf94e415794"
- cv.workbook = TSC.WorkbookItem()
- assert cv.workbook is not None
- cv.workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
- with ExitStack() as stack:
- temp_dir = stack.enter_context(TemporaryDirectory())
- file_path = Path(temp_dir) / "test_file"
- file_path.write_bytes(os.urandom(65 * BYTES_PER_MB))
- mock = stack.enter_context(requests_mock.mock())
- # Mock initializing upload
- mock.post(server.fileuploads.baseurl, status_code=201, text=FILE_UPLOAD_INIT.read_text())
- # Mock the upload
- mock.put(
- f"{server.fileuploads.baseurl}/7720:170fe6b1c1c7422dadff20f944d58a52-1:0",
- text=FILE_UPLOAD_APPEND.read_text(),
- )
- # Mock the publish
- mock.post(server.custom_views.expurl, status_code=201, text=GET_XML.read_text())
-
- view = server.custom_views.publish(cv, file_path)
-
- assert view is not None
- assert isinstance(view, TSC.CustomViewItem)
- assert view.id is not None
- assert view.name is not None
-
-
-def test_populate_pdf(server: TSC.Server) -> None:
- server.version = "3.23"
- response = CUSTOM_VIEW_POPULATE_PDF.read_bytes()
- with requests_mock.mock() as m:
- m.get(
- server.custom_views.baseurl
- + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/pdf?type=letter&orientation=portrait&maxAge=5",
- content=response,
- )
- custom_view = TSC.CustomViewItem()
- custom_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5"
-
- size = TSC.PDFRequestOptions.PageType.Letter
- orientation = TSC.PDFRequestOptions.Orientation.Portrait
- req_option = TSC.PDFRequestOptions(size, orientation, 5)
-
- server.custom_views.populate_pdf(custom_view, req_option)
- assert response == custom_view.pdf
-
-
-def test_populate_csv(server: TSC.Server) -> None:
- server.version = "3.23"
- response = CUSTOM_VIEW_POPULATE_CSV.read_bytes()
- with requests_mock.mock() as m:
- m.get(server.custom_views.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/data?maxAge=1", content=response)
- custom_view = TSC.CustomViewItem()
- custom_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5"
- request_option = TSC.CSVRequestOptions(maxage=1)
- server.custom_views.populate_csv(custom_view, request_option)
-
- csv_file = b"".join(custom_view.csv)
- assert response == csv_file
-
-
-def test_populate_csv_default_maxage(server: TSC.Server) -> None:
- server.version = "3.23"
- response = CUSTOM_VIEW_POPULATE_CSV.read_bytes()
- with requests_mock.mock() as m:
- m.get(server.custom_views.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/data", content=response)
- custom_view = TSC.CustomViewItem()
- custom_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5"
- server.custom_views.populate_csv(custom_view)
-
- csv_file = b"".join(custom_view.csv)
- assert response == csv_file
-
-
-def test_pdf_height(server: TSC.Server) -> None:
- server.version = "3.23"
- response = CUSTOM_VIEW_POPULATE_PDF.read_bytes()
- with requests_mock.mock() as m:
- m.get(
- server.custom_views.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/pdf?vizHeight=1080&vizWidth=1920",
- content=response,
- )
- custom_view = TSC.CustomViewItem()
- custom_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5"
-
- req_option = TSC.PDFRequestOptions(
- viz_height=1080,
- viz_width=1920,
- )
-
- server.custom_views.populate_pdf(custom_view, req_option)
- assert response == custom_view.pdf
diff --git a/test/test_data_acceleration_report.py b/test/test_data_acceleration_report.py
deleted file mode 100644
index c0589a84b..000000000
--- a/test/test_data_acceleration_report.py
+++ /dev/null
@@ -1,45 +0,0 @@
-from pathlib import Path
-
-import pytest
-import requests_mock
-
-import tableauserverclient as TSC
-
-TEST_ASSETS_DIR = Path(__file__).parent / "assets"
-
-GET_XML = TEST_ASSETS_DIR / "data_acceleration_report.xml"
-
-
-@pytest.fixture(scope="function")
-def server():
- server = TSC.Server("http://test", False)
-
- # Fake signin
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
- server.version = "3.8"
-
- return server
-
-
-def test_get_data_acceleration_report(server):
- response_xml = GET_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.data_acceleration_report.baseurl, text=response_xml)
- data_acceleration_report = server.data_acceleration_report.get()
-
- assert 2 == len(data_acceleration_report.comparison_records)
-
- assert "site-1" == data_acceleration_report.comparison_records[0].site
- assert "sheet-1" == data_acceleration_report.comparison_records[0].sheet_uri
- assert "0" == data_acceleration_report.comparison_records[0].unaccelerated_session_count
- assert "0.0" == data_acceleration_report.comparison_records[0].avg_non_accelerated_plt
- assert "1" == data_acceleration_report.comparison_records[0].accelerated_session_count
- assert "0.166" == data_acceleration_report.comparison_records[0].avg_accelerated_plt
-
- assert "site-2" == data_acceleration_report.comparison_records[1].site
- assert "sheet-2" == data_acceleration_report.comparison_records[1].sheet_uri
- assert "2" == data_acceleration_report.comparison_records[1].unaccelerated_session_count
- assert "1.29" == data_acceleration_report.comparison_records[1].avg_non_accelerated_plt
- assert "3" == data_acceleration_report.comparison_records[1].accelerated_session_count
- assert "0.372" == data_acceleration_report.comparison_records[1].avg_accelerated_plt
diff --git a/test/test_data_freshness_policy.py b/test/test_data_freshness_policy.py
deleted file mode 100644
index 3c5bf5cc2..000000000
--- a/test/test_data_freshness_policy.py
+++ /dev/null
@@ -1,182 +0,0 @@
-from pathlib import Path
-import requests_mock
-
-import pytest
-
-import tableauserverclient as TSC
-
-TEST_ASSET_DIR = Path(__file__).parent / "assets"
-
-UPDATE_DFP_ALWAYS_LIVE_XML = TEST_ASSET_DIR / "workbook_update_data_freshness_policy.xml"
-UPDATE_DFP_SITE_DEFAULT_XML = TEST_ASSET_DIR / "workbook_update_data_freshness_policy2.xml"
-UPDATE_DFP_FRESH_EVERY_XML = TEST_ASSET_DIR / "workbook_update_data_freshness_policy3.xml"
-UPDATE_DFP_FRESH_AT_DAILY_XML = TEST_ASSET_DIR / "workbook_update_data_freshness_policy4.xml"
-UPDATE_DFP_FRESH_AT_WEEKLY_XML = TEST_ASSET_DIR / "workbook_update_data_freshness_policy5.xml"
-UPDATE_DFP_FRESH_AT_MONTHLY_XML = TEST_ASSET_DIR / "workbook_update_data_freshness_policy6.xml"
-
-
-@pytest.fixture(scope="function")
-def server() -> TSC.Server:
- server = TSC.Server("http://test", False)
- # Fake sign in
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
-
- return server
-
-
-def test_update_DFP_always_live(server) -> None:
- response_xml = UPDATE_DFP_ALWAYS_LIVE_XML.read_text()
- with requests_mock.mock() as m:
- m.put(server.workbooks.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2", text=response_xml)
- single_workbook = TSC.WorkbookItem("1d0304cd-3796-429f-b815-7258370b9b74", show_tabs=True)
- single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
- single_workbook.data_freshness_policy = TSC.DataFreshnessPolicyItem(
- TSC.DataFreshnessPolicyItem.Option.AlwaysLive
- )
- single_workbook = server.workbooks.update(single_workbook)
-
- assert "1f951daf-4061-451a-9df1-69a8062664f2" == single_workbook.id
- assert "AlwaysLive" == single_workbook.data_freshness_policy.option
-
-
-def test_update_DFP_site_default(server) -> None:
- response_xml = UPDATE_DFP_SITE_DEFAULT_XML.read_text()
- with requests_mock.mock() as m:
- m.put(server.workbooks.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2", text=response_xml)
- single_workbook = TSC.WorkbookItem("1d0304cd-3796-429f-b815-7258370b9b74", show_tabs=True)
- single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
- single_workbook.data_freshness_policy = TSC.DataFreshnessPolicyItem(
- TSC.DataFreshnessPolicyItem.Option.SiteDefault
- )
- single_workbook = server.workbooks.update(single_workbook)
-
- assert "1f951daf-4061-451a-9df1-69a8062664f2" == single_workbook.id
- assert "SiteDefault" == single_workbook.data_freshness_policy.option
-
-
-def test_update_DFP_fresh_every(server) -> None:
- response_xml = UPDATE_DFP_FRESH_EVERY_XML.read_text()
- with requests_mock.mock() as m:
- m.put(server.workbooks.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2", text=response_xml)
- single_workbook = TSC.WorkbookItem("1d0304cd-3796-429f-b815-7258370b9b74", show_tabs=True)
- single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
- single_workbook.data_freshness_policy = TSC.DataFreshnessPolicyItem(
- TSC.DataFreshnessPolicyItem.Option.FreshEvery
- )
- fresh_every_ten_hours = TSC.DataFreshnessPolicyItem.FreshEvery(
- TSC.DataFreshnessPolicyItem.FreshEvery.Frequency.Hours, 10
- )
- single_workbook.data_freshness_policy.fresh_every_schedule = fresh_every_ten_hours
- single_workbook = server.workbooks.update(single_workbook)
-
- assert "1f951daf-4061-451a-9df1-69a8062664f2" == single_workbook.id
- assert "FreshEvery" == single_workbook.data_freshness_policy.option
- assert "Hours" == single_workbook.data_freshness_policy.fresh_every_schedule.frequency
- assert 10 == single_workbook.data_freshness_policy.fresh_every_schedule.value
-
-
-def test_update_DFP_fresh_every_missing_attributes(server) -> None:
- response_xml = UPDATE_DFP_FRESH_EVERY_XML.read_text()
- with requests_mock.mock() as m:
- m.put(server.workbooks.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2", text=response_xml)
- single_workbook = TSC.WorkbookItem("1d0304cd-3796-429f-b815-7258370b9b74", show_tabs=True)
- single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
- single_workbook.data_freshness_policy = TSC.DataFreshnessPolicyItem(
- TSC.DataFreshnessPolicyItem.Option.FreshEvery
- )
-
- with pytest.raises(ValueError):
- server.workbooks.update(single_workbook)
-
-
-def test_update_DFP_fresh_at_day(server) -> None:
- response_xml = UPDATE_DFP_FRESH_AT_DAILY_XML.read_text()
- with requests_mock.mock() as m:
- m.put(server.workbooks.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2", text=response_xml)
- single_workbook = TSC.WorkbookItem("1d0304cd-3796-429f-b815-7258370b9b74", show_tabs=True)
- single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
- single_workbook.data_freshness_policy = TSC.DataFreshnessPolicyItem(TSC.DataFreshnessPolicyItem.Option.FreshAt)
- fresh_at_10pm_daily = TSC.DataFreshnessPolicyItem.FreshAt(
- TSC.DataFreshnessPolicyItem.FreshAt.Frequency.Day, "22:00:00", " Asia/Singapore"
- )
- single_workbook.data_freshness_policy.fresh_at_schedule = fresh_at_10pm_daily
- single_workbook = server.workbooks.update(single_workbook)
-
- assert "1f951daf-4061-451a-9df1-69a8062664f2" == single_workbook.id
- assert "FreshAt" == single_workbook.data_freshness_policy.option
- assert "Day" == single_workbook.data_freshness_policy.fresh_at_schedule.frequency
- assert "22:00:00" == single_workbook.data_freshness_policy.fresh_at_schedule.time
- assert "Asia/Singapore" == single_workbook.data_freshness_policy.fresh_at_schedule.timezone
-
-
-def test_update_DFP_fresh_at_week(server) -> None:
- response_xml = UPDATE_DFP_FRESH_AT_WEEKLY_XML.read_text()
- with requests_mock.mock() as m:
- m.put(server.workbooks.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2", text=response_xml)
- single_workbook = TSC.WorkbookItem("1d0304cd-3796-429f-b815-7258370b9b74", show_tabs=True)
- single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
- single_workbook.data_freshness_policy = TSC.DataFreshnessPolicyItem(TSC.DataFreshnessPolicyItem.Option.FreshAt)
- fresh_at_10am_mon_wed = TSC.DataFreshnessPolicyItem.FreshAt(
- TSC.DataFreshnessPolicyItem.FreshAt.Frequency.Week,
- "10:00:00",
- "America/Los_Angeles",
- ["Monday", "Wednesday"],
- )
- single_workbook.data_freshness_policy.fresh_at_schedule = fresh_at_10am_mon_wed
- single_workbook = server.workbooks.update(single_workbook)
-
- assert "1f951daf-4061-451a-9df1-69a8062664f2" == single_workbook.id
- assert "FreshAt" == single_workbook.data_freshness_policy.option
- assert "Week" == single_workbook.data_freshness_policy.fresh_at_schedule.frequency
- assert "10:00:00" == single_workbook.data_freshness_policy.fresh_at_schedule.time
- assert "Wednesday" == single_workbook.data_freshness_policy.fresh_at_schedule.interval_item[0]
- assert "Monday" == single_workbook.data_freshness_policy.fresh_at_schedule.interval_item[1]
-
-
-def test_update_DFP_fresh_at_month(server) -> None:
- response_xml = UPDATE_DFP_FRESH_AT_MONTHLY_XML.read_text()
- with requests_mock.mock() as m:
- m.put(server.workbooks.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2", text=response_xml)
- single_workbook = TSC.WorkbookItem("1d0304cd-3796-429f-b815-7258370b9b74", show_tabs=True)
- single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
- single_workbook.data_freshness_policy = TSC.DataFreshnessPolicyItem(TSC.DataFreshnessPolicyItem.Option.FreshAt)
- fresh_at_00am_lastDayOfMonth = TSC.DataFreshnessPolicyItem.FreshAt(
- TSC.DataFreshnessPolicyItem.FreshAt.Frequency.Month, "00:00:00", "America/Los_Angeles", ["LastDay"]
- )
- single_workbook.data_freshness_policy.fresh_at_schedule = fresh_at_00am_lastDayOfMonth
- single_workbook = server.workbooks.update(single_workbook)
-
- assert "1f951daf-4061-451a-9df1-69a8062664f2" == single_workbook.id
- assert "FreshAt" == single_workbook.data_freshness_policy.option
- assert "Month" == single_workbook.data_freshness_policy.fresh_at_schedule.frequency
- assert "00:00:00" == single_workbook.data_freshness_policy.fresh_at_schedule.time
- assert "LastDay" == single_workbook.data_freshness_policy.fresh_at_schedule.interval_item[0]
-
-
-def test_update_DFP_fresh_at_missing_params(server) -> None:
- response_xml = UPDATE_DFP_FRESH_AT_DAILY_XML.read_text()
- with requests_mock.mock() as m:
- m.put(server.workbooks.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2", text=response_xml)
- single_workbook = TSC.WorkbookItem("1d0304cd-3796-429f-b815-7258370b9b74", show_tabs=True)
- single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
- single_workbook.data_freshness_policy = TSC.DataFreshnessPolicyItem(TSC.DataFreshnessPolicyItem.Option.FreshAt)
-
- with pytest.raises(ValueError):
- server.workbooks.update(single_workbook)
-
-
-def test_update_DFP_fresh_at_missing_interval(server) -> None:
- response_xml = UPDATE_DFP_FRESH_AT_DAILY_XML.read_text()
- with requests_mock.mock() as m:
- m.put(server.workbooks.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2", text=response_xml)
- single_workbook = TSC.WorkbookItem("1d0304cd-3796-429f-b815-7258370b9b74", show_tabs=True)
- single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
- single_workbook.data_freshness_policy = TSC.DataFreshnessPolicyItem(TSC.DataFreshnessPolicyItem.Option.FreshAt)
- fresh_at_month_no_interval = TSC.DataFreshnessPolicyItem.FreshAt(
- TSC.DataFreshnessPolicyItem.FreshAt.Frequency.Month, "00:00:00", "America/Los_Angeles"
- )
- single_workbook.data_freshness_policy.fresh_at_schedule = fresh_at_month_no_interval
-
- with pytest.raises(ValueError):
- server.workbooks.update(single_workbook)
diff --git a/test/test_dataalert.py b/test/test_dataalert.py
deleted file mode 100644
index 879f5ed00..000000000
--- a/test/test_dataalert.py
+++ /dev/null
@@ -1,120 +0,0 @@
-from pathlib import Path
-
-import pytest
-import requests_mock
-
-import tableauserverclient as TSC
-
-TEST_ASSET_DIR = Path(__file__).parent / "assets"
-
-GET_XML = TEST_ASSET_DIR / "data_alerts_get.xml"
-GET_BY_ID_XML = TEST_ASSET_DIR / "data_alerts_get_by_id.xml"
-ADD_USER_TO_ALERT = TEST_ASSET_DIR / "data_alerts_add_user.xml"
-UPDATE_XML = TEST_ASSET_DIR / "data_alerts_update.xml"
-
-
-@pytest.fixture(scope="function")
-def server():
- server = TSC.Server("http://test", False)
-
- # Fake signin
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
- server.version = "3.2"
-
- return server
-
-
-def test_get(server) -> None:
- response_xml = GET_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.data_alerts.baseurl, text=response_xml)
- all_alerts, pagination_item = server.data_alerts.get()
-
- assert 1 == pagination_item.total_available
- assert "5ea59b45-e497-5673-8809-bfe213236f75" == all_alerts[0].id
- assert "Data Alert test" == all_alerts[0].subject
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == all_alerts[0].creatorId
- assert "2020-08-10T23:17:06Z" == all_alerts[0].createdAt
- assert "2020-08-10T23:17:06Z" == all_alerts[0].updatedAt
- assert "Daily" == all_alerts[0].frequency
- assert "true" == all_alerts[0].public
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == all_alerts[0].owner_id
- assert "Bob" == all_alerts[0].owner_name
- assert "d79634e1-6063-4ec9-95ff-50acbf609ff5" == all_alerts[0].view_id
- assert "ENDANGERED SAFARI" == all_alerts[0].view_name
- assert "6d13b0ca-043d-4d42-8c9d-3f3313ea3a00" == all_alerts[0].workbook_id
- assert "Safari stats" == all_alerts[0].workbook_name
- assert "5241e88d-d384-4fd7-9c2f-648b5247efc5" == all_alerts[0].project_id
- assert "Default" == all_alerts[0].project_name
-
-
-def test_get_by_id(server) -> None:
- response_xml = GET_BY_ID_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.data_alerts.baseurl + "/5ea59b45-e497-5673-8809-bfe213236f75", text=response_xml)
- alert = server.data_alerts.get_by_id("5ea59b45-e497-5673-8809-bfe213236f75")
-
- assert isinstance(alert.recipients, list)
- assert len(alert.recipients) == 1
- assert alert.recipients[0] == "dd2239f6-ddf1-4107-981a-4cf94e415794"
-
-
-def test_update(server) -> None:
- response_xml = UPDATE_XML.read_text()
- with requests_mock.mock() as m:
- m.put(server.data_alerts.baseurl + "/5ea59b45-e497-5673-8809-bfe213236f75", text=response_xml)
- single_alert = TSC.DataAlertItem()
- single_alert._id = "5ea59b45-e497-5673-8809-bfe213236f75"
- single_alert._subject = "Data Alert test"
- single_alert._frequency = "Daily"
- single_alert._public = True
- single_alert._owner_id = "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7"
- single_alert = server.data_alerts.update(single_alert)
-
- assert "5ea59b45-e497-5673-8809-bfe213236f75" == single_alert.id
- assert "Data Alert test" == single_alert.subject
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == single_alert.creatorId
- assert "2020-08-10T23:17:06Z" == single_alert.createdAt
- assert "2020-08-10T23:17:06Z" == single_alert.updatedAt
- assert "Daily" == single_alert.frequency
- assert "true" == single_alert.public
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == single_alert.owner_id
- assert "Bob" == single_alert.owner_name
- assert "d79634e1-6063-4ec9-95ff-50acbf609ff5" == single_alert.view_id
- assert "ENDANGERED SAFARI" == single_alert.view_name
- assert "6d13b0ca-043d-4d42-8c9d-3f3313ea3a00" == single_alert.workbook_id
- assert "Safari stats" == single_alert.workbook_name
- assert "5241e88d-d384-4fd7-9c2f-648b5247efc5" == single_alert.project_id
- assert "Default" == single_alert.project_name
-
-
-def test_add_user_to_alert(server) -> None:
- response_xml = ADD_USER_TO_ALERT.read_text()
- single_alert = TSC.DataAlertItem()
- single_alert._id = "0448d2ed-590d-4fa0-b272-a2a8a24555b5"
- in_user = TSC.UserItem("Bob", TSC.UserItem.Roles.Explorer)
- in_user._id = "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7"
-
- with requests_mock.mock() as m:
- m.post(server.data_alerts.baseurl + "/0448d2ed-590d-4fa0-b272-a2a8a24555b5/users", text=response_xml)
-
- out_user = server.data_alerts.add_user_to_alert(single_alert, in_user)
-
- assert out_user.id == in_user.id
- assert out_user.name == in_user.name
- assert out_user.site_role == in_user.site_role
-
-
-def test_delete(server) -> None:
- with requests_mock.mock() as m:
- m.delete(server.data_alerts.baseurl + "/0448d2ed-590d-4fa0-b272-a2a8a24555b5", status_code=204)
- server.data_alerts.delete("0448d2ed-590d-4fa0-b272-a2a8a24555b5")
-
-
-def test_delete_user_from_alert(server) -> None:
- alert_id = "5ea59b45-e497-5673-8809-bfe213236f75"
- user_id = "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7"
- with requests_mock.mock() as m:
- m.delete(server.data_alerts.baseurl + f"/{alert_id}/users/{user_id}", status_code=204)
- server.data_alerts.delete_user_from_alert(alert_id, user_id)
diff --git a/test/test_database.py b/test/test_database.py
deleted file mode 100644
index 951eebe00..000000000
--- a/test/test_database.py
+++ /dev/null
@@ -1,112 +0,0 @@
-from pathlib import Path
-
-import pytest
-import requests_mock
-
-import tableauserverclient as TSC
-
-TEST_ASSET_DIR = Path(__file__).parent / "assets"
-
-GET_XML = TEST_ASSET_DIR / "database_get.xml"
-POPULATE_PERMISSIONS_XML = TEST_ASSET_DIR / "database_populate_permissions.xml"
-UPDATE_XML = TEST_ASSET_DIR / "database_update.xml"
-GET_DQW_BY_CONTENT = TEST_ASSET_DIR / "dqw_by_content_type.xml"
-
-
-@pytest.fixture(scope="function")
-def server() -> TSC.Server:
- server = TSC.Server("http://test", False)
- # Fake signin
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
- server.version = "3.5"
-
- return server
-
-
-def test_get(server):
- response_xml = GET_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.databases.baseurl, text=response_xml)
- all_databases, pagination_item = server.databases.get()
-
- assert 5 == pagination_item.total_available
- assert "5ea59b45-e497-4827-8809-bfe213236f75" == all_databases[0].id
- assert "hyper" == all_databases[0].connection_type
- assert "hyper_0.hyper" == all_databases[0].name
-
- assert "23591f2c-4802-4d6a-9e28-574a8ea9bc4c" == all_databases[1].id
- assert "sqlserver" == all_databases[1].connection_type
- assert "testv1" == all_databases[1].name
- assert "9324cf6b-ba72-4b8e-b895-ac3f28d2f0e0" == all_databases[1].contact_id
- assert all_databases[1].certified
-
-
-def test_update(server):
- response_xml = UPDATE_XML.read_text()
- with requests_mock.mock() as m:
- m.put(server.databases.baseurl + "/23591f2c-4802-4d6a-9e28-574a8ea9bc4c", text=response_xml)
- single_database = TSC.DatabaseItem("test")
- single_database.contact_id = "9324cf6b-ba72-4b8e-b895-ac3f28d2f0e0"
- single_database._id = "23591f2c-4802-4d6a-9e28-574a8ea9bc4c"
- single_database.certified = True
- single_database.certification_note = "Test"
- single_database = server.databases.update(single_database)
-
- assert "23591f2c-4802-4d6a-9e28-574a8ea9bc4c" == single_database.id
- assert "9324cf6b-ba72-4b8e-b895-ac3f28d2f0e0" == single_database.contact_id
- assert single_database.certified
- assert "Test" == single_database.certification_note
-
-
-def test_populate_permissions(server):
- response_xml = POPULATE_PERMISSIONS_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.databases.baseurl + "/0448d2ed-590d-4fa0-b272-a2a8a24555b5/permissions", text=response_xml)
- single_database = TSC.DatabaseItem("test")
- single_database._id = "0448d2ed-590d-4fa0-b272-a2a8a24555b5"
-
- server.databases.populate_permissions(single_database)
- permissions = single_database.permissions
-
- assert permissions[0].grantee.tag_name == "group"
- assert permissions[0].grantee.id == "5e5e1978-71fa-11e4-87dd-7382f5c437af"
- assert permissions[0].capabilities == {
- TSC.Permission.Capability.ChangePermissions: TSC.Permission.Mode.Deny,
- TSC.Permission.Capability.Read: TSC.Permission.Mode.Allow,
- }
-
- assert permissions[1].grantee.tag_name == "user"
- assert permissions[1].grantee.id == "7c37ee24-c4b1-42b6-a154-eaeab7ee330a"
- assert permissions[1].capabilities == {
- TSC.Permission.Capability.Write: TSC.Permission.Mode.Allow,
- }
-
-
-def test_populate_data_quality_warning(server):
- response_xml = GET_DQW_BY_CONTENT.read_text()
- with requests_mock.mock() as m:
- m.get(
- server.databases._data_quality_warnings.baseurl + "/94441d26-9a52-4a42-b0fb-3f94792d1aac",
- text=response_xml,
- )
- single_database = TSC.DatabaseItem("test")
- single_database._id = "94441d26-9a52-4a42-b0fb-3f94792d1aac"
-
- server.databases.populate_dqw(single_database)
- dqws = single_database.dqws
- first_dqw = dqws.pop()
- assert first_dqw.id == "c2e0e406-84fb-4f4e-9998-f20dd9306710"
- assert first_dqw.warning_type == "WARNING"
- assert first_dqw.message == "Hello, World!"
- assert first_dqw.owner_id == "eddc8c5f-6af0-40be-b6b0-2c790290a43f"
- assert first_dqw.active
- assert first_dqw.severe
- assert str(first_dqw.created_at) == "2021-04-09 18:39:54+00:00"
- assert str(first_dqw.updated_at) == "2021-04-09 18:39:54+00:00"
-
-
-def test_delete(server):
- with requests_mock.mock() as m:
- m.delete(server.databases.baseurl + "/0448d2ed-590d-4fa0-b272-a2a8a24555b5", status_code=204)
- server.databases.delete("0448d2ed-590d-4fa0-b272-a2a8a24555b5")
diff --git a/test/test_datasource.py b/test/test_datasource.py
deleted file mode 100644
index c4bca0a7b..000000000
--- a/test/test_datasource.py
+++ /dev/null
@@ -1,991 +0,0 @@
-from io import BytesIO
-import os
-from pathlib import Path
-import tempfile
-import unittest
-from zipfile import ZipFile
-
-from defusedxml.ElementTree import fromstring
-import pytest
-import requests_mock
-
-import tableauserverclient as TSC
-from tableauserverclient import ConnectionItem
-from tableauserverclient.datetime_helpers import format_datetime, parse_datetime
-from tableauserverclient.server.endpoint.exceptions import InternalServerError
-from tableauserverclient.server.endpoint.fileuploads_endpoint import Fileuploads
-from tableauserverclient.server.request_factory import RequestFactory
-
-TEST_ASSET_DIR = Path(__file__).parent / "assets"
-
-ADD_TAGS_XML = TEST_ASSET_DIR / "datasource_add_tags.xml"
-GET_XML = TEST_ASSET_DIR / "datasource_get.xml"
-GET_EMPTY_XML = TEST_ASSET_DIR / "datasource_get_empty.xml"
-GET_BY_ID_XML = TEST_ASSET_DIR / "datasource_get_by_id.xml"
-GET_XML_ALL_FIELDS = TEST_ASSET_DIR / "datasource_get_all_fields.xml"
-GET_NO_OWNER = TEST_ASSET_DIR / "datasource_get_no_owner.xml"
-POPULATE_CONNECTIONS_XML = TEST_ASSET_DIR / "datasource_populate_connections.xml"
-POPULATE_PERMISSIONS_XML = TEST_ASSET_DIR / "datasource_populate_permissions.xml"
-PUBLISH_XML = TEST_ASSET_DIR / "datasource_publish.xml"
-PUBLISH_XML_ASYNC = TEST_ASSET_DIR / "datasource_publish_async.xml"
-REFRESH_XML = TEST_ASSET_DIR / "datasource_refresh.xml"
-REVISION_XML = TEST_ASSET_DIR / "datasource_revision.xml"
-UPDATE_XML = TEST_ASSET_DIR / "datasource_update.xml"
-UPDATE_HYPER_DATA_XML = TEST_ASSET_DIR / "datasource_data_update.xml"
-UPDATE_CONNECTION_XML = TEST_ASSET_DIR / "datasource_connection_update.xml"
-UPDATE_CONNECTIONS_XML = TEST_ASSET_DIR / "datasource_connections_update.xml"
-UPDATE_CONNECTIONS_NO_AUTH_XML = TEST_ASSET_DIR / "datasource_connections_update_no_auth.xml"
-REFRESH_DUPLICATE_XML = TEST_ASSET_DIR / "datasource_refresh_duplicate.xml"
-
-
-@pytest.fixture(scope="function")
-def server():
- """Fixture to create a TSC.Server instance for testing."""
- server = TSC.Server("http://test", False)
-
- # Fake signin
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
-
- return server
-
-
-def test_get(server) -> None:
- response_xml = GET_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.datasources.baseurl, text=response_xml)
- all_datasources, pagination_item = server.datasources.get()
-
- assert 2 == pagination_item.total_available
- assert "e76a1461-3b1d-4588-bf1b-17551a879ad9" == all_datasources[0].id
- assert "dataengine" == all_datasources[0].datasource_type
- assert "SampleDsDescription" == all_datasources[0].description
- assert "SampleDS" == all_datasources[0].content_url
- assert 4096 == all_datasources[0].size
- assert "2016-08-11T21:22:40Z" == format_datetime(all_datasources[0].created_at)
- assert "2016-08-11T21:34:17Z" == format_datetime(all_datasources[0].updated_at)
- assert "default" == all_datasources[0].project_name
- assert "SampleDS" == all_datasources[0].name
- assert "ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" == all_datasources[0].project_id
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == all_datasources[0].owner_id
- assert "https://web.com" == all_datasources[0].webpage_url
- assert not all_datasources[0].encrypt_extracts
- assert all_datasources[0].has_extracts
- assert not all_datasources[0].use_remote_query_agent
-
- assert "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb" == all_datasources[1].id
- assert "dataengine" == all_datasources[1].datasource_type
- assert "description Sample" == all_datasources[1].description
- assert "Sampledatasource" == all_datasources[1].content_url
- assert 10240 == all_datasources[1].size
- assert "2016-08-04T21:31:55Z" == format_datetime(all_datasources[1].created_at)
- assert "2016-08-04T21:31:55Z" == format_datetime(all_datasources[1].updated_at)
- assert "default" == all_datasources[1].project_name
- assert "Sample datasource" == all_datasources[1].name
- assert "ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" == all_datasources[1].project_id
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == all_datasources[1].owner_id
- assert {"world", "indicators", "sample"} == all_datasources[1].tags
- assert "https://page.com" == all_datasources[1].webpage_url
- assert all_datasources[1].encrypt_extracts
- assert not all_datasources[1].has_extracts
- assert all_datasources[1].use_remote_query_agent
-
-
-def test_get_before_signin(server) -> None:
- server._auth_token = None
- with pytest.raises(TSC.NotSignedInError):
- server.datasources.get()
-
-
-def test_get_empty(server) -> None:
- response_xml = GET_EMPTY_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.datasources.baseurl, text=response_xml)
- all_datasources, pagination_item = server.datasources.get()
-
- assert 0 == pagination_item.total_available
- assert [] == all_datasources
-
-
-def test_get_by_id(server) -> None:
- response_xml = GET_BY_ID_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.datasources.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb", text=response_xml)
- single_datasource = server.datasources.get_by_id("9dbd2263-16b5-46e1-9c43-a76bb8ab65fb")
-
- assert "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb" == single_datasource.id
- assert "dataengine" == single_datasource.datasource_type
- assert "abc description xyz" == single_datasource.description
- assert "Sampledatasource" == single_datasource.content_url
- assert "2016-08-04T21:31:55Z" == format_datetime(single_datasource.created_at)
- assert "2016-08-04T21:31:55Z" == format_datetime(single_datasource.updated_at)
- assert "default" == single_datasource.project_name
- assert "Sample datasource" == single_datasource.name
- assert "ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" == single_datasource.project_id
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == single_datasource.owner_id
- assert {"world", "indicators", "sample"} == single_datasource.tags
- assert TSC.DatasourceItem.AskDataEnablement.SiteDefault == single_datasource.ask_data_enablement
-
-
-def test_update(server) -> None:
- response_xml = UPDATE_XML.read_text()
- with requests_mock.mock() as m:
- m.put(server.datasources.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb", text=response_xml)
- single_datasource = TSC.DatasourceItem("1d0304cd-3796-429f-b815-7258370b9b74", "Sample datasource")
- single_datasource.owner_id = "dd2239f6-ddf1-4107-981a-4cf94e415794"
- single_datasource._content_url = "Sampledatasource"
- single_datasource._id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb"
- single_datasource.certified = True
- single_datasource.certification_note = "Warning, here be dragons."
- updated_datasource = server.datasources.update(single_datasource)
-
- assert updated_datasource.id == single_datasource.id
- assert updated_datasource.name == single_datasource.name
- assert updated_datasource.content_url == single_datasource.content_url
- assert updated_datasource.project_id == single_datasource.project_id
- assert updated_datasource.owner_id == single_datasource.owner_id
- assert updated_datasource.certified == single_datasource.certified
- assert updated_datasource.certification_note == single_datasource.certification_note
-
-
-def test_update_copy_fields(server) -> None:
- response_xml = UPDATE_XML.read_text()
- with requests_mock.mock() as m:
- m.put(server.datasources.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb", text=response_xml)
- single_datasource = TSC.DatasourceItem("1d0304cd-3796-429f-b815-7258370b9b74", "test")
- single_datasource._id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb"
- single_datasource._project_name = "Tester"
- updated_datasource = server.datasources.update(single_datasource)
-
- assert single_datasource.tags == updated_datasource.tags
- assert single_datasource._project_name == updated_datasource._project_name
-
-
-def test_update_tags(server) -> None:
- add_tags_xml = ADD_TAGS_XML.read_text()
- update_xml = UPDATE_XML.read_text()
- with requests_mock.mock() as m:
- m.delete(server.datasources.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/tags/b", status_code=204)
- m.delete(server.datasources.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/tags/d", status_code=204)
- m.put(server.datasources.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/tags", text=add_tags_xml)
- m.put(server.datasources.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb", text=update_xml)
- single_datasource = TSC.DatasourceItem("1d0304cd-3796-429f-b815-7258370b9b74")
- single_datasource._id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb"
- single_datasource._initial_tags.update(["a", "b", "c", "d"])
- single_datasource.tags.update(["a", "c", "e"])
- updated_datasource = server.datasources.update(single_datasource)
-
- assert single_datasource.tags == updated_datasource.tags
- assert single_datasource._initial_tags == updated_datasource._initial_tags
-
-
-def test_populate_connections(server) -> None:
- response_xml = POPULATE_CONNECTIONS_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.datasources.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/connections", text=response_xml)
- single_datasource = TSC.DatasourceItem("1d0304cd-3796-429f-b815-7258370b9b74", "test")
- single_datasource.owner_id = "dd2239f6-ddf1-4107-981a-4cf94e415794"
- single_datasource._id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb"
- server.datasources.populate_connections(single_datasource)
- assert "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb" == single_datasource.id
- connections: list[ConnectionItem] | None = single_datasource.connections
-
- assert connections is not None
- ds1, ds2 = connections
- assert "be786ae0-d2bf-4a4b-9b34-e2de8d2d4488" == ds1.id
- assert "textscan" == ds1.connection_type
- assert "forty-two.net" == ds1.server_address
- assert "duo" == ds1.username
- assert True == ds1.embed_password
- assert ds1.datasource_id == single_datasource.id
- assert single_datasource.name == ds1.datasource_name
- assert "970e24bc-e200-4841-a3e9-66e7d122d77e" == ds2.id
- assert "sqlserver" == ds2.connection_type
- assert "database.com" == ds2.server_address
- assert "heero" == ds2.username
- assert False == ds2.embed_password
- assert ds2.datasource_id == single_datasource.id
- assert single_datasource.name == ds2.datasource_name
-
-
-def test_update_connection(server) -> None:
- populate_xml = POPULATE_CONNECTIONS_XML.read_text()
- response_xml = UPDATE_CONNECTION_XML.read_text()
-
- with requests_mock.mock() as m:
- m.get(server.datasources.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/connections", text=populate_xml)
- m.put(
- server.datasources.baseurl
- + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/connections/be786ae0-d2bf-4a4b-9b34-e2de8d2d4488",
- text=response_xml,
- )
- single_datasource = TSC.DatasourceItem("be786ae0-d2bf-4a4b-9b34-e2de8d2d4488")
- single_datasource.owner_id = "dd2239f6-ddf1-4107-981a-4cf94e415794"
- single_datasource._id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb"
- server.datasources.populate_connections(single_datasource)
-
- connection = single_datasource.connections[0] # type: ignore[index]
- connection.server_address = "bar"
- connection.server_port = "9876"
- connection.username = "foo"
- new_connection = server.datasources.update_connection(single_datasource, connection)
- assert connection.id == new_connection.id
- assert connection.connection_type == new_connection.connection_type
- assert "bar" == new_connection.server_address
- assert "9876" == new_connection.server_port
- assert "foo" == new_connection.username
-
-
-def test_update_connections(server) -> None:
- populate_xml = POPULATE_CONNECTIONS_XML.read_text()
- response_xml = UPDATE_CONNECTIONS_XML.read_text()
-
- with requests_mock.Mocker() as m:
-
- datasource_id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb"
- connection_luids = ["be786ae0-d2bf-4a4b-9b34-e2de8d2d4488", "a1b2c3d4-e5f6-7a8b-9c0d-123456789abc"]
-
- datasource = TSC.DatasourceItem(datasource_id)
- datasource._id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb"
- datasource.owner_id = "dd2239f6-ddf1-4107-981a-4cf94e415794"
- server.version = "3.26"
-
- url = f"{server.baseurl}/{datasource.id}/connections"
- m.get(
- "http://test/api/3.26/sites/dad65087-b08b-4603-af4e-2887b8aafc67/datasources/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/connections",
- text=populate_xml,
- )
- m.put(
- "http://test/api/3.26/sites/dad65087-b08b-4603-af4e-2887b8aafc67/datasources/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/connections",
- text=response_xml,
- )
-
- print("BASEURL:", server.baseurl)
- print("Calling PUT on:", f"{server.baseurl}/{datasource.id}/connections")
-
- connection_items = server.datasources.update_connections(
- datasource_item=datasource,
- connection_luids=connection_luids,
- authentication_type="auth-keypair",
- username="testuser",
- password="testpass",
- embed_password=True,
- )
- updated_ids = [conn.id for conn in connection_items]
-
- assert updated_ids == connection_luids
- assert "auth-keypair" == connection_items[0].auth_type
-
-
-def test_update_connections_without_auth_type(server) -> None:
- """Test that update_connections works when authentication_type is not provided."""
- populate_xml = POPULATE_CONNECTIONS_XML.read_text()
- response_xml = UPDATE_CONNECTIONS_NO_AUTH_XML.read_text()
-
- with requests_mock.Mocker() as m:
-
- datasource_id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb"
- connection_luids = ["be786ae0-d2bf-4a4b-9b34-e2de8d2d4488", "a1b2c3d4-e5f6-7a8b-9c0d-123456789abc"]
-
- datasource = TSC.DatasourceItem(datasource_id)
- datasource._id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb"
- datasource.owner_id = "dd2239f6-ddf1-4107-981a-4cf94e415794"
- server.version = "3.26"
-
- m.get(
- "http://test/api/3.26/sites/dad65087-b08b-4603-af4e-2887b8aafc67/datasources/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/connections",
- text=populate_xml,
- )
- m.put(
- "http://test/api/3.26/sites/dad65087-b08b-4603-af4e-2887b8aafc67/datasources/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/connections",
- text=response_xml,
- )
-
- # Update connections without specifying authentication_type
- connection_items = server.datasources.update_connections(
- datasource_item=datasource,
- connection_luids=connection_luids,
- username="user1",
- embed_password=True,
- )
- updated_ids = [conn.id for conn in connection_items]
-
- assert updated_ids == connection_luids
- # Verify that the auth type from the response is preserved (UsernamePassword)
- assert connection_items[0].auth_type == "UsernamePassword"
-
-
-def test_populate_permissions(server) -> None:
- response_xml = POPULATE_PERMISSIONS_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.datasources.baseurl + "/0448d2ed-590d-4fa0-b272-a2a8a24555b5/permissions", text=response_xml)
- single_datasource = TSC.DatasourceItem("1d0304cd-3796-429f-b815-7258370b9b74", "test")
- single_datasource._id = "0448d2ed-590d-4fa0-b272-a2a8a24555b5"
-
- server.datasources.populate_permissions(single_datasource)
- permissions = single_datasource.permissions
-
- assert permissions is not None
- assert permissions[0].grantee.tag_name == "group"
- assert permissions[0].grantee.id == "5e5e1978-71fa-11e4-87dd-7382f5c437af"
- assert permissions[0].capabilities == { # type: ignore[index]
- TSC.Permission.Capability.Delete: TSC.Permission.Mode.Deny,
- TSC.Permission.Capability.ChangePermissions: TSC.Permission.Mode.Deny,
- TSC.Permission.Capability.Connect: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.Read: TSC.Permission.Mode.Allow,
- }
-
- assert permissions[1].grantee.tag_name == "user"
- assert permissions[1].grantee.id == "7c37ee24-c4b1-42b6-a154-eaeab7ee330a"
- assert permissions[1].capabilities == { # type: ignore[index]
- TSC.Permission.Capability.Write: TSC.Permission.Mode.Allow,
- }
-
-
-def test_publish(server) -> None:
- response_xml = PUBLISH_XML.read_text()
- with requests_mock.mock() as m:
- m.post(server.datasources.baseurl, text=response_xml)
- new_datasource = TSC.DatasourceItem("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", "SampleDS")
- publish_mode = server.PublishMode.CreateNew
-
- new_datasource = server.datasources.publish(new_datasource, TEST_ASSET_DIR / "SampleDS.tds", mode=publish_mode)
-
- assert "e76a1461-3b1d-4588-bf1b-17551a879ad9" == new_datasource.id
- assert "SampleDS" == new_datasource.name
- assert "SampleDS" == new_datasource.content_url
- assert "dataengine" == new_datasource.datasource_type
- assert "2016-08-11T21:22:40Z" == format_datetime(new_datasource.created_at)
- assert "2016-08-17T23:37:08Z" == format_datetime(new_datasource.updated_at)
- assert "ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" == new_datasource.project_id
- assert "default" == new_datasource.project_name
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == new_datasource.owner_id
-
-
-def test_publish_a_non_packaged_file_object(server) -> None:
- response_xml = PUBLISH_XML.read_text()
- with requests_mock.mock() as m:
- m.post(server.datasources.baseurl, text=response_xml)
- new_datasource = TSC.DatasourceItem("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", "SampleDS")
- publish_mode = server.PublishMode.CreateNew
-
- with open(TEST_ASSET_DIR / "SampleDS.tds", "rb") as file_object:
- new_datasource = server.datasources.publish(new_datasource, file_object, mode=publish_mode)
-
- assert "e76a1461-3b1d-4588-bf1b-17551a879ad9" == new_datasource.id
- assert "SampleDS" == new_datasource.name
- assert "SampleDS" == new_datasource.content_url
- assert "dataengine" == new_datasource.datasource_type
- assert "2016-08-11T21:22:40Z" == format_datetime(new_datasource.created_at)
- assert "2016-08-17T23:37:08Z" == format_datetime(new_datasource.updated_at)
- assert "ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" == new_datasource.project_id
- assert "default" == new_datasource.project_name
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == new_datasource.owner_id
-
-
-def test_publish_a_packaged_file_object(server) -> None:
- response_xml = PUBLISH_XML.read_text()
- with requests_mock.mock() as m:
- m.post(server.datasources.baseurl, text=response_xml)
- new_datasource = TSC.DatasourceItem("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", "SampleDS")
- publish_mode = server.PublishMode.CreateNew
-
- # Create a dummy tdsx file in memory
- with BytesIO() as zip_archive:
- with ZipFile(zip_archive, "w") as zf:
- zf.write(str(TEST_ASSET_DIR / "SampleDS.tds"), arcname="SampleDS.tds")
-
- zip_archive.seek(0)
-
- new_datasource = server.datasources.publish(new_datasource, zip_archive, mode=publish_mode)
-
- assert "e76a1461-3b1d-4588-bf1b-17551a879ad9" == new_datasource.id
- assert "SampleDS" == new_datasource.name
- assert "SampleDS" == new_datasource.content_url
- assert "dataengine" == new_datasource.datasource_type
- assert "2016-08-11T21:22:40Z" == format_datetime(new_datasource.created_at)
- assert "2016-08-17T23:37:08Z" == format_datetime(new_datasource.updated_at)
- assert "ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" == new_datasource.project_id
- assert "default" == new_datasource.project_name
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == new_datasource.owner_id
-
-
-def test_publish_async(server) -> None:
- server.version = "3.0"
- baseurl = server.datasources.baseurl
- response_xml = PUBLISH_XML_ASYNC.read_text()
- with requests_mock.mock() as m:
- m.post(baseurl, text=response_xml)
- new_datasource = TSC.DatasourceItem("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", "SampleDS")
- publish_mode = server.PublishMode.CreateNew
-
- new_job = server.datasources.publish(
- new_datasource, TEST_ASSET_DIR / "SampleDS.tds", mode=publish_mode, as_job=True
- )
-
- assert "9a373058-af5f-4f83-8662-98b3e0228a73" == new_job.id
- assert "PublishDatasource" == new_job.type
- assert "0" == new_job.progress
- assert "2018-06-30T00:54:54Z" == format_datetime(new_job.created_at)
- assert 1 == new_job.finish_code
-
-
-def test_publish_unnamed_file_object(server) -> None:
- new_datasource = TSC.DatasourceItem("test")
- publish_mode = server.PublishMode.CreateNew
-
- with open(TEST_ASSET_DIR / "SampleDS.tds", "rb") as file_object:
- with pytest.raises(ValueError):
- server.datasources.publish(new_datasource, file_object, publish_mode)
-
-
-def test_refresh_id(server) -> None:
- server.version = "2.8"
- response_xml = REFRESH_XML.read_text()
- with requests_mock.mock() as m:
- m.post(
- server.datasources.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/refresh",
- status_code=202,
- text=response_xml,
- )
- new_job = server.datasources.refresh("9dbd2263-16b5-46e1-9c43-a76bb8ab65fb")
-
- assert "7c3d599e-949f-44c3-94a1-f30ba85757e4" == new_job.id
- assert "RefreshExtract" == new_job.type
- assert None == new_job.progress
- assert "2020-03-05T22:05:32Z" == format_datetime(new_job.created_at)
- assert -1 == new_job.finish_code
-
-
-def test_refresh_object(server) -> None:
- server.version = "2.8"
- datasource = TSC.DatasourceItem("")
- datasource._id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb"
- response_xml = REFRESH_XML.read_text()
- with requests_mock.mock() as m:
- m.post(
- server.datasources.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/refresh",
- status_code=202,
- text=response_xml,
- )
- new_job = server.datasources.refresh(datasource)
-
- # We only check the `id`; remaining fields are already tested in `test_refresh_id`
- assert "7c3d599e-949f-44c3-94a1-f30ba85757e4" == new_job.id
-
-
-def test_refresh_already_running(server) -> None:
- server.version = "2.8"
- response_xml = REFRESH_DUPLICATE_XML.read_text()
- with requests_mock.mock() as m:
- m.post(
- server.datasources.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/refresh",
- status_code=409,
- text=response_xml,
- )
- refresh_job = server.datasources.refresh("9dbd2263-16b5-46e1-9c43-a76bb8ab65fb")
- assert refresh_job is None
-
-
-def test_datasource_refresh_request_empty(server) -> None:
- server.version = "2.8"
- item = TSC.DatasourceItem("")
- item._id = "1234"
- text = REFRESH_XML.read_text()
-
- def match_request_body(request):
- try:
- root = fromstring(request.body)
- assert root.tag == "tsRequest"
- assert len(root) == 0
- return True
- except Exception:
- return False
-
- with requests_mock.mock() as m:
- m.post(f"{server.datasources.baseurl}/1234/refresh", text=text, additional_matcher=match_request_body)
-
-
-def test_update_hyper_data_datasource_object(server) -> None:
- """Calling `update_hyper_data` with a `DatasourceItem` should update that datasource"""
- server.version = "3.13"
-
- datasource = TSC.DatasourceItem("")
- datasource._id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb"
- response_xml = UPDATE_HYPER_DATA_XML.read_text()
- with requests_mock.mock() as m:
- m.patch(
- server.datasources.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/data",
- status_code=202,
- headers={"requestid": "test_id"},
- text=response_xml,
- )
- new_job = server.datasources.update_hyper_data(datasource, request_id="test_id", actions=[])
-
- assert "5c0ba560-c959-424e-b08a-f32ef0bfb737" == new_job.id
- assert "UpdateUploadedFile" == new_job.type
- assert None == new_job.progress
- assert "2021-09-18T09:40:12Z" == format_datetime(new_job.created_at)
- assert -1 == new_job.finish_code
-
-
-def test_update_hyper_data_connection_object(server) -> None:
- """Calling `update_hyper_data` with a `ConnectionItem` should update that connection"""
- server.version = "3.13"
-
- connection = TSC.ConnectionItem()
- connection._datasource_id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb"
- connection._id = "7ecaccd8-39b0-4875-a77d-094f6e930019"
- response_xml = UPDATE_HYPER_DATA_XML.read_text()
- with requests_mock.mock() as m:
- m.patch(
- server.datasources.baseurl
- + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/connections/7ecaccd8-39b0-4875-a77d-094f6e930019/data",
- status_code=202,
- headers={"requestid": "test_id"},
- text=response_xml,
- )
- new_job = server.datasources.update_hyper_data(connection, request_id="test_id", actions=[])
-
- # We only check the `id`; remaining fields are already tested in `test_update_hyper_data_datasource_object`
- assert "5c0ba560-c959-424e-b08a-f32ef0bfb737" == new_job.id
-
-
-def test_update_hyper_data_datasource_string(server) -> None:
- """For convenience, calling `update_hyper_data` with a `str` should update the datasource with the corresponding UUID"""
- server.version = "3.13"
-
- datasource_id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb"
- response_xml = UPDATE_HYPER_DATA_XML.read_text()
- with requests_mock.mock() as m:
- m.patch(
- server.datasources.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/data",
- status_code=202,
- headers={"requestid": "test_id"},
- text=response_xml,
- )
- new_job = server.datasources.update_hyper_data(datasource_id, request_id="test_id", actions=[])
-
- # We only check the `id`; remaining fields are already tested in `test_update_hyper_data_datasource_object`
- assert "5c0ba560-c959-424e-b08a-f32ef0bfb737" == new_job.id
-
-
-def test_update_hyper_data_datasource_payload_file(server) -> None:
- """If `payload` is present, we upload it and associate the job with it"""
- server.version = "3.13"
-
- datasource_id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb"
- mock_upload_id = "10051:c3e56879876842d4b3600f20c1f79876-0:0"
- response_xml = UPDATE_HYPER_DATA_XML.read_text()
- with requests_mock.mock() as rm, unittest.mock.patch.object(Fileuploads, "upload", return_value=mock_upload_id):
- rm.patch(
- server.datasources.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/data?uploadSessionId=" + mock_upload_id,
- status_code=202,
- headers={"requestid": "test_id"},
- text=response_xml,
- )
- new_job = server.datasources.update_hyper_data(
- datasource_id, request_id="test_id", actions=[], payload=(TEST_ASSET_DIR / "World Indicators.hyper")
- )
-
- # We only check the `id`; remaining fields are already tested in `test_update_hyper_data_datasource_object`
- assert "5c0ba560-c959-424e-b08a-f32ef0bfb737" == new_job.id
-
-
-def test_update_hyper_data_datasource_invalid_payload_file(server) -> None:
- """If `payload` points to a non-existing file, we report an error"""
- server.version = "3.13"
- datasource_id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb"
- with pytest.raises(IOError, match="File path does not lead to an existing file."):
- server.datasources.update_hyper_data(
- datasource_id, request_id="test_id", actions=[], payload="no/such/file.missing"
- )
-
-
-def test_delete(server) -> None:
- with requests_mock.mock() as m:
- m.delete(server.datasources.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb", status_code=204)
- server.datasources.delete("9dbd2263-16b5-46e1-9c43-a76bb8ab65fb")
-
-
-def test_download(server, tmp_path) -> None:
- with requests_mock.mock() as m:
- m.get(
- server.datasources.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/content",
- headers={"Content-Disposition": 'name="tableau_datasource"; filename="Sample datasource.tds"'},
- )
- file_path = server.datasources.download("9dbd2263-16b5-46e1-9c43-a76bb8ab65fb", filepath=tmp_path)
- assert os.path.exists(file_path)
-
-
-def test_download_object(server) -> None:
- with BytesIO() as file_object:
- with requests_mock.mock() as m:
- m.get(
- server.datasources.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/content",
- headers={"Content-Disposition": 'name="tableau_datasource"; filename="Sample datasource.tds"'},
- )
- file_path = server.datasources.download("9dbd2263-16b5-46e1-9c43-a76bb8ab65fb", filepath=file_object)
- assert isinstance(file_path, BytesIO)
-
-
-def test_download_sanitizes_name(server, tmp_path) -> None:
- filename = "Name,With,Commas.tds"
- disposition = f'name="tableau_workbook"; filename="{filename}"'
- with requests_mock.mock() as m:
- m.get(
- server.datasources.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/content",
- headers={"Content-Disposition": disposition},
- )
- file_path = server.datasources.download("1f951daf-4061-451a-9df1-69a8062664f2", filepath=tmp_path)
- assert os.path.basename(file_path) == "NameWithCommas.tds"
- assert os.path.exists(file_path)
-
-
-def test_download_extract_only(server, tmp_path) -> None:
- # Pretend we're 2.5 for 'extract_only'
- server.version = "2.5"
-
- with requests_mock.mock() as m:
- m.get(
- server.datasources.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/content?includeExtract=False",
- headers={"Content-Disposition": 'name="tableau_datasource"; filename="Sample datasource.tds"'},
- complete_qs=True,
- )
- file_path = server.datasources.download(
- "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb", include_extract=False, filepath=tmp_path
- )
- assert os.path.exists(file_path)
-
-
-def test_download_no_extract_emits_deprecation_warning(server, tmp_path) -> None:
- """no_extract=True should emit a DeprecationWarning and map to includeExtract=False."""
- server.version = "2.5"
-
- with requests_mock.mock() as m:
- m.get(
- server.datasources.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/content?includeExtract=False",
- headers={"Content-Disposition": 'name="tableau_datasource"; filename="Sample datasource.tds"'},
- complete_qs=True,
- )
- with pytest.warns(DeprecationWarning, match="deprecated and will be removed"):
- file_path = server.datasources.download(
- "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb", no_extract=True, filepath=tmp_path
- )
- assert os.path.exists(file_path)
-
-
-def test_update_missing_id(server) -> None:
- single_datasource = TSC.DatasourceItem("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", "test")
- with pytest.raises(TSC.MissingRequiredFieldError):
- server.datasources.update(single_datasource)
-
-
-def test_publish_missing_path(server) -> None:
- new_datasource = TSC.DatasourceItem("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", "test")
- with pytest.raises(IOError):
- server.datasources.publish(new_datasource, "", server.PublishMode.CreateNew)
-
-
-def test_publish_missing_mode(server) -> None:
- new_datasource = TSC.DatasourceItem("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", "test")
- with pytest.raises(ValueError):
- server.datasources.publish(new_datasource, TEST_ASSET_DIR / "SampleDS.tds", None)
-
-
-def test_publish_invalid_file_type(server) -> None:
- new_datasource = TSC.DatasourceItem("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", "test")
- with pytest.raises(ValueError):
- server.datasources.publish(
- new_datasource,
- TEST_ASSET_DIR / "SampleWB.twbx",
- server.PublishMode.Append,
- )
-
-
-def test_publish_hyper_file_object_raises_exception(server) -> None:
- new_datasource = TSC.DatasourceItem("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", "test")
- with open(TEST_ASSET_DIR / "World Indicators.hyper", "rb") as file_object:
- with pytest.raises(ValueError):
- server.datasources.publish(new_datasource, file_object, server.PublishMode.Append)
-
-
-def test_publish_tde_file_object_raises_exception(server) -> None:
- new_datasource = TSC.DatasourceItem("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", "test")
- tds_asset = TEST_ASSET_DIR / "Data" / "Tableau Samples" / "World Indicators.tde"
- with open(tds_asset, "rb") as file_object:
- with pytest.raises(ValueError):
- server.datasources.publish(new_datasource, file_object, server.PublishMode.Append)
-
-
-def test_publish_file_object_of_unknown_type_raises_exception(server) -> None:
- new_datasource = TSC.DatasourceItem("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", "test")
-
- with BytesIO() as file_object:
- file_object.write(bytes.fromhex("89504E470D0A1A0A"))
- file_object.seek(0)
- with pytest.raises(ValueError):
- server.datasources.publish(new_datasource, file_object, server.PublishMode.Append)
-
-
-def test_publish_multi_connection(server) -> None:
- new_datasource = TSC.DatasourceItem(name="Sample", project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760")
- connection1 = TSC.ConnectionItem()
- connection1.server_address = "mysql.test.com"
- connection1.connection_credentials = TSC.ConnectionCredentials("test", "secret", True)
- connection2 = TSC.ConnectionItem()
- connection2.server_address = "pgsql.test.com"
- connection2.connection_credentials = TSC.ConnectionCredentials("test", "secret", True)
-
- response = RequestFactory.Datasource._generate_xml(new_datasource, connections=[connection1, connection2])
- # Can't use ConnectionItem parser due to xml namespace problems
- connection_results = fromstring(response).findall(".//connection")
-
- assert connection_results[0].get("serverAddress", None) == "mysql.test.com"
- assert connection_results[0].find("connectionCredentials").get("name", None) == "test"
- assert connection_results[1].get("serverAddress", None) == "pgsql.test.com"
- assert connection_results[1].find("connectionCredentials").get("password", None) == "secret"
-
-
-def test_publish_single_connection(server) -> None:
- new_datasource = TSC.DatasourceItem(name="Sample", project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760")
- connection_creds = TSC.ConnectionCredentials("test", "secret", True)
-
- response = RequestFactory.Datasource._generate_xml(new_datasource, connection_credentials=connection_creds)
- # Can't use ConnectionItem parser due to xml namespace problems
- credentials = fromstring(response).findall(".//connectionCredentials")
-
- assert len(credentials) == 1
- assert credentials[0].get("name", None) == "test"
- assert credentials[0].get("password", None) == "secret"
- assert credentials[0].get("embed", None) == "true"
-
-
-def test_credentials_and_multi_connect_raises_exception(server) -> None:
- new_datasource = TSC.DatasourceItem(name="Sample", project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760")
-
- connection_creds = TSC.ConnectionCredentials("test", "secret", True)
-
- connection1 = TSC.ConnectionItem()
- connection1.server_address = "mysql.test.com"
- connection1.connection_credentials = TSC.ConnectionCredentials("test", "secret", True)
-
- with pytest.raises(RuntimeError):
- response = RequestFactory.Datasource._generate_xml(
- new_datasource, connection_credentials=connection_creds, connections=[connection1]
- )
-
-
-def test_synchronous_publish_timeout_error(server) -> None:
- with requests_mock.mock() as m:
- m.register_uri("POST", server.datasources.baseurl, status_code=504)
-
- new_datasource = TSC.DatasourceItem(project_id="")
- publish_mode = server.PublishMode.CreateNew
- # http://test/api/2.4/sites/dad65087-b08b-4603-af4e-2887b8aafc67/datasources?datasourceType=tds
-
- with pytest.raises(InternalServerError, match="Please use asynchronous publishing to avoid timeouts."):
- server.datasources.publish(
- new_datasource,
- TEST_ASSET_DIR / "SampleDS.tds",
- publish_mode,
- )
-
-
-def test_async_publish_timeout_error(server) -> None:
- with requests_mock.mock() as m:
- m.register_uri("POST", server.datasources.baseurl, status_code=504)
-
- new_datasource = TSC.DatasourceItem(project_id="")
- publish_mode = server.PublishMode.CreateNew
-
- with pytest.raises(InternalServerError, match="TSC_CHUNK_SIZE_MB"):
- server.datasources.publish(
- new_datasource,
- TEST_ASSET_DIR / "SampleDS.tds",
- publish_mode,
- as_job=True,
- )
-
-
-def test_delete_extracts(server) -> None:
- server.version = "3.10"
- with requests_mock.mock() as m:
- m.post(server.datasources.baseurl + "/3cc6cd06-89ce-4fdc-b935-5294135d6d42/deleteExtract", status_code=200)
- server.datasources.delete_extract("3cc6cd06-89ce-4fdc-b935-5294135d6d42")
-
-
-def test_create_extracts(server) -> None:
- server.version = "3.10"
-
- response_xml = PUBLISH_XML_ASYNC.read_text()
- with requests_mock.mock() as m:
- m.post(
- server.datasources.baseurl + "/3cc6cd06-89ce-4fdc-b935-5294135d6d42/createExtract",
- status_code=200,
- text=response_xml,
- )
- server.datasources.create_extract("3cc6cd06-89ce-4fdc-b935-5294135d6d42")
-
-
-def test_create_extracts_encrypted(server) -> None:
- server.version = "3.10"
-
- response_xml = PUBLISH_XML_ASYNC.read_text()
- with requests_mock.mock() as m:
- m.post(
- server.datasources.baseurl + "/3cc6cd06-89ce-4fdc-b935-5294135d6d42/createExtract",
- status_code=200,
- text=response_xml,
- )
- server.datasources.create_extract("3cc6cd06-89ce-4fdc-b935-5294135d6d42", True)
-
-
-def test_revisions(server) -> None:
- datasource = TSC.DatasourceItem("project", "test")
- datasource._id = "06b944d2-959d-4604-9305-12323c95e70e"
-
- response_xml = REVISION_XML.read_text()
- with requests_mock.mock() as m:
- m.get(f"{server.datasources.baseurl}/{datasource.id}/revisions", text=response_xml)
- server.datasources.populate_revisions(datasource)
- revisions = datasource.revisions
-
- assert len(revisions) == 3
- assert "2016-07-26T20:34:56Z" == format_datetime(revisions[0].created_at)
- assert "2016-07-27T20:34:56Z" == format_datetime(revisions[1].created_at)
- assert "2016-07-28T20:34:56Z" == format_datetime(revisions[2].created_at)
-
- assert False == revisions[0].deleted
- assert False == revisions[0].current
- assert False == revisions[1].deleted
- assert False == revisions[1].current
- assert False == revisions[2].deleted
- assert True == revisions[2].current
-
- assert "Cassie" == revisions[0].user_name
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == revisions[0].user_id
- assert revisions[1].user_name is None
- assert revisions[1].user_id is None
- assert "Cassie" == revisions[2].user_name
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == revisions[2].user_id
-
-
-def test_delete_revision(server) -> None:
- datasource = TSC.DatasourceItem("project", "test")
- datasource._id = "06b944d2-959d-4604-9305-12323c95e70e"
-
- with requests_mock.mock() as m:
- m.delete(f"{server.datasources.baseurl}/{datasource.id}/revisions/3")
- server.datasources.delete_revision(datasource.id, "3")
-
-
-def test_download_revision(server) -> None:
- with requests_mock.mock() as m, tempfile.TemporaryDirectory() as td:
- m.get(
- server.datasources.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/revisions/3/content",
- headers={"Content-Disposition": 'name="tableau_datasource"; filename="Sample datasource.tds"'},
- )
- file_path = server.datasources.download_revision("9dbd2263-16b5-46e1-9c43-a76bb8ab65fb", "3", td)
- assert os.path.exists(file_path)
-
-
-def test_bad_download_response(server) -> None:
- with requests_mock.mock() as m, tempfile.TemporaryDirectory() as td:
- m.get(
- server.datasources.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/content",
- headers={"Content-Disposition": '''name="tableau_datasource"; filename*=UTF-8''"Sample datasource.tds"'''},
- )
- file_path = server.datasources.download("9dbd2263-16b5-46e1-9c43-a76bb8ab65fb", td)
- assert os.path.exists(file_path)
-
-
-def test_get_datasource_all_fields(server) -> None:
- ro = TSC.RequestOptions()
- ro.all_fields = True
- with requests_mock.mock() as m:
- m.get(f"{server.datasources.baseurl}?fields=_all_", text=GET_XML_ALL_FIELDS.read_text())
- datasources, _ = server.datasources.get(req_options=ro)
-
- assert datasources[0].connected_workbooks_count == 0
- assert datasources[0].content_url == "SuperstoreDatasource"
- assert datasources[0].created_at == parse_datetime("2024-02-14T04:42:13Z")
- assert not datasources[0].encrypt_extracts
- assert datasources[0].favorites_total == 0
- assert not datasources[0].has_alert
- assert not datasources[0].has_extracts
- assert datasources[0].id == "a71cdd15-3a23-4ec1-b3ce-9956f5e00bb7"
- assert not datasources[0].certified
- assert datasources[0].is_published
- assert datasources[0].name == "Superstore Datasource"
- assert datasources[0].size == 1
- assert datasources[0].datasource_type == "excel-direct"
- assert datasources[0].updated_at == parse_datetime("2024-02-14T04:42:14Z")
- assert not datasources[0].use_remote_query_agent
- assert datasources[0].server_name == "localhost"
- assert datasources[0].webpage_url == "https://10ax.online.tableau.com/#/site/example/datasources/3566752"
- assert isinstance(datasources[0].project, TSC.ProjectItem)
- assert datasources[0].project.id == "669ca36b-492e-4ccf-bca1-3614fe6a9d7a"
- assert datasources[0].project.name == "Samples"
- assert datasources[0].project.description == "This project includes automatically uploaded samples."
- assert datasources[0].owner.email == "bob@example.com"
- assert isinstance(datasources[0].owner, TSC.UserItem)
- assert datasources[0].owner.fullname == "Bob Smith"
- assert datasources[0].owner.id == "ee8bc9ca-77fe-4ae0-8093-cf77f0ee67a9"
- assert datasources[0].owner.last_login == parse_datetime("2025-02-04T06:39:20Z")
- assert datasources[0].owner.name == "bob@example.com"
- assert datasources[0].owner.site_role == "SiteAdministratorCreator"
-
-
-def test_update_description(server: TSC.Server) -> None:
- response_xml = UPDATE_XML.read_text()
- with requests_mock.mock() as m:
- m.put(server.datasources.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb", text=response_xml)
- single_datasource = TSC.DatasourceItem("1d0304cd-3796-429f-b815-7258370b9b74", "Sample datasource")
- single_datasource.owner_id = "dd2239f6-ddf1-4107-981a-4cf94e415794"
- single_datasource._content_url = "Sampledatasource"
- single_datasource._id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb"
- single_datasource.certified = True
- single_datasource.certification_note = "Warning, here be dragons."
- single_datasource.description = "Sample description"
- _ = server.datasources.update(single_datasource)
-
- history = m.request_history[0]
- body = fromstring(history.body)
- ds_elem = body.find(".//datasource")
- assert ds_elem is not None
- assert ds_elem.attrib["description"] == "Sample description"
-
-
-def test_publish_description(server: TSC.Server) -> None:
- response_xml = PUBLISH_XML.read_text()
- with requests_mock.mock() as m:
- m.post(server.datasources.baseurl, text=response_xml)
- single_datasource = TSC.DatasourceItem("1d0304cd-3796-429f-b815-7258370b9b74", "Sample datasource")
- single_datasource.owner_id = "dd2239f6-ddf1-4107-981a-4cf94e415794"
- single_datasource._content_url = "Sampledatasource"
- single_datasource._id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb"
- single_datasource.certified = True
- single_datasource.certification_note = "Warning, here be dragons."
- single_datasource.description = "Sample description"
- _ = server.datasources.publish(single_datasource, TEST_ASSET_DIR / "SampleDS.tds", server.PublishMode.CreateNew)
-
- history = m.request_history[0]
- boundary = history.body[: history.body.index(b"\r\n")].strip()
- parts = history.body.split(boundary)
- request_payload = next(part for part in parts if b"request_payload" in part)
- xml_payload = request_payload.strip().split(b"\r\n")[-1]
- body = fromstring(xml_payload)
- ds_elem = body.find(".//datasource")
- assert ds_elem is not None
- assert ds_elem.attrib["description"] == "Sample description"
-
-
-def test_get_datasource_no_owner(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.get(server.datasources.baseurl, text=GET_NO_OWNER.read_text())
- datasources, _ = server.datasources.get()
-
- datasource = datasources[0]
- assert datasource.owner is None
- assert datasource.project is None
diff --git a/test/test_datasource_model.py b/test/test_datasource_model.py
deleted file mode 100644
index c74805fa6..000000000
--- a/test/test_datasource_model.py
+++ /dev/null
@@ -1,20 +0,0 @@
-import pytest
-
-import tableauserverclient as TSC
-
-
-def test_nullable_project_id():
- datasource = TSC.DatasourceItem(name="10")
- assert datasource.project_id is None
-
-
-def test_require_boolean_flag_bridge_fail():
- datasource = TSC.DatasourceItem("10")
- with pytest.raises(ValueError):
- datasource.use_remote_query_agent = "yes"
-
-
-def test_require_boolean_flag_bridge_ok():
- datasource = TSC.DatasourceItem("10")
- datasource.use_remote_query_agent = True
- assert datasource.use_remote_query_agent
diff --git a/test/test_dqw.py b/test/test_dqw.py
deleted file mode 100644
index 5cb17221a..000000000
--- a/test/test_dqw.py
+++ /dev/null
@@ -1,9 +0,0 @@
-import tableauserverclient as TSC
-
-
-def test_dqw_existence():
- dqw: TSC.DQWItem = TSC.DQWItem()
- dqw.message = "message"
- dqw.warning_type = TSC.DQWItem.WarningType.STALE
- dqw.active = True
- dqw.severe = True
diff --git a/test/test_endpoint.py b/test/test_endpoint.py
deleted file mode 100644
index e4670d1fc..000000000
--- a/test/test_endpoint.py
+++ /dev/null
@@ -1,161 +0,0 @@
-from pathlib import Path
-import pytest
-import requests
-
-import tableauserverclient as TSC
-from tableauserverclient.server.endpoint import Endpoint
-from tableauserverclient.server.endpoint.exceptions import (
- FailedSignInError,
- NonXMLResponseError,
- ServerResponseError,
-)
-
-import requests_mock
-
-ASSETS = Path(__file__).parent / "assets"
-
-
-@pytest.fixture(scope="function")
-def server():
- """Fixture to create a TSC.Server instance for testing."""
- server = TSC.Server("http://test", False)
-
- # Fake signin
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvS"
-
- return server
-
-
-def test_fallback_request_logic(server: TSC.Server) -> None:
- url = "http://test/"
- endpoint = Endpoint(server)
- with requests_mock.mock() as m:
- m.get(url)
- response = endpoint.get_request(url=url)
- assert response is not None
-
-
-def test_user_friendly_request_returns(server: TSC.Server) -> None:
- url = "http://test/"
- endpoint = Endpoint(server)
- with requests_mock.mock() as m:
- m.get(url)
- response = endpoint.send_request_while_show_progress_threaded(
- endpoint.parent_srv.session.get, url=url, request_timeout=2
- )
- assert response is not None
-
-
-def test_blocking_request_raises_request_error(server: TSC.Server) -> None:
- with pytest.raises(requests.exceptions.ConnectionError):
- url = "http://test/"
- endpoint = Endpoint(server)
- response = endpoint._blocking_request(endpoint.parent_srv.session.get, url=url)
- assert response is not None
-
-
-def test_get_request_stream(server: TSC.Server) -> None:
- url = "http://test/"
- endpoint = Endpoint(server)
- with requests_mock.mock() as m:
- m.get(url, headers={"Content-Type": "application/octet-stream"})
- response = endpoint.get_request(url, parameters={"stream": True})
-
- assert response._content_consumed is False
-
-
-def test_binary_log_truncated(server: TSC.Server) -> None:
- class FakeResponse:
- headers = {"Content-Type": "application/octet-stream"}
- content = b"\x1337" * 1000
- status_code = 200
-
- endpoint = Endpoint(server)
- server_response = FakeResponse()
- log = endpoint.log_response_safely(server_response) # type: ignore
- assert log.find("[Truncated File Contents]") > 0
-
-
-def test_set_user_agent_from_options_headers(server: TSC.Server) -> None:
- params = {"User-Agent": "1", "headers": {"User-Agent": "2"}}
- result = Endpoint.set_user_agent(params)
- # it should use the value under 'headers' if more than one is given
- print(result)
- print(result["headers"]["User-Agent"])
- assert result["headers"]["User-Agent"] == "2"
-
-
-def test_set_user_agent_from_options(server: TSC.Server) -> None:
- params = {"headers": {"User-Agent": "2"}}
- result = Endpoint.set_user_agent(params)
- assert result["headers"]["User-Agent"] == "2"
-
-
-def test_set_user_agent_when_blank(server: TSC.Server) -> None:
- params = {"headers": {}} # type: ignore
- result = Endpoint.set_user_agent(params)
- assert result["headers"]["User-Agent"].startswith("Tableau Server Client")
-
-
-# --- ServerResponseError / FailedSignInError exception parsing (issue #1083) ---
-
-NS = {"t": "http://tableau.com/api"}
-
-STANDARD_ERROR_XML = b"""
-
-
- Unauthorized Access
- Invalid credentials were provided.
-
- """
-
-NO_ERROR_ELEMENT_XML = b"""
-
- Something went wrong but with no error element
- """
-
-NOT_XML_CONTENT = b"Internal Server Error (not XML at all)"
-
-
-def test_server_response_error_standard_xml():
- """Standard XML with a t:error element parses code/summary/detail correctly."""
- err = ServerResponseError.from_response(STANDARD_ERROR_XML, NS, "http://test/")
- assert err.code == "401002"
- assert "Unauthorized" in err.summary
- assert "Invalid credentials" in err.detail
-
-
-def test_server_response_error_no_error_element_does_not_raise():
- """XML without a t:error element must not raise AttributeError (issue #1083)."""
- err = ServerResponseError.from_response(NO_ERROR_ELEMENT_XML, NS, "http://test/")
- assert err.code == ""
- # The raw XML content should appear in summary/detail as the fallback
- assert "Something went wrong" in err.summary or len(err.summary) > 0
-
-
-def test_server_response_error_not_xml_raises_parse_error():
- """Non-XML content causes fromstring to raise a ParseError (not AttributeError)."""
- import xml.etree.ElementTree as ET
-
- with pytest.raises(ET.ParseError):
- ServerResponseError.from_response(NOT_XML_CONTENT, NS, "http://test/")
-
-
-def test_failed_sign_in_error_no_error_element_does_not_raise():
- """FailedSignInError shares from_response β same None guard must apply."""
- err = FailedSignInError.from_response(NO_ERROR_ELEMENT_XML, NS, "http://test/")
- assert err.code == ""
- assert isinstance(err, FailedSignInError)
-
-
-def test_server_response_error_missing_summary_and_detail():
- """XML with t:error but missing summary/detail children falls back gracefully."""
- xml = b"""
-
-
- """
- err = ServerResponseError.from_response(xml, NS, "http://test/")
- assert err.code == "500001"
- assert err.summary == ""
- assert err.detail == ""
diff --git a/test/test_exponential_backoff.py b/test/test_exponential_backoff.py
deleted file mode 100644
index b5c37002f..000000000
--- a/test/test_exponential_backoff.py
+++ /dev/null
@@ -1,62 +0,0 @@
-import pytest
-
-from tableauserverclient.exponential_backoff import ExponentialBackoffTimer
-from ._utils import mocked_time
-
-
-def test_exponential() -> None:
- with mocked_time() as mock_time:
- exponentialBackoff = ExponentialBackoffTimer()
- # The creation of our mock shouldn't sleep
- pytest.approx(mock_time(), 0)
- # The first sleep sleeps for a rather short time, the following sleeps become longer
- exponentialBackoff.sleep()
- pytest.approx(mock_time(), 0.5)
- exponentialBackoff.sleep()
- pytest.approx(mock_time(), 1.2)
- exponentialBackoff.sleep()
- pytest.approx(mock_time(), 2.18)
- exponentialBackoff.sleep()
- pytest.approx(mock_time(), 3.552)
- exponentialBackoff.sleep()
- pytest.approx(mock_time(), 5.4728)
-
-
-def test_exponential_saturation() -> None:
- with mocked_time() as mock_time:
- exponentialBackoff = ExponentialBackoffTimer()
- for _ in range(99):
- exponentialBackoff.sleep()
- # We don't increase the sleep time above 30 seconds.
- # Otherwise, the exponential sleep time could easily
- # reach minutes or even hours between polls
- for _ in range(5):
- s = mock_time()
- exponentialBackoff.sleep()
- slept = mock_time() - s
- pytest.approx(slept, 30)
-
-
-def test_timeout() -> None:
- with mocked_time() as mock_time:
- exponentialBackoff = ExponentialBackoffTimer(timeout=4.5)
- for _ in range(4):
- exponentialBackoff.sleep()
- pytest.approx(mock_time(), 3.552)
- # Usually, the following sleep would sleep until 5.5, but due to
- # the timeout we wait less; thereby we make sure to take the timeout
- # into account as good as possible
- exponentialBackoff.sleep()
- pytest.approx(mock_time(), 4.5)
- # The next call to `sleep` will raise a TimeoutError
- with pytest.raises(TimeoutError):
- exponentialBackoff.sleep()
-
-
-def test_timeout_zero() -> None:
- with mocked_time() as mock_time:
- # The construction of the timer doesn't throw, yet
- exponentialBackoff = ExponentialBackoffTimer(timeout=0)
- # But the first `sleep` immediately throws
- with pytest.raises(TimeoutError):
- exponentialBackoff.sleep()
diff --git a/test/test_extensions.py b/test/test_extensions.py
deleted file mode 100644
index 7f6f84b93..000000000
--- a/test/test_extensions.py
+++ /dev/null
@@ -1,194 +0,0 @@
-from pathlib import Path
-
-from defusedxml.ElementTree import fromstring
-import requests_mock
-import pytest
-
-import tableauserverclient as TSC
-
-TEST_ASSET_DIR = Path(__file__).parent / "assets"
-
-GET_SERVER_EXT_SETTINGS = TEST_ASSET_DIR / "extensions_server_settings_true.xml"
-GET_SERVER_EXT_SETTINGS_FALSE = TEST_ASSET_DIR / "extensions_server_settings_false.xml"
-GET_SITE_SETTINGS = TEST_ASSET_DIR / "extensions_site_settings.xml"
-
-
-@pytest.fixture(scope="function")
-def server() -> TSC.Server:
- server = TSC.Server("http://test", False)
-
- # Fake sign in
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
- server.version = "3.21"
-
- return server
-
-
-def test_get_server_extensions_settings(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.get(server.extensions._server_baseurl, text=GET_SERVER_EXT_SETTINGS.read_text())
- ext_settings = server.extensions.get_server_settings()
-
- assert ext_settings.enabled is True
- assert ext_settings.block_list is not None
- assert set(ext_settings.block_list) == {"https://test.com", "https://example.com"}
-
-
-def test_get_server_extensions_settings_false(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.get(server.extensions._server_baseurl, text=GET_SERVER_EXT_SETTINGS_FALSE.read_text())
- ext_settings = server.extensions.get_server_settings()
-
- assert ext_settings.enabled is False
- assert ext_settings.block_list is not None
- assert len(ext_settings.block_list) == 0
-
-
-def test_update_server_extensions_settings(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.put(server.extensions._server_baseurl, text=GET_SERVER_EXT_SETTINGS_FALSE.read_text())
-
- ext_settings = TSC.ExtensionsServer()
- ext_settings.enabled = False
- ext_settings.block_list = []
-
- updated_settings = server.extensions.update_server_settings(ext_settings)
-
- assert updated_settings.enabled is False
- assert updated_settings.block_list is not None
- assert len(updated_settings.block_list) == 0
-
-
-def test_get_site_settings(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.get(server.extensions.baseurl, text=GET_SITE_SETTINGS.read_text())
- site_settings = server.extensions.get()
-
- assert isinstance(site_settings, TSC.ExtensionsSiteSettings)
- assert site_settings.enabled is True
- assert site_settings.use_default_setting is False
- assert site_settings.safe_list is not None
- assert site_settings.allow_trusted is True
- assert site_settings.include_partner_built is False
- assert site_settings.include_sandboxed is False
- assert site_settings.include_tableau_built is False
- assert len(site_settings.safe_list) == 1
- first_safe = site_settings.safe_list[0]
- assert first_safe.url == "http://localhost:9123/Dynamic.html"
- assert first_safe.full_data_allowed is True
- assert first_safe.prompt_needed is True
-
-
-def test_update_site_settings(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.put(server.extensions.baseurl, text=GET_SITE_SETTINGS.read_text())
-
- site_settings = TSC.ExtensionsSiteSettings()
- site_settings.enabled = True
- site_settings.use_default_setting = False
- safe_extension = TSC.SafeExtension(
- url="http://localhost:9123/Dynamic.html",
- full_data_allowed=True,
- prompt_needed=True,
- )
- site_settings.safe_list = [safe_extension]
-
- updated_settings = server.extensions.update(site_settings)
- history = m.request_history
-
- assert isinstance(updated_settings, TSC.ExtensionsSiteSettings)
- assert updated_settings.enabled is True
- assert updated_settings.use_default_setting is False
- assert updated_settings.safe_list is not None
- assert len(updated_settings.safe_list) == 1
- first_safe = updated_settings.safe_list[0]
- assert first_safe.url == "http://localhost:9123/Dynamic.html"
- assert first_safe.full_data_allowed is True
- assert first_safe.prompt_needed is True
-
- # Verify that the request body was as expected
- assert len(history) == 1
- xml_payload = fromstring(history[0].body)
- extensions_site_settings_elem = xml_payload.find(".//extensionsSiteSettings")
- assert extensions_site_settings_elem is not None
- enabled_elem = extensions_site_settings_elem.find("extensionsEnabled")
- assert enabled_elem is not None
- assert enabled_elem.text == "true"
- use_default_elem = extensions_site_settings_elem.find("useDefaultSetting")
- assert use_default_elem is not None
- assert use_default_elem.text == "false"
- safe_list_elements = list(extensions_site_settings_elem.findall("safeList"))
- assert len(safe_list_elements) == 1
- safe_extension_elem = safe_list_elements[0]
- url_elem = safe_extension_elem.find("url")
- assert url_elem is not None
- assert url_elem.text == "http://localhost:9123/Dynamic.html"
- full_data_allowed_elem = safe_extension_elem.find("fullDataAllowed")
- assert full_data_allowed_elem is not None
- assert full_data_allowed_elem.text == "true"
- prompt_needed_elem = safe_extension_elem.find("promptNeeded")
- assert prompt_needed_elem is not None
- assert prompt_needed_elem.text == "true"
-
-
-def test_update_safe_list_none(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.put(server.extensions.baseurl, text=GET_SITE_SETTINGS.read_text())
-
- site_settings = TSC.ExtensionsSiteSettings()
- site_settings.enabled = True
- site_settings.use_default_setting = False
-
- updated_settings = server.extensions.update(site_settings)
- history = m.request_history
-
- assert isinstance(updated_settings, TSC.ExtensionsSiteSettings)
- assert updated_settings.enabled is True
- assert updated_settings.use_default_setting is False
- assert updated_settings.safe_list is not None
- assert len(updated_settings.safe_list) == 1
- first_safe = updated_settings.safe_list[0]
- assert first_safe.url == "http://localhost:9123/Dynamic.html"
- assert first_safe.full_data_allowed is True
- assert first_safe.prompt_needed is True
-
- # Verify that the request body was as expected
- assert len(history) == 1
- xml_payload = fromstring(history[0].body)
- extensions_site_settings_elem = xml_payload.find(".//extensionsSiteSettings")
- assert extensions_site_settings_elem is not None
- safe_list_element = extensions_site_settings_elem.find("safeList")
- assert safe_list_element is None
-
-
-def test_update_safe_list_empty(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.put(server.extensions.baseurl, text=GET_SITE_SETTINGS.read_text())
-
- site_settings = TSC.ExtensionsSiteSettings()
- site_settings.enabled = True
- site_settings.use_default_setting = False
- site_settings.safe_list = []
-
- updated_settings = server.extensions.update(site_settings)
- history = m.request_history
-
- assert isinstance(updated_settings, TSC.ExtensionsSiteSettings)
- assert updated_settings.enabled is True
- assert updated_settings.use_default_setting is False
- assert updated_settings.safe_list is not None
- assert len(updated_settings.safe_list) == 1
- first_safe = updated_settings.safe_list[0]
- assert first_safe.url == "http://localhost:9123/Dynamic.html"
- assert first_safe.full_data_allowed is True
- assert first_safe.prompt_needed is True
-
- # Verify that the request body was as expected
- assert len(history) == 1
- xml_payload = fromstring(history[0].body)
- extensions_site_settings_elem = xml_payload.find(".//extensionsSiteSettings")
- assert extensions_site_settings_elem is not None
- safe_list_element = extensions_site_settings_elem.find("safeList")
- assert safe_list_element is not None
- assert len(safe_list_element) == 0
diff --git a/test/test_favorites.py b/test/test_favorites.py
deleted file mode 100644
index a7bed8d9b..000000000
--- a/test/test_favorites.py
+++ /dev/null
@@ -1,146 +0,0 @@
-from pathlib import Path
-
-import pytest
-import requests_mock
-
-import tableauserverclient as TSC
-from tableauserverclient.datetime_helpers import parse_datetime
-
-TEST_ASSET_DIR = Path(__file__).parent / "assets"
-
-GET_FAVORITES_XML = TEST_ASSET_DIR / "favorites_get.xml"
-ADD_FAVORITE_WORKBOOK_XML = TEST_ASSET_DIR / "favorites_add_workbook.xml"
-ADD_FAVORITE_VIEW_XML = TEST_ASSET_DIR / "favorites_add_view.xml"
-ADD_FAVORITE_DATASOURCE_XML = TEST_ASSET_DIR / "favorites_add_datasource.xml"
-ADD_FAVORITE_PROJECT_XML = TEST_ASSET_DIR / "favorites_add_project.xml"
-
-
-@pytest.fixture(scope="function")
-def server() -> TSC.Server:
- server = TSC.Server("http://test", False)
- # Fake signin
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
- server.version = "3.5"
-
- return server
-
-
-@pytest.fixture(scope="function")
-def user() -> TSC.UserItem:
- user = TSC.UserItem("alice", TSC.UserItem.Roles.Viewer)
- user._id = "dd2239f6-ddf1-4107-981a-4cf94e415794"
- return user
-
-
-def test_get(server: TSC.Server, user: TSC.UserItem) -> None:
- response_xml = GET_FAVORITES_XML.read_text()
- with requests_mock.mock() as m:
- m.get(f"{server.favorites.baseurl}/{user.id}", text=response_xml)
- server.favorites.get(user)
- assert user._favorites is not None
- assert len(user.favorites["workbooks"]) == 1
- assert len(user.favorites["views"]) == 1
- assert len(user.favorites["projects"]) == 1
- assert len(user.favorites["datasources"]) == 1
-
- workbook = user.favorites["workbooks"][0]
- print("favorited: ")
- print(workbook)
- view = user.favorites["views"][0]
- datasource = user.favorites["datasources"][0]
- project = user.favorites["projects"][0]
-
- assert workbook.id == "6d13b0ca-043d-4d42-8c9d-3f3313ea3a00"
- assert view.id == "d79634e1-6063-4ec9-95ff-50acbf609ff5"
- assert datasource.id == "e76a1461-3b1d-4588-bf1b-17551a879ad9"
- assert project.id == "1d0304cd-3796-429f-b815-7258370b9b74"
-
- collection = user.favorites["collections"][0]
-
- assert collection.id == "8c57cb8a-d65f-4a32-813e-5a3f86e8f94e"
- assert collection.name == "sample collection"
- assert collection.description == "description for sample collection"
- assert collection.total_item_count == 3
- assert collection.permissioned_item_count == 2
- assert collection.visibility == "Private"
- assert collection.created_at == parse_datetime("2016-08-11T21:22:40Z")
- assert collection.updated_at == parse_datetime("2016-08-11T21:34:17Z")
-
-
-def test_add_favorite_workbook(server: TSC.Server, user: TSC.UserItem) -> None:
- response_xml = ADD_FAVORITE_WORKBOOK_XML.read_text()
- workbook = TSC.WorkbookItem("")
- workbook._id = "6d13b0ca-043d-4d42-8c9d-3f3313ea3a00"
- workbook.name = "Superstore"
- with requests_mock.mock() as m:
- m.put(f"{server.favorites.baseurl}/{user.id}", text=response_xml)
- server.favorites.add_favorite_workbook(user, workbook)
-
-
-def test_add_favorite_view(server: TSC.Server, user: TSC.UserItem) -> None:
- response_xml = ADD_FAVORITE_VIEW_XML.read_text()
- view = TSC.ViewItem()
- view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5"
- view._name = "ENDANGERED SAFARI"
- with requests_mock.mock() as m:
- m.put(f"{server.favorites.baseurl}/{user.id}", text=response_xml)
- server.favorites.add_favorite_view(user, view)
-
-
-def test_add_favorite_datasource(server: TSC.Server, user: TSC.UserItem) -> None:
- response_xml = ADD_FAVORITE_DATASOURCE_XML.read_text()
- datasource = TSC.DatasourceItem("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760")
- datasource._id = "e76a1461-3b1d-4588-bf1b-17551a879ad9"
- datasource.name = "SampleDS"
- with requests_mock.mock() as m:
- m.put(f"{server.favorites.baseurl}/{user.id}", text=response_xml)
- server.favorites.add_favorite_datasource(user, datasource)
-
-
-def test_add_favorite_project(server: TSC.Server, user: TSC.UserItem) -> None:
- server.version = "3.1"
- baseurl = server.favorites.baseurl
- response_xml = ADD_FAVORITE_PROJECT_XML.read_text()
- project = TSC.ProjectItem("Tableau")
- project._id = "1d0304cd-3796-429f-b815-7258370b9b74"
- with requests_mock.mock() as m:
- m.put(f"{baseurl}/{user.id}", text=response_xml)
- server.favorites.add_favorite_project(user, project)
-
-
-def test_delete_favorite_workbook(server: TSC.Server, user: TSC.UserItem) -> None:
- workbook = TSC.WorkbookItem("")
- workbook._id = "6d13b0ca-043d-4d42-8c9d-3f3313ea3a00"
- workbook.name = "Superstore"
- with requests_mock.mock() as m:
- m.delete(f"{server.favorites.baseurl}/{user.id}/workbooks/{workbook.id}")
- server.favorites.delete_favorite_workbook(user, workbook)
-
-
-def test_delete_favorite_view(server: TSC.Server, user: TSC.UserItem) -> None:
- view = TSC.ViewItem()
- view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5"
- view._name = "ENDANGERED SAFARI"
- with requests_mock.mock() as m:
- m.delete(f"{server.favorites.baseurl}/{user.id}/views/{view.id}")
- server.favorites.delete_favorite_view(user, view)
-
-
-def test_delete_favorite_datasource(server: TSC.Server, user: TSC.UserItem) -> None:
- datasource = TSC.DatasourceItem("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760")
- datasource._id = "e76a1461-3b1d-4588-bf1b-17551a879ad9"
- datasource.name = "SampleDS"
- with requests_mock.mock() as m:
- m.delete(f"{server.favorites.baseurl}/{user.id}/datasources/{datasource.id}")
- server.favorites.delete_favorite_datasource(user, datasource)
-
-
-def test_delete_favorite_project(server: TSC.Server, user: TSC.UserItem) -> None:
- server.version = "3.1"
- baseurl = server.favorites.baseurl
- project = TSC.ProjectItem("Tableau")
- project._id = "1d0304cd-3796-429f-b815-7258370b9b74"
- with requests_mock.mock() as m:
- m.delete(f"{baseurl}/{user.id}/projects/{project.id}")
- server.favorites.delete_favorite_project(user, project)
diff --git a/test/test_filesys_helpers.py b/test/test_filesys_helpers.py
deleted file mode 100644
index aa31ae98a..000000000
--- a/test/test_filesys_helpers.py
+++ /dev/null
@@ -1,113 +0,0 @@
-import os
-from pathlib import Path
-from io import BytesIO
-from xml.etree import ElementTree as ET
-from zipfile import ZipFile
-
-import pytest
-
-from tableauserverclient.filesys_helpers import get_file_object_size, get_file_type
-
-TEST_ASSET_DIR = Path(__file__).parent / "assets"
-
-
-def test_get_file_size_returns_correct_size() -> None:
- target_size = 1000 # bytes
-
- with BytesIO() as f:
- f.seek(target_size - 1)
- f.write(b"\0")
- file_size = get_file_object_size(f)
-
- assert file_size == target_size
-
-
-def test_get_file_size_returns_zero_for_empty_file() -> None:
- with BytesIO() as f:
- file_size = get_file_object_size(f)
-
- assert file_size == 0
-
-
-def test_get_file_size_coincides_with_built_in_method() -> None:
- asset_path = TEST_ASSET_DIR / "SampleWB.twbx"
- target_size = os.path.getsize(asset_path)
- with open(asset_path, "rb") as f:
- file_size = get_file_object_size(f)
-
- assert file_size == target_size
-
-
-def test_get_file_type_identifies_a_zip_file() -> None:
- with BytesIO() as file_object:
- with ZipFile(file_object, "w") as zf:
- with BytesIO() as stream:
- stream.write(b"This is a zip file")
- zf.writestr("dummy_file", stream.getbuffer())
- file_object.seek(0)
- file_type = get_file_type(file_object)
-
- assert file_type == "zip"
-
-
-def test_get_file_type_identifies_tdsx_as_zip_file() -> None:
- with open(TEST_ASSET_DIR / "World Indicators.tdsx", "rb") as file_object:
- file_type = get_file_type(file_object)
- assert file_type == "zip"
-
-
-def test_get_file_type_identifies_twbx_as_zip_file() -> None:
- with open(TEST_ASSET_DIR / "SampleWB.twbx", "rb") as file_object:
- file_type = get_file_type(file_object)
- assert file_type == "zip"
-
-
-def test_get_file_type_identifies_xml_file() -> None:
- root = ET.Element("root")
- child = ET.SubElement(root, "child")
- child.text = "This is a child element"
- etree = ET.ElementTree(root)
-
- with BytesIO() as file_object:
- etree.write(file_object, encoding="utf-8", xml_declaration=True)
-
- file_object.seek(0)
- file_type = get_file_type(file_object)
-
- assert file_type == "xml"
-
-
-def test_get_file_type_identifies_tds_as_xml_file() -> None:
- with open(TEST_ASSET_DIR / "World Indicators.tds", "rb") as file_object:
- file_type = get_file_type(file_object)
- assert file_type == "xml"
-
-
-def test_get_file_type_identifies_twb_as_xml_file() -> None:
- with open(TEST_ASSET_DIR / "RESTAPISample.twb", "rb") as file_object:
- file_type = get_file_type(file_object)
- assert file_type == "xml"
-
-
-def test_get_file_type_identifies_hyper_file() -> None:
- with open(TEST_ASSET_DIR / "World Indicators.hyper", "rb") as file_object:
- file_type = get_file_type(file_object)
- assert file_type == "hyper"
-
-
-def test_get_file_type_identifies_tde_file() -> None:
- asset_path = TEST_ASSET_DIR / "Data" / "Tableau Samples" / "World Indicators.tde"
- with open(asset_path, "rb") as file_object:
- file_type = get_file_type(file_object)
- assert file_type == "tde"
-
-
-def test_get_file_type_handles_unknown_file_type() -> None:
- # Create a dummy png file
- with BytesIO() as file_object:
- png_signature = bytes.fromhex("89504E470D0A1A0A")
- file_object.write(png_signature)
- file_object.seek(0)
-
- with pytest.raises(ValueError):
- get_file_type(file_object)
diff --git a/test/test_fileuploads.py b/test/test_fileuploads.py
deleted file mode 100644
index 2e69b5884..000000000
--- a/test/test_fileuploads.py
+++ /dev/null
@@ -1,92 +0,0 @@
-import contextlib
-import io
-import os
-from pathlib import Path
-
-import pytest
-import requests_mock
-
-import tableauserverclient as TSC
-from tableauserverclient.config import BYTES_PER_MB, config
-
-TEST_ASSET_DIR = Path(__file__).parent / "assets"
-FILEUPLOAD_INITIALIZE = TEST_ASSET_DIR / "fileupload_initialize.xml"
-FILEUPLOAD_APPEND = TEST_ASSET_DIR / "fileupload_append.xml"
-SAMPLE_WB = TEST_ASSET_DIR / "SampleWB.twbx"
-
-
-@contextlib.contextmanager
-def set_env(**environ):
- old_environ = dict(os.environ)
- os.environ.update(environ)
- try:
- yield
- finally:
- os.environ.clear()
- os.environ.update(old_environ)
-
-
-@pytest.fixture(scope="function")
-def server():
- """Fixture to create a TSC.Server instance for testing."""
- server = TSC.Server("http://test", False)
-
- # Fake signin
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
-
- return server
-
-
-def test_read_chunks_file_path(server: TSC.Server) -> None:
- file_path = str(SAMPLE_WB)
- chunks = server.fileuploads._read_chunks(file_path)
- for chunk in chunks:
- assert chunk is not None
-
-
-def test_read_chunks_file_object(server: TSC.Server) -> None:
- with SAMPLE_WB.open("rb") as f:
- chunks = server.fileuploads._read_chunks(f)
- for chunk in chunks:
- assert chunk is not None
-
-
-def test_upload_chunks_file_path(server: TSC.Server) -> None:
- file_path = str(SAMPLE_WB)
- upload_id = "7720:170fe6b1c1c7422dadff20f944d58a52-1:0"
-
- initialize_response_xml = FILEUPLOAD_INITIALIZE.read_text()
- append_response_xml = FILEUPLOAD_APPEND.read_text()
- with requests_mock.mock() as m:
- m.post(server.fileuploads.baseurl, text=initialize_response_xml)
- m.put(f"{server.fileuploads.baseurl}/{upload_id}", text=append_response_xml)
- actual = server.fileuploads.upload(file_path)
-
- assert upload_id == actual
-
-
-def test_upload_chunks_file_object(server: TSC.Server) -> None:
- upload_id = "7720:170fe6b1c1c7422dadff20f944d58a52-1:0"
-
- with SAMPLE_WB.open("rb") as file_content:
- initialize_response_xml = FILEUPLOAD_INITIALIZE.read_text()
- append_response_xml = FILEUPLOAD_APPEND.read_text()
- with requests_mock.mock() as m:
- m.post(server.fileuploads.baseurl, text=initialize_response_xml)
- m.put(f"{server.fileuploads.baseurl}/{upload_id}", text=append_response_xml)
- actual = server.fileuploads.upload(file_content)
-
- assert upload_id == actual
-
-
-def test_upload_chunks_config(server: TSC.Server) -> None:
- data = io.BytesIO()
- data.write(b"1" * (config.CHUNK_SIZE_MB * BYTES_PER_MB + 1))
- data.seek(0)
- with set_env(TSC_CHUNK_SIZE_MB="1"):
- chunker = server.fileuploads._read_chunks(data)
- chunk = next(chunker)
- assert len(chunk) == config.CHUNK_SIZE_MB * BYTES_PER_MB
- data.seek(0)
- assert len(chunk) < len(data.read())
diff --git a/test/test_filter.py b/test/test_filter.py
deleted file mode 100644
index 460813dd5..000000000
--- a/test/test_filter.py
+++ /dev/null
@@ -1,16 +0,0 @@
-import tableauserverclient as TSC
-
-
-def test_filter_equal():
- filter = TSC.Filter(TSC.RequestOptions.Field.Name, TSC.RequestOptions.Operator.Equals, "Superstore")
-
- assert str(filter) == "name:eq:Superstore"
-
-
-def test_filter_in():
- # create a IN filter condition with project names that
- # contain spaces and "special" characters
- projects_to_find = ["default", "Salesforce Sales ProjeΕt"]
- filter = TSC.Filter(TSC.RequestOptions.Field.Name, TSC.RequestOptions.Operator.In, projects_to_find)
-
- assert str(filter) == "name:in:[default,Salesforce Sales ProjeΕt]"
diff --git a/test/test_flow.py b/test/test_flow.py
deleted file mode 100644
index e0275dcc9..000000000
--- a/test/test_flow.py
+++ /dev/null
@@ -1,254 +0,0 @@
-from io import BytesIO
-import os
-from pathlib import Path
-import requests_mock
-import tempfile
-
-import pytest
-
-import tableauserverclient as TSC
-from tableauserverclient.datetime_helpers import format_datetime
-
-TEST_ASSET_DIR = Path(__file__).parent / "assets"
-
-GET_XML = TEST_ASSET_DIR / "flow_get.xml"
-POPULATE_CONNECTIONS_XML = TEST_ASSET_DIR / "flow_populate_connections.xml"
-POPULATE_PERMISSIONS_XML = TEST_ASSET_DIR / "flow_populate_permissions.xml"
-PUBLISH_XML = TEST_ASSET_DIR / "flow_publish.xml"
-UPDATE_XML = TEST_ASSET_DIR / "flow_update.xml"
-REFRESH_XML = TEST_ASSET_DIR / "flow_refresh.xml"
-REFRESH_DUPLICATE_XML = TEST_ASSET_DIR / "flow_refresh_duplicate.xml"
-
-
-@pytest.fixture(scope="function")
-def server():
- """Fixture to create a TSC.Server instance for testing."""
- server = TSC.Server("http://test", False)
-
- # Fake signin
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
- server.version = "3.3"
-
- return server
-
-
-def test_download(server: TSC.Server, tmp_path: Path) -> None:
- with requests_mock.mock() as m:
- m.get(
- server.flows.baseurl + "/587daa37-b84d-4400-a9a2-aa90e0be7837/content",
- headers={"Content-Disposition": 'name="tableau_flow"; filename="FlowOne.tfl"'},
- )
- file_path = server.flows.download("587daa37-b84d-4400-a9a2-aa90e0be7837", filepath=tmp_path)
- assert os.path.exists(file_path) is True
-
-
-def test_download_object(server: TSC.Server) -> None:
- with BytesIO() as file_object:
- with requests_mock.mock() as m:
- m.get(
- server.flows.baseurl + "/587daa37-b84d-4400-a9a2-aa90e0be7837/content",
- headers={"Content-Disposition": 'name="tableau_flow"; filename="FlowOne.tfl"'},
- )
- file_path = server.flows.download("587daa37-b84d-4400-a9a2-aa90e0be7837", filepath=file_object)
- assert isinstance(file_path, BytesIO)
-
-
-def test_get(server: TSC.Server) -> None:
- response_xml = GET_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.flows.baseurl, text=response_xml)
- all_flows, pagination_item = server.flows.get()
-
- assert 5 == pagination_item.total_available
- assert "587daa37-b84d-4400-a9a2-aa90e0be7837" == all_flows[0].id
- assert "http://tableauserver/#/flows/1" == all_flows[0].webpage_url
- assert "2019-06-16T21:43:28Z" == format_datetime(all_flows[0].created_at)
- assert "2019-06-16T21:43:28Z" == format_datetime(all_flows[0].updated_at)
- assert "Default" == all_flows[0].project_name
- assert "FlowOne" == all_flows[0].name
- assert "aa23f4ac-906f-11e9-86fb-3f0f71412e77" == all_flows[0].project_id
- assert "7ebb3f20-0fd2-4f27-a2f6-c539470999e2" == all_flows[0].owner_id
- assert {"i_love_tags"} == all_flows[0].tags
- assert "Descriptive" == all_flows[0].description
-
- assert "5c36be69-eb30-461b-b66e-3e2a8e27cc35" == all_flows[1].id
- assert "http://tableauserver/#/flows/4" == all_flows[1].webpage_url
- assert "2019-06-18T03:08:19Z" == format_datetime(all_flows[1].created_at)
- assert "2019-06-18T03:08:19Z" == format_datetime(all_flows[1].updated_at)
- assert "Default" == all_flows[1].project_name
- assert "FlowTwo" == all_flows[1].name
- assert "aa23f4ac-906f-11e9-86fb-3f0f71412e77" == all_flows[1].project_id
- assert "9127d03f-d996-405f-b392-631b25183a0f" == all_flows[1].owner_id
-
-
-def test_update(server: TSC.Server) -> None:
- response_xml = UPDATE_XML.read_text()
- with requests_mock.mock() as m:
- m.put(server.flows.baseurl + "/587daa37-b84d-4400-a9a2-aa90e0be7837", text=response_xml)
- single_datasource = TSC.FlowItem("test", "aa23f4ac-906f-11e9-86fb-3f0f71412e77")
- single_datasource.owner_id = "7ebb3f20-0fd2-4f27-a2f6-c539470999e2"
- single_datasource._id = "587daa37-b84d-4400-a9a2-aa90e0be7837"
- single_datasource.description = "So fun to see"
- single_datasource = server.flows.update(single_datasource)
-
- assert "587daa37-b84d-4400-a9a2-aa90e0be7837" == single_datasource.id
- assert "aa23f4ac-906f-11e9-86fb-3f0f71412e77" == single_datasource.project_id
- assert "7ebb3f20-0fd2-4f27-a2f6-c539470999e2" == single_datasource.owner_id
- assert "So fun to see" == single_datasource.description
-
-
-def test_populate_connections(server: TSC.Server) -> None:
- response_xml = POPULATE_CONNECTIONS_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.flows.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/connections", text=response_xml)
- single_datasource = TSC.FlowItem("test", "aa23f4ac-906f-11e9-86fb-3f0f71412e77")
- single_datasource.owner_id = "dd2239f6-ddf1-4107-981a-4cf94e415794"
- single_datasource._id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb"
- server.flows.populate_connections(single_datasource)
- assert "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb" == single_datasource.id
- connections = single_datasource.connections
-
- assert connections is not None
- assert len(connections) > 0
- conn1, conn2, conn3 = connections
- assert "405c1e4b-60c9-499f-9c47-a4ef1af69359" == conn1.id
- assert "excel-direct" == conn1.connection_type
- assert "" == conn1.server_address
- assert "" == conn1.username
- assert conn1.embed_password is False
- assert "b47f41b1-2c47-41a3-8b17-a38ebe8b340c" == conn2.id
- assert "sqlserver" == conn2.connection_type
- assert "test.database.com" == conn2.server_address
- assert "bob" == conn2.username
- assert conn2.embed_password is False
- assert "4f4a3b78-0554-43a7-b327-9605e9df9dd2" == conn3.id
- assert "tableau-server-site" == conn3.connection_type
- assert "http://tableauserver" == conn3.server_address
- assert "sally" == conn3.username
- assert conn3.embed_password is True
-
-
-def test_populate_permissions(server: TSC.Server) -> None:
- response_xml = POPULATE_PERMISSIONS_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.flows.baseurl + "/0448d2ed-590d-4fa0-b272-a2a8a24555b5/permissions", text=response_xml)
- single_datasource = TSC.FlowItem("test")
- single_datasource._id = "0448d2ed-590d-4fa0-b272-a2a8a24555b5"
-
- server.flows.populate_permissions(single_datasource)
- permissions = single_datasource.permissions
-
- assert permissions[0].grantee.tag_name == "group"
- assert permissions[0].grantee.id == "aa42f384-906f-11e9-86fc-bb24278874b9"
- assert permissions[0].capabilities == {
- TSC.Permission.Capability.Write: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.Read: TSC.Permission.Mode.Allow,
- }
-
- assert permissions[1].grantee.tag_name == "groupSet"
- assert permissions[1].grantee.id == "7ea95a1b-6872-44d6-a969-68598a7df4a0"
- assert permissions[1].capabilities == {
- TSC.Permission.Capability.Write: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.Read: TSC.Permission.Mode.Allow,
- }
-
-
-def test_publish(server: TSC.Server) -> None:
- response_xml = PUBLISH_XML.read_text()
- with requests_mock.mock() as m:
- m.post(server.flows.baseurl, text=response_xml)
-
- new_flow = TSC.FlowItem(name="SampleFlow", project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760")
-
- sample_flow = TEST_ASSET_DIR / "SampleFlow.tfl"
- publish_mode = server.PublishMode.CreateNew
-
- new_flow = server.flows.publish(new_flow, sample_flow, publish_mode)
-
- assert "2457c468-1b24-461a-8f95-a461b3209d32" == new_flow.id
- assert "SampleFlow" == new_flow.name
- assert "2023-01-13T09:50:55Z" == format_datetime(new_flow.created_at)
- assert "2023-01-13T09:50:55Z" == format_datetime(new_flow.updated_at)
- assert "ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" == new_flow.project_id
- assert "default" == new_flow.project_name
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == new_flow.owner_id
-
-
-def test_publish_file_object(server: TSC.Server) -> None:
- response_xml = PUBLISH_XML.read_text()
- with requests_mock.mock() as m:
- m.post(server.flows.baseurl, text=response_xml)
-
- new_flow = TSC.FlowItem(name="SampleFlow", project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760")
- sample_flow = os.path.join(TEST_ASSET_DIR, "SampleFlow.tfl")
- publish_mode = server.PublishMode.CreateNew
-
- with open(sample_flow, "rb") as fp:
- publish_mode = server.PublishMode.CreateNew
-
- new_flow = server.flows.publish(new_flow, fp, publish_mode)
-
- assert "2457c468-1b24-461a-8f95-a461b3209d32" == new_flow.id
- assert "SampleFlow" == new_flow.name
- assert "2023-01-13T09:50:55Z" == format_datetime(new_flow.created_at)
- assert "2023-01-13T09:50:55Z" == format_datetime(new_flow.updated_at)
- assert "ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" == new_flow.project_id
- assert "default" == new_flow.project_name
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == new_flow.owner_id
-
-
-def test_refresh(server: TSC.Server) -> None:
- response_xml = REFRESH_XML.read_text()
- with requests_mock.mock() as m:
- m.post(server.flows.baseurl + "/92967d2d-c7e2-46d0-8847-4802df58f484/run", text=response_xml)
- flow_item = TSC.FlowItem("test")
- flow_item._id = "92967d2d-c7e2-46d0-8847-4802df58f484"
- refresh_job = server.flows.refresh(flow_item)
-
- assert refresh_job.id == "d1b2ccd0-6dfa-444a-aee4-723dbd6b7c9d"
- assert refresh_job.mode == "Asynchronous"
- assert refresh_job.type == "RunFlow"
- assert format_datetime(refresh_job.created_at) == "2018-05-22T13:00:29Z"
- assert isinstance(refresh_job.flow_run, TSC.FlowRunItem)
- assert refresh_job.flow_run.id == "e0c3067f-2333-4eee-8028-e0a56ca496f6"
- assert refresh_job.flow_run.flow_id == "92967d2d-c7e2-46d0-8847-4802df58f484"
- assert format_datetime(refresh_job.flow_run.started_at) == "2018-05-22T13:00:29Z"
-
-
-def test_refresh_id_str(server: TSC.Server) -> None:
- response_xml = REFRESH_XML.read_text()
- with requests_mock.mock() as m:
- m.post(server.flows.baseurl + "/92967d2d-c7e2-46d0-8847-4802df58f484/run", text=response_xml)
- refresh_job = server.flows.refresh("92967d2d-c7e2-46d0-8847-4802df58f484")
-
- assert refresh_job.id == "d1b2ccd0-6dfa-444a-aee4-723dbd6b7c9d"
- assert refresh_job.mode == "Asynchronous"
- assert refresh_job.type == "RunFlow"
- assert format_datetime(refresh_job.created_at) == "2018-05-22T13:00:29Z"
- assert isinstance(refresh_job.flow_run, TSC.FlowRunItem)
- assert refresh_job.flow_run.id == "e0c3067f-2333-4eee-8028-e0a56ca496f6"
- assert refresh_job.flow_run.flow_id == "92967d2d-c7e2-46d0-8847-4802df58f484"
- assert format_datetime(refresh_job.flow_run.started_at) == "2018-05-22T13:00:29Z"
-
-
-def test_refresh_already_running(server: TSC.Server) -> None:
- response_xml = REFRESH_DUPLICATE_XML.read_text()
- with requests_mock.mock() as m:
- m.post(
- server.flows.baseurl + "/92967d2d-c7e2-46d0-8847-4802df58f484/run",
- status_code=409,
- text=response_xml,
- )
- refresh_job = server.flows.refresh("92967d2d-c7e2-46d0-8847-4802df58f484")
- assert refresh_job is None
-
-
-def test_bad_download_response(server: TSC.Server) -> None:
- with requests_mock.mock() as m, tempfile.TemporaryDirectory() as td:
- m.get(
- server.flows.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/content",
- headers={"Content-Disposition": '''name="tableau_flow"; filename*=UTF-8''"Sample flow.tfl"'''},
- )
- file_path = server.flows.download("9dbd2263-16b5-46e1-9c43-a76bb8ab65fb", td)
- assert os.path.exists(file_path) is True
diff --git a/test/test_flowruns.py b/test/test_flowruns.py
deleted file mode 100644
index 003ee944b..000000000
--- a/test/test_flowruns.py
+++ /dev/null
@@ -1,121 +0,0 @@
-from pathlib import Path
-import sys
-
-import pytest
-import requests_mock
-
-import tableauserverclient as TSC
-from tableauserverclient.datetime_helpers import format_datetime
-from tableauserverclient.server.endpoint.exceptions import FlowRunFailedException
-from ._utils import mocked_time, server_response_error_factory
-
-TEST_ASSET_DIR = Path(__file__).parent / "assets"
-
-GET_XML = TEST_ASSET_DIR / "flow_runs_get.xml"
-GET_BY_ID_XML = TEST_ASSET_DIR / "flow_runs_get_by_id.xml"
-GET_BY_ID_FAILED_XML = TEST_ASSET_DIR / "flow_runs_get_by_id_failed.xml"
-GET_BY_ID_INPROGRESS_XML = TEST_ASSET_DIR / "flow_runs_get_by_id_inprogress.xml"
-
-
-@pytest.fixture(scope="function")
-def server() -> TSC.Server:
- server = TSC.Server("http://test", False)
- # Fake signin
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
- server.version = "3.10"
-
- return server
-
-
-def test_get(server: TSC.Server) -> None:
- response_xml = GET_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.flow_runs.baseurl, text=response_xml)
- all_flow_runs = server.flow_runs.get()
-
- assert "cc2e652d-4a9b-4476-8c93-b238c45db968" == all_flow_runs[0].id
- assert "2021-02-11T01:42:55Z" == format_datetime(all_flow_runs[0].started_at)
- assert "2021-02-11T01:57:38Z" == format_datetime(all_flow_runs[0].completed_at)
- assert "Success" == all_flow_runs[0].status
- assert "100" == all_flow_runs[0].progress
- assert "aa23f4ac-906f-11e9-86fb-3f0f71412e77" == all_flow_runs[0].background_job_id
-
- assert "a3104526-c0c6-4ea5-8362-e03fc7cbd7ee" == all_flow_runs[1].id
- assert "2021-02-13T04:05:30Z" == format_datetime(all_flow_runs[1].started_at)
- assert "2021-02-13T04:05:35Z" == format_datetime(all_flow_runs[1].completed_at)
- assert "Failed" == all_flow_runs[1].status
- assert "100" == all_flow_runs[1].progress
- assert "1ad21a9d-2530-4fbf-9064-efd3c736e023" == all_flow_runs[1].background_job_id
-
-
-def test_get_by_id(server: TSC.Server) -> None:
- response_xml = GET_BY_ID_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.flow_runs.baseurl + "/cc2e652d-4a9b-4476-8c93-b238c45db968", text=response_xml)
- flow_run = server.flow_runs.get_by_id("cc2e652d-4a9b-4476-8c93-b238c45db968")
-
- assert "cc2e652d-4a9b-4476-8c93-b238c45db968" == flow_run.id
- assert "2021-02-11T01:42:55Z" == format_datetime(flow_run.started_at)
- assert "2021-02-11T01:57:38Z" == format_datetime(flow_run.completed_at)
- assert "Success" == flow_run.status
- assert "100" == flow_run.progress
- assert "1ad21a9d-2530-4fbf-9064-efd3c736e023" == flow_run.background_job_id
-
-
-def test_cancel_id(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.put(server.flow_runs.baseurl + "/ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", status_code=204)
- server.flow_runs.cancel("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760")
-
-
-def test_cancel_item(server: TSC.Server) -> None:
- run = TSC.FlowRunItem()
- run._id = "ee8c6e70-43b6-11e6-af4f-f7b0d8e20760"
- with requests_mock.mock() as m:
- m.put(server.flow_runs.baseurl + "/ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", status_code=204)
- server.flow_runs.cancel(run)
-
-
-def test_wait_for_job_finished(server: TSC.Server) -> None:
- # Waiting for an already finished job, directly returns that job's info
- response_xml = GET_BY_ID_XML.read_text()
- flow_run_id = "cc2e652d-4a9b-4476-8c93-b238c45db968"
- with mocked_time(), requests_mock.mock() as m:
- m.get(f"{server.flow_runs.baseurl}/{flow_run_id}", text=response_xml)
- flow_run = server.flow_runs.wait_for_job(flow_run_id)
-
- assert flow_run_id == flow_run.id
- assert flow_run.progress == "100"
-
-
-def test_wait_for_job_failed(server: TSC.Server) -> None:
- # Waiting for a failed job raises an exception
- response_xml = GET_BY_ID_FAILED_XML.read_text()
- flow_run_id = "c2b35d5a-e130-471a-aec8-7bc5435fe0e7"
- with mocked_time(), requests_mock.mock() as m:
- m.get(f"{server.flow_runs.baseurl}/{flow_run_id}", text=response_xml)
- with pytest.raises(FlowRunFailedException):
- server.flow_runs.wait_for_job(flow_run_id)
-
-
-def test_wait_for_job_timeout(server: TSC.Server) -> None:
- # Waiting for a job which doesn't terminate will throw an exception
- response_xml = GET_BY_ID_INPROGRESS_XML.read_text()
- flow_run_id = "71afc22c-9c06-40be-8d0f-4c4166d29e6c"
- with mocked_time(), requests_mock.mock() as m:
- m.get(f"{server.flow_runs.baseurl}/{flow_run_id}", text=response_xml)
- with pytest.raises(TimeoutError):
- server.flow_runs.wait_for_job(flow_run_id, timeout=30)
-
-
-def test_queryset(server: TSC.Server) -> None:
- response_xml = GET_XML.read_text()
- error_response = server_response_error_factory(
- "400006", "Bad Request", "0xB4EAB088 : The start index '9900' is greater than or equal to the total count.)"
- )
- with requests_mock.mock() as m:
- m.get(f"{server.flow_runs.baseurl}?pageNumber=1", text=response_xml)
- m.get(f"{server.flow_runs.baseurl}?pageNumber=2", text=error_response)
- queryset = server.flow_runs.all()
- assert len(queryset) == sys.maxsize
diff --git a/test/test_flowtask.py b/test/test_flowtask.py
deleted file mode 100644
index 601446c0e..000000000
--- a/test/test_flowtask.py
+++ /dev/null
@@ -1,46 +0,0 @@
-from datetime import time
-from pathlib import Path
-
-import pytest
-import requests_mock
-
-import tableauserverclient as TSC
-from tableauserverclient.models.task_item import TaskItem
-
-TEST_ASSET_DIR = Path(__file__).parent / "assets"
-GET_XML_CREATE_FLOW_TASK_RESPONSE = TEST_ASSET_DIR / "tasks_create_flow_task.xml"
-
-
-@pytest.fixture(scope="function")
-def server():
- """Fixture to create a TSC.Server instance for testing."""
- server = TSC.Server("http://test", False)
-
- # Fake signin
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
- server.version = "3.22"
-
- return server
-
-
-def test_create_flow_task(server: TSC.Server) -> None:
- monthly_interval = TSC.MonthlyInterval(start_time=time(23, 30), interval_value=15)
- monthly_schedule = TSC.ScheduleItem(
- "Monthly Schedule",
- 50,
- TSC.ScheduleItem.Type.Flow,
- TSC.ScheduleItem.ExecutionOrder.Parallel,
- monthly_interval,
- )
- target_item = TSC.Target("flow_id", "flow")
-
- task = TaskItem("", "RunFlow", 0, schedule_item=monthly_schedule, target=target_item)
-
- response_xml = GET_XML_CREATE_FLOW_TASK_RESPONSE.read_text()
- with requests_mock.mock() as m:
- m.post(f"{server.flow_tasks.baseurl}", text=response_xml)
- create_response_content = server.flow_tasks.create(task).decode("utf-8")
-
- assert "schedule_id" in create_response_content
- assert "flow_id" in create_response_content
diff --git a/test/test_group.py b/test/test_group.py
deleted file mode 100644
index 734b5fa38..000000000
--- a/test/test_group.py
+++ /dev/null
@@ -1,342 +0,0 @@
-from pathlib import Path
-import requests_mock
-import tableauserverclient as TSC
-from tableauserverclient.datetime_helpers import format_datetime
-
-import pytest
-
-TEST_ASSET_DIR = Path(__file__).absolute().parent / "assets"
-
-GET_XML = TEST_ASSET_DIR / "group_get.xml"
-GET_XML_ALL_FIELDS = TEST_ASSET_DIR / "group_get_all_fields.xml"
-POPULATE_USERS = TEST_ASSET_DIR / "group_populate_users.xml"
-POPULATE_USERS_EMPTY = TEST_ASSET_DIR / "group_populate_users_empty.xml"
-ADD_USER = TEST_ASSET_DIR / "group_add_user.xml"
-ADD_USERS = TEST_ASSET_DIR / "group_add_users.xml"
-ADD_USER_POPULATE = TEST_ASSET_DIR / "group_users_added.xml"
-CREATE_GROUP = TEST_ASSET_DIR / "group_create.xml"
-CREATE_GROUP_AD = TEST_ASSET_DIR / "group_create_ad.xml"
-CREATE_GROUP_ASYNC = TEST_ASSET_DIR / "group_create_async.xml"
-UPDATE_XML = TEST_ASSET_DIR / "group_update.xml"
-UPDATE_ASYNC_XML = TEST_ASSET_DIR / "group_update_async.xml"
-
-
-@pytest.fixture(scope="function")
-def server():
- """Fixture to create a TSC.Server instance for testing."""
- server = TSC.Server("http://test", False)
-
- # Fake signin
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
-
- return server
-
-
-def test_get(server: TSC.Server) -> None:
- response_xml = GET_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.groups.baseurl, text=response_xml)
- all_groups, pagination_item = server.groups.get()
-
- assert 3 == pagination_item.total_available
- assert "ef8b19c0-43b6-11e6-af50-63f5805dbe3c" == all_groups[0].id
- assert "All Users" == all_groups[0].name
- assert "local" == all_groups[0].domain_name
-
- assert "e7833b48-c6f7-47b5-a2a7-36e7dd232758" == all_groups[1].id
- assert "Another group" == all_groups[1].name
- assert "local" == all_groups[1].domain_name
-
- assert "86a66d40-f289-472a-83d0-927b0f954dc8" == all_groups[2].id
- assert "TableauExample" == all_groups[2].name
- assert "local" == all_groups[2].domain_name
-
-
-def test_get_before_signin(server: TSC.Server) -> None:
- server._auth_token = None
- with pytest.raises(TSC.NotSignedInError):
- server.groups.get()
-
-
-def test_populate_users(server: TSC.Server) -> None:
- response_xml = POPULATE_USERS.read_text()
- with requests_mock.mock() as m:
- m.get(
- server.groups.baseurl + "/e7833b48-c6f7-47b5-a2a7-36e7dd232758/users?pageNumber=1&pageSize=100",
- text=response_xml,
- complete_qs=True,
- )
- single_group = TSC.GroupItem(name="Test Group")
- single_group._id = "e7833b48-c6f7-47b5-a2a7-36e7dd232758"
- server.groups.populate_users(single_group)
-
- assert 1 == len(list(single_group.users))
- user = list(single_group.users).pop()
- assert "dd2239f6-ddf1-4107-981a-4cf94e415794" == user.id
- assert "alice" == user.name
- assert "Publisher" == user.site_role
- assert "2016-08-16T23:17:06Z" == format_datetime(user.last_login)
-
-
-def test_delete(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.delete(server.groups.baseurl + "/e7833b48-c6f7-47b5-a2a7-36e7dd232758", status_code=204)
- server.groups.delete("e7833b48-c6f7-47b5-a2a7-36e7dd232758")
-
-
-def test_remove_user(server: TSC.Server) -> None:
- response_xml_populate = POPULATE_USERS.read_text()
-
- response_xml_empty = POPULATE_USERS_EMPTY.read_text()
-
- with requests_mock.mock() as m:
- url = (
- server.groups.baseurl + "/e7833b48-c6f7-47b5-a2a7-36e7dd232758/users"
- "/dd2239f6-ddf1-4107-981a-4cf94e415794"
- )
-
- m.delete(url, status_code=204)
- # We register the get endpoint twice. The first time we have 1 user, the second we have 'removed' them.
- m.get(server.groups.baseurl + "/e7833b48-c6f7-47b5-a2a7-36e7dd232758/users", text=response_xml_populate)
-
- single_group = TSC.GroupItem("test")
- single_group._id = "e7833b48-c6f7-47b5-a2a7-36e7dd232758"
- server.groups.populate_users(single_group)
- assert 1 == len(list(single_group.users))
- server.groups.remove_user(single_group, "dd2239f6-ddf1-4107-981a-4cf94e415794")
-
- m.get(server.groups.baseurl + "/e7833b48-c6f7-47b5-a2a7-36e7dd232758/users", text=response_xml_empty)
- assert 0 == len(list(single_group.users))
-
-
-def test_add_user(server: TSC.Server) -> None:
- response_xml_add = ADD_USER.read_text()
- response_xml_populate = ADD_USER_POPULATE.read_text()
- with requests_mock.mock() as m:
- m.post(server.groups.baseurl + "/e7833b48-c6f7-47b5-a2a7-36e7dd232758/users", text=response_xml_add)
- m.get(server.groups.baseurl + "/e7833b48-c6f7-47b5-a2a7-36e7dd232758/users", text=response_xml_populate)
- single_group = TSC.GroupItem("test")
- single_group._id = "e7833b48-c6f7-47b5-a2a7-36e7dd232758"
-
- server.groups.add_user(single_group, "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7")
- server.groups.populate_users(single_group)
- assert 1 == len(list(single_group.users))
- user = list(single_group.users).pop()
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == user.id
- assert "testuser" == user.name
- assert "ServerAdministrator" == user.site_role
-
-
-def test_add_users(server: TSC.Server) -> None:
- server.version = "3.21"
-
- def make_user(id: str, name: str, siteRole: str) -> TSC.UserItem:
- user = TSC.UserItem(name, siteRole)
- user._id = id
- return user
-
- users = [
- make_user(id="5de011f8-4aa9-4d5b-b991-f464c8dd6bb7", name="Alice", siteRole="ServerAdministrator"),
- make_user(id="5de011f8-3aa9-4d5b-b991-f467c8dd6bb8", name="Bob", siteRole="Explorer"),
- make_user(id="5de011f8-2aa9-4d5b-b991-f466c8dd6bb8", name="Charlie", siteRole="Viewer"),
- ]
- group = TSC.GroupItem("test")
- group._id = "e7833b48-c6f7-47b5-a2a7-36e7dd232758"
-
- with requests_mock.mock() as m:
- m.post(f"{server.groups.baseurl}/{group.id}/users", text=ADD_USERS.read_text())
- resp_users = server.groups.add_users(group, users)
-
- for user, resp_user in zip(users, resp_users):
- assert user.id == resp_user.id
- assert user.name == resp_user.name
- assert user.site_role == resp_user.site_role
-
-
-def test_remove_users(server: TSC.Server) -> None:
- server.version = "3.21"
-
- def make_user(id: str, name: str, siteRole: str) -> TSC.UserItem:
- user = TSC.UserItem(name, siteRole)
- user._id = id
- return user
-
- users = [
- make_user(id="5de011f8-4aa9-4d5b-b991-f464c8dd6bb7", name="Alice", siteRole="ServerAdministrator"),
- make_user(id="5de011f8-3aa9-4d5b-b991-f467c8dd6bb8", name="Bob", siteRole="Explorer"),
- make_user(id="5de011f8-2aa9-4d5b-b991-f466c8dd6bb8", name="Charlie", siteRole="Viewer"),
- ]
- group = TSC.GroupItem("test")
- group._id = "e7833b48-c6f7-47b5-a2a7-36e7dd232758"
-
- with requests_mock.mock() as m:
- m.put(f"{server.groups.baseurl}/{group.id}/users/remove")
- server.groups.remove_users(group, users)
-
-
-def test_add_user_before_populating(server: TSC.Server) -> None:
- get_xml_response = GET_XML.read_text()
- add_user_response = ADD_USER.read_text()
- with requests_mock.mock() as m:
- m.get(server.groups.baseurl, text=get_xml_response)
- m.post(
- server.groups.baseurl + "/ef8b19c0-43b6-11e6-af50-63f5805dbe3c/users",
- text=add_user_response,
- )
- all_groups, pagination_item = server.groups.get()
- single_group = all_groups[0]
- server.groups.add_user(single_group, "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7")
-
-
-def test_add_user_missing_user_id(server: TSC.Server) -> None:
- response_xml = POPULATE_USERS.read_text()
- with requests_mock.mock() as m:
- m.get(server.groups.baseurl + "/e7833b48-c6f7-47b5-a2a7-36e7dd232758/users", text=response_xml)
- single_group = TSC.GroupItem(name="Test Group")
- single_group._id = "e7833b48-c6f7-47b5-a2a7-36e7dd232758"
- server.groups.populate_users(single_group)
-
- with pytest.raises(ValueError):
- server.groups.add_user(single_group, "")
-
-
-def test_add_user_missing_group_id(server: TSC.Server) -> None:
- single_group = TSC.GroupItem("test")
- with pytest.raises(TSC.MissingRequiredFieldError):
- server.groups.add_user(
- single_group,
- "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7",
- )
-
-
-def test_remove_user_before_populating(server: TSC.Server) -> None:
- response_xml = GET_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.groups.baseurl, text=response_xml)
- m.delete(
- server.groups.baseurl + "/ef8b19c0-43b6-11e6-af50-63f5805dbe3c/users/5de011f8-5aa9-4d5b-b991-f462c8dd6bb7",
- text="ok",
- )
- all_groups, pagination_item = server.groups.get()
- single_group = all_groups[0]
- server.groups.remove_user(single_group, "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7")
-
-
-def test_remove_user_missing_user_id(server: TSC.Server) -> None:
- response_xml = POPULATE_USERS.read_text()
- with requests_mock.mock() as m:
- m.get(server.groups.baseurl + "/e7833b48-c6f7-47b5-a2a7-36e7dd232758/users", text=response_xml)
- single_group = TSC.GroupItem(name="Test Group")
- single_group._id = "e7833b48-c6f7-47b5-a2a7-36e7dd232758"
- server.groups.populate_users(single_group)
-
- with pytest.raises(ValueError):
- server.groups.remove_user(single_group, "")
-
-
-def test_remove_user_missing_group_id(server: TSC.Server) -> None:
- single_group = TSC.GroupItem("test")
- with pytest.raises(TSC.MissingRequiredFieldError):
- server.groups.remove_user(
- single_group,
- "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7",
- )
-
-
-def test_create_group(server: TSC.Server) -> None:
- response_xml = CREATE_GROUP.read_text(encoding="utf-8")
- with requests_mock.mock() as m:
- m.post(server.groups.baseurl, text=response_xml)
- group_to_create = TSC.GroupItem("試δΎε")
- group = server.groups.create(group_to_create)
- assert group.name == "試δΎε"
- assert group.id == "3e4a9ea0-a07a-4fe6-b50f-c345c8c81034"
-
-
-def test_create_ad_group(server: TSC.Server) -> None:
- response_xml = CREATE_GROUP_AD.read_bytes().decode("utf8")
- with requests_mock.mock() as m:
- m.post(server.groups.baseurl, text=response_xml)
- group_to_create = TSC.GroupItem("試δΎε")
- group_to_create.domain_name = "just-has-to-exist"
- group = server.groups.create_AD_group(group_to_create, False)
- assert group.name == "試δΎε"
- assert group.license_mode == "onLogin"
- assert group.minimum_site_role == "Creator"
- assert group.domain_name == "active-directory-domain-name"
-
-
-def test_create_group_async(server: TSC.Server) -> None:
- response_xml = CREATE_GROUP_ASYNC.read_text()
- with requests_mock.mock() as m:
- m.post(server.groups.baseurl, text=response_xml)
- group_to_create = TSC.GroupItem("試δΎε")
- group_to_create.domain_name = "woohoo"
- job = server.groups.create_AD_group(group_to_create, True)
- assert job.mode == "Asynchronous"
- assert job.type == "GroupImport"
-
-
-def test_update(server: TSC.Server) -> None:
- response_xml = UPDATE_XML.read_text()
- with requests_mock.mock() as m:
- m.put(server.groups.baseurl + "/ef8b19c0-43b6-11e6-af50-63f5805dbe3c", text=response_xml)
- group = TSC.GroupItem(name="Test Group")
- group._domain_name = "local"
- group._id = "ef8b19c0-43b6-11e6-af50-63f5805dbe3c"
- group = server.groups.update(group)
-
- assert "ef8b19c0-43b6-11e6-af50-63f5805dbe3c" == group.id
- assert "Group updated name" == group.name
- assert "ExplorerCanPublish" == group.minimum_site_role
- assert "onLogin" == group.license_mode
-
-
-# async update is not supported for local groups
-def test_update_local_async(server: TSC.Server) -> None:
- group = TSC.GroupItem("myGroup")
- group._id = "ef8b19c0-43b6-11e6-af50-63f5805dbe3c"
- with pytest.raises(ValueError):
- server.groups.update(group, as_job=True)
-
- # mimic group returned from server where domain name is set to 'local'
- group.domain_name = "local"
- with pytest.raises(ValueError):
- server.groups.update(group, as_job=True)
-
-
-def test_update_ad_async(server: TSC.Server) -> None:
- group = TSC.GroupItem("myGroup", "example.com")
- group._id = "ef8b19c0-43b6-11e6-af50-63f5805dbe3c"
- group.minimum_site_role = TSC.UserItem.Roles.Viewer
-
- with requests_mock.mock() as m:
- m.put(f"{server.groups.baseurl}/{group.id}?asJob=True", text=UPDATE_ASYNC_XML.read_bytes().decode("utf8"))
- job = server.groups.update(group, as_job=True)
-
- assert job.id == "c2566efc-0767-4f15-89cb-56acb4349c1b"
- assert job.mode == "Asynchronous"
- assert job.type == "GroupSync"
-
-
-def test_get_all_fields(server: TSC.Server) -> None:
- ro = TSC.RequestOptions()
- ro.all_fields = True
- server.version = "3.21"
- with requests_mock.mock() as m:
- m.get(f"{server.groups.baseurl}?fields=_all_", text=GET_XML_ALL_FIELDS.read_text())
- groups, pages = server.groups.get(req_options=ro)
-
- assert pages.total_available == 3
- assert len(groups) == 3
- assert groups[0].id == "28c5b855-16df-482f-ad0b-428c1df58859"
- assert groups[0].name == "All Users"
- assert groups[0].user_count == 2
- assert groups[0].domain_name == "local"
- assert groups[1].id == "ace1ee2d-e7dd-4d7a-9504-a1ccaa5212ea"
- assert groups[1].name == "group1"
- assert groups[1].user_count == 1
- assert groups[2].id == "baf0ed9d-c25d-4114-97ed-5232b8a732fd"
- assert groups[2].name == "test"
- assert groups[2].user_count == 0
diff --git a/test/test_group_model.py b/test/test_group_model.py
deleted file mode 100644
index 6ca2f6b25..000000000
--- a/test/test_group_model.py
+++ /dev/null
@@ -1,15 +0,0 @@
-import pytest
-
-import tableauserverclient as TSC
-
-
-def test_invalid_minimum_site_role():
- group = TSC.GroupItem("grp")
- with pytest.raises(ValueError):
- group.minimum_site_role = "Captain"
-
-
-def test_invalid_license_mode():
- group = TSC.GroupItem("grp")
- with pytest.raises(ValueError):
- group.license_mode = "off"
diff --git a/test/test_groupsets.py b/test/test_groupsets.py
deleted file mode 100644
index e8276d803..000000000
--- a/test/test_groupsets.py
+++ /dev/null
@@ -1,146 +0,0 @@
-from pathlib import Path
-
-import pytest
-import requests_mock
-
-import tableauserverclient as TSC
-from tableauserverclient.models.reference_item import ResourceReference
-
-TEST_ASSET_DIR = Path(__file__).parent / "assets"
-GROUPSET_CREATE = TEST_ASSET_DIR / "groupsets_create.xml"
-GROUPSETS_GET = TEST_ASSET_DIR / "groupsets_get.xml"
-GROUPSET_GET_BY_ID = TEST_ASSET_DIR / "groupsets_get_by_id.xml"
-GROUPSET_UPDATE = TEST_ASSET_DIR / "groupsets_get_by_id.xml"
-
-
-@pytest.fixture(scope="function")
-def server():
- """Fixture to create a TSC.Server instance for testing."""
- server = TSC.Server("http://test", False)
-
- # Fake signin
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
- server.version = "3.22"
-
- return server
-
-
-def test_get(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.get(server.group_sets.baseurl, text=GROUPSETS_GET.read_text())
- groupsets, pagination_item = server.group_sets.get()
-
- assert len(groupsets) == 3
- assert pagination_item.total_available == 3
- assert groupsets[0].id == "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
- assert groupsets[0].name == "All Users"
- assert groupsets[0].group_count == 1
- assert groupsets[0].groups[0].name == "group-one"
- assert groupsets[0].groups[0].id == "gs-1"
-
- assert groupsets[1].id == "9a8a7b6b-5c4c-3d2d-1e0e-9a8a7b6b5b4b"
- assert groupsets[1].name == "active-directory-group-import"
- assert groupsets[1].group_count == 1
- assert groupsets[1].groups[0].name == "group-two"
- assert groupsets[1].groups[0].id == "gs21"
-
- assert groupsets[2].id == "7b6b59a8-ac3c-4d1d-2e9e-0b5b4ba8a7b6"
- assert groupsets[2].name == "local-group-license-on-login"
- assert groupsets[2].group_count == 1
- assert groupsets[2].groups[0].name == "group-three"
- assert groupsets[2].groups[0].id == "gs-3"
-
-
-def test_get_by_id(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.get(f"{server.group_sets.baseurl}/1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d", text=GROUPSET_GET_BY_ID.read_text())
- groupset = server.group_sets.get_by_id("1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d")
-
- assert groupset.id == "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
- assert groupset.name == "All Users"
- assert groupset.group_count == 3
- assert len(groupset.groups) == 3
-
- assert groupset.groups[0].name == "group-one"
- assert groupset.groups[0].id == "gs-1"
- assert groupset.groups[1].name == "group-two"
- assert groupset.groups[1].id == "gs21"
- assert groupset.groups[2].name == "group-three"
- assert groupset.groups[2].id == "gs-3"
-
-
-def test_update(server: TSC.Server) -> None:
- id_ = "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
- groupset = TSC.GroupSetItem("All Users")
- groupset.id = id_
- with requests_mock.mock() as m:
- m.put(f"{server.group_sets.baseurl}/{id_}", text=GROUPSET_UPDATE.read_text())
- groupset = server.group_sets.update(groupset)
-
- assert groupset.id == id_
- assert groupset.name == "All Users"
- assert groupset.group_count == 3
- assert len(groupset.groups) == 3
-
- assert groupset.groups[0].name == "group-one"
- assert groupset.groups[0].id == "gs-1"
- assert groupset.groups[1].name == "group-two"
- assert groupset.groups[1].id == "gs21"
- assert groupset.groups[2].name == "group-three"
- assert groupset.groups[2].id == "gs-3"
-
-
-def test_create(server: TSC.Server) -> None:
- groupset = TSC.GroupSetItem("All Users")
- with requests_mock.mock() as m:
- m.post(server.group_sets.baseurl, text=GROUPSET_CREATE.read_text())
- groupset = server.group_sets.create(groupset)
-
- assert groupset.id == "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
- assert groupset.name == "All Users"
- assert groupset.group_count == 0
- assert len(groupset.groups) == 0
-
-
-def test_add_group(server: TSC.Server) -> None:
- groupset = TSC.GroupSetItem("All")
- groupset.id = "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
- group = TSC.GroupItem("Example")
- group._id = "ef8b19c0-43b6-11e6-af50-63f5805dbe3c"
-
- with requests_mock.mock() as m:
- m.put(f"{server.group_sets.baseurl}/{groupset.id}/groups/{group._id}")
- server.group_sets.add_group(groupset, group)
-
- history = m.request_history
-
- assert len(history) == 1
- assert history[0].method == "PUT"
- assert history[0].url == f"{server.group_sets.baseurl}/{groupset.id}/groups/{group._id}"
-
-
-def test_remove_group(server: TSC.Server) -> None:
- groupset = TSC.GroupSetItem("All")
- groupset.id = "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
- group = TSC.GroupItem("Example")
- group._id = "ef8b19c0-43b6-11e6-af50-63f5805dbe3c"
-
- with requests_mock.mock() as m:
- m.delete(f"{server.group_sets.baseurl}/{groupset.id}/groups/{group._id}")
- server.group_sets.remove_group(groupset, group)
-
- history = m.request_history
-
- assert len(history) == 1
- assert history[0].method == "DELETE"
- assert history[0].url == f"{server.group_sets.baseurl}/{groupset.id}/groups/{group._id}"
-
-
-def test_as_reference(server: TSC.Server) -> None:
- groupset = TSC.GroupSetItem()
- groupset.id = "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
- ref = groupset.as_reference(groupset.id)
- assert ref.id == groupset.id
- assert ref.tag_name == groupset.tag_name
- assert isinstance(ref, ResourceReference)
diff --git a/test/test_job.py b/test/test_job.py
deleted file mode 100644
index 19f324d1e..000000000
--- a/test/test_job.py
+++ /dev/null
@@ -1,173 +0,0 @@
-from datetime import datetime
-from pathlib import Path
-
-import pytest
-import requests_mock
-
-import tableauserverclient as TSC
-from tableauserverclient.datetime_helpers import utc
-from tableauserverclient.server.endpoint.exceptions import JobFailedException
-from ._utils import mocked_time
-
-TEST_ASSET_DIR = Path(__file__).parent / "assets"
-GET_XML = TEST_ASSET_DIR / "job_get.xml"
-GET_BY_ID_XML = TEST_ASSET_DIR / "job_get_by_id.xml"
-GET_BY_ID_COMPLETED_XML = TEST_ASSET_DIR / "job_get_by_id_completed.xml"
-GET_BY_ID_FAILED_XML = TEST_ASSET_DIR / "job_get_by_id_failed.xml"
-GET_BY_ID_CANCELLED_XML = TEST_ASSET_DIR / "job_get_by_id_cancelled.xml"
-GET_BY_ID_INPROGRESS_XML = TEST_ASSET_DIR / "job_get_by_id_inprogress.xml"
-GET_BY_ID_WORKBOOK = TEST_ASSET_DIR / "job_get_by_id_failed_workbook.xml"
-
-
-@pytest.fixture(scope="function")
-def server():
- """Fixture to create a TSC.Server instance for testing."""
- server = TSC.Server("http://test", False)
-
- # Fake signin
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
- server.version = "3.1"
-
- return server
-
-
-def test_get(server: TSC.Server) -> None:
- response_xml = GET_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.jobs.baseurl, text=response_xml)
- all_jobs, pagination_item = server.jobs.get()
- job = all_jobs[0]
- created_at = datetime(2018, 5, 22, 13, 0, 29, tzinfo=utc)
- started_at = datetime(2018, 5, 22, 13, 0, 37, tzinfo=utc)
- ended_at = datetime(2018, 5, 22, 13, 0, 45, tzinfo=utc)
-
- assert 1 == pagination_item.total_available
- assert "2eef4225-aa0c-41c4-8662-a76d89ed7336" == job.id
- assert "Success" == job.status
- assert "50" == job.priority
- assert "single_subscription_notify" == job.type
- assert created_at == job.created_at
- assert started_at == job.started_at
- assert ended_at == job.ended_at
-
-
-def test_get_by_id(server: TSC.Server) -> None:
- response_xml = GET_BY_ID_XML.read_text()
- job_id = "2eef4225-aa0c-41c4-8662-a76d89ed7336"
- with requests_mock.mock() as m:
- m.get(f"{server.jobs.baseurl}/{job_id}", text=response_xml)
- job = server.jobs.get_by_id(job_id)
- updated_at = datetime(2020, 5, 13, 20, 25, 18, tzinfo=utc)
-
- assert job_id == job.id
- assert updated_at == job.updated_at
- assert job.notes == ["Job detail notes"]
-
-
-def test_get_before_signin(server: TSC.Server) -> None:
- server._auth_token = None
- with pytest.raises(TSC.NotSignedInError):
- server.jobs.get()
-
-
-def test_cancel_id(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.put(server.jobs.baseurl + "/ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", status_code=204)
- server.jobs.cancel("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760")
-
-
-def test_cancel_item(server: TSC.Server) -> None:
- created_at = datetime(2018, 5, 22, 13, 0, 29, tzinfo=utc)
- started_at = datetime(2018, 5, 22, 13, 0, 37, tzinfo=utc)
- job = TSC.JobItem("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", "backgroundJob", "0", created_at, started_at, None, 0)
- with requests_mock.mock() as m:
- m.put(server.jobs.baseurl + "/ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", status_code=204)
- server.jobs.cancel(job)
-
-
-def test_wait_for_job_finished(server: TSC.Server) -> None:
- # Waiting for an already finished job, directly returns that job's info
- response_xml = GET_BY_ID_XML.read_text()
- job_id = "2eef4225-aa0c-41c4-8662-a76d89ed7336"
- with mocked_time(), requests_mock.mock() as m:
- m.get(f"{server.jobs.baseurl}/{job_id}", text=response_xml)
- job = server.jobs.wait_for_job(job_id)
-
- assert job_id == job.id
- assert job.notes == ["Job detail notes"]
-
-
-def test_wait_for_job_completed(server: TSC.Server) -> None:
- # Waiting for a bridge (cloud) job completion
- response_xml = GET_BY_ID_COMPLETED_XML.read_text()
- job_id = "2eef4225-aa0c-41c4-8662-a76d89ed7336"
- with mocked_time(), requests_mock.mock() as m:
- m.get(f"{server.jobs.baseurl}/{job_id}", text=response_xml)
- job = server.jobs.wait_for_job(job_id)
-
- assert job_id == job.id
- assert job.notes == ["Job detail notes"]
-
-
-def test_wait_for_job_failed(server: TSC.Server) -> None:
- # Waiting for a failed job raises an exception
- response_xml = GET_BY_ID_FAILED_XML.read_text()
- job_id = "77d5e57a-2517-479f-9a3c-a32025f2b64d"
- with mocked_time(), requests_mock.mock() as m:
- m.get(f"{server.jobs.baseurl}/{job_id}", text=response_xml)
- with pytest.raises(JobFailedException):
- server.jobs.wait_for_job(job_id)
-
-
-def test_wait_for_job_timeout(server: TSC.Server) -> None:
- # Waiting for a job which doesn't terminate will throw an exception
- response_xml = GET_BY_ID_INPROGRESS_XML.read_text()
- job_id = "77d5e57a-2517-479f-9a3c-a32025f2b64d"
- with mocked_time(), requests_mock.mock() as m:
- m.get(f"{server.jobs.baseurl}/{job_id}", text=response_xml)
- with pytest.raises(TimeoutError):
- server.jobs.wait_for_job(job_id, timeout=30)
-
-
-def test_get_job_datasource_id(server: TSC.Server) -> None:
- response_xml = GET_BY_ID_FAILED_XML.read_text()
- job_id = "777bf7c4-421d-4b2c-a518-11b90187c545"
- with requests_mock.mock() as m:
- m.get(f"{server.jobs.baseurl}/{job_id}", text=response_xml)
- job = server.jobs.get_by_id(job_id)
- assert job.datasource_id == "03b9fbec-81f6-4160-ae49-5f9f6d412758"
-
-
-def test_get_job_workbook_id(server: TSC.Server) -> None:
- response_xml = GET_BY_ID_WORKBOOK.read_text()
- job_id = "bb1aab79-db54-4e96-9dd3-461d8f081d08"
- with requests_mock.mock() as m:
- m.get(f"{server.jobs.baseurl}/{job_id}", text=response_xml)
- job = server.jobs.get_by_id(job_id)
- assert job.workbook_id == "5998aaaf-1abe-4d38-b4d9-bc53e85bdd13"
-
-
-def test_get_job_workbook_name(server: TSC.Server) -> None:
- response_xml = GET_BY_ID_WORKBOOK.read_text()
- job_id = "bb1aab79-db54-4e96-9dd3-461d8f081d08"
- with requests_mock.mock() as m:
- m.get(f"{server.jobs.baseurl}/{job_id}", text=response_xml)
- job = server.jobs.get_by_id(job_id)
- assert job.workbook_name == "Superstore"
-
-
-def test_get_job_datasource_name(server: TSC.Server) -> None:
- response_xml = GET_BY_ID_FAILED_XML.read_text()
- job_id = "777bf7c4-421d-4b2c-a518-11b90187c545"
- with requests_mock.mock() as m:
- m.get(f"{server.jobs.baseurl}/{job_id}", text=response_xml)
- job = server.jobs.get_by_id(job_id)
- assert job.datasource_name == "World Indicators"
-
-
-def test_background_job_str() -> None:
- job = TSC.BackgroundJobItem("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", datetime.now(), 1, "extractRefresh", "Failed")
- assert not str(job).startswith("< None:
- xml = fromstring(GET_LINKED_TASKS.read_bytes())
- task_runs = LinkedTaskFlowRunItem._parse_element(xml, server.namespace)
- assert 1 == len(task_runs)
- task = task_runs[0]
- assert task.flow_run_id == "e3d1fc25-5644-4e32-af35-58dcbd1dbd73"
- assert task.flow_run_priority == 1
- assert task.flow_run_consecutive_failed_count == 3
- assert task.flow_run_task_type == "runFlow"
- assert task.flow_id == "ab1231eb-b8ca-461e-a131-83f3c2b6a673"
- assert task.flow_name == "flow-name"
-
-
-def test_parse_linked_task_step(server: TSC.Server) -> None:
- xml = fromstring(GET_LINKED_TASKS.read_bytes())
- steps = LinkedTaskStepItem.from_task_xml(xml, server.namespace)
- assert 1 == len(steps)
- step = steps[0]
- assert step.id == "f554a4df-bb6f-4294-94ee-9a709ef9bda0"
- assert step.stop_downstream_on_failure
- assert step.step_number == 1
- assert 1 == len(step.task_details)
- task = step.task_details[0]
- assert task.flow_run_id == "e3d1fc25-5644-4e32-af35-58dcbd1dbd73"
- assert task.flow_run_priority == 1
- assert task.flow_run_consecutive_failed_count == 3
- assert task.flow_run_task_type == "runFlow"
- assert task.flow_id == "ab1231eb-b8ca-461e-a131-83f3c2b6a673"
- assert task.flow_name == "flow-name"
-
-
-def test_parse_linked_task(server: TSC.Server) -> None:
- tasks = LinkedTaskItem.from_response(GET_LINKED_TASKS.read_bytes(), server.namespace)
- assert 1 == len(tasks)
- task = tasks[0]
- assert task.id == "1b8211dc-51a8-45ce-a831-b5921708e03e"
- assert task.num_steps == 1
- assert task.schedule is not None
- assert task.schedule.id == "be077332-d01d-481b-b2f3-917e463d4dca"
-
-
-def test_get_linked_tasks(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.get(server.linked_tasks.baseurl, text=GET_LINKED_TASKS.read_text())
- tasks, pagination_item = server.linked_tasks.get()
-
- assert 1 == len(tasks)
- task = tasks[0]
- assert task.id == "1b8211dc-51a8-45ce-a831-b5921708e03e"
- assert task.num_steps == 1
- assert task.schedule is not None
- assert task.schedule.id == "be077332-d01d-481b-b2f3-917e463d4dca"
-
-
-def test_get_by_id_str_linked_task(server: TSC.Server) -> None:
- id_ = "1b8211dc-51a8-45ce-a831-b5921708e03e"
-
- with requests_mock.mock() as m:
- m.get(f"{server.linked_tasks.baseurl}/{id_}", text=GET_LINKED_TASKS.read_text())
- task = server.linked_tasks.get_by_id(id_)
-
- assert task.id == "1b8211dc-51a8-45ce-a831-b5921708e03e"
- assert task.num_steps == 1
- assert task.schedule is not None
- assert task.schedule.id == "be077332-d01d-481b-b2f3-917e463d4dca"
-
-
-def test_get_by_id_obj_linked_task(server: TSC.Server) -> None:
- id_ = "1b8211dc-51a8-45ce-a831-b5921708e03e"
- in_task = LinkedTaskItem()
- in_task.id = id_
-
- with requests_mock.mock() as m:
- m.get(f"{server.linked_tasks.baseurl}/{id_}", text=GET_LINKED_TASKS.read_text())
- task = server.linked_tasks.get_by_id(in_task)
-
- assert task.id == "1b8211dc-51a8-45ce-a831-b5921708e03e"
- assert task.num_steps == 1
- assert task.schedule is not None
- assert task.schedule.id == "be077332-d01d-481b-b2f3-917e463d4dca"
-
-
-def test_run_now_str_linked_task(server: TSC.Server) -> None:
- id_ = "1b8211dc-51a8-45ce-a831-b5921708e03e"
-
- with requests_mock.mock() as m:
- m.post(f"{server.linked_tasks.baseurl}/{id_}/runNow", text=RUN_LINKED_TASK_NOW.read_text())
- job = server.linked_tasks.run_now(id_)
-
- assert job.id == "269a1e5a-1220-4a13-ac01-704982693dd8"
- assert job.status == "InProgress"
- assert job.created_at == parse_datetime("2022-02-15T00:22:22Z")
- assert job.linked_task_id == id_
-
-
-def test_run_now_obj_linked_task(server: TSC.Server) -> None:
- id_ = "1b8211dc-51a8-45ce-a831-b5921708e03e"
- in_task = LinkedTaskItem()
- in_task.id = id_
-
- with requests_mock.mock() as m:
- m.post(f"{server.linked_tasks.baseurl}/{id_}/runNow", text=RUN_LINKED_TASK_NOW.read_text())
- job = server.linked_tasks.run_now(in_task)
-
- assert job.id == "269a1e5a-1220-4a13-ac01-704982693dd8"
- assert job.status == "InProgress"
- assert job.created_at == parse_datetime("2022-02-15T00:22:22Z")
- assert job.linked_task_id == id_
diff --git a/test/test_metadata.py b/test/test_metadata.py
deleted file mode 100644
index 8b8b25151..000000000
--- a/test/test_metadata.py
+++ /dev/null
@@ -1,109 +0,0 @@
-import json
-from pathlib import Path
-
-import pytest
-import requests_mock
-
-import tableauserverclient as TSC
-from tableauserverclient.server.endpoint.exceptions import GraphQLError
-
-TEST_ASSET_DIR = Path(__file__).parent / "assets"
-
-METADATA_QUERY_SUCCESS = TEST_ASSET_DIR / "metadata_query_success.json"
-METADATA_QUERY_ERROR = TEST_ASSET_DIR / "metadata_query_error.json"
-EXPECTED_PAGED_DICT = TEST_ASSET_DIR / "metadata_query_expected_dict.dict"
-
-METADATA_PAGE_1 = TEST_ASSET_DIR / "metadata_paged_1.json"
-METADATA_PAGE_2 = TEST_ASSET_DIR / "metadata_paged_2.json"
-METADATA_PAGE_3 = TEST_ASSET_DIR / "metadata_paged_3.json"
-
-EXPECTED_DICT = {
- "publishedDatasources": [
- {"id": "01cf92b2-2d17-b656-fc48-5c25ef6d5352", "name": "Batters (TestV1)"},
- {"id": "020ae1cd-c356-f1ad-a846-b0094850d22a", "name": "SharePoint_List_sharepoint2010.test.tsi.lan"},
- {"id": "061493a0-c3b2-6f39-d08c-bc3f842b44af", "name": "Batters_mongodb"},
- {"id": "089fe515-ad2f-89bc-94bd-69f55f69a9c2", "name": "Sample - Superstore"},
- ]
-}
-
-EXPECTED_DICT_ERROR = [{"message": "Reached time limit of PT5S for query execution.", "path": None, "extensions": None}]
-
-
-@pytest.fixture(scope="function")
-def server():
- """Fixture to create a TSC.Server instance for testing."""
- server = TSC.Server("http://test", False)
-
- # Fake signin
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
- server.version = "3.5"
-
- return server
-
-
-def test_metadata_query(server: TSC.Server) -> None:
- with open(METADATA_QUERY_SUCCESS, "rb") as f:
- response_json = json.loads(f.read().decode())
- with requests_mock.mock() as m:
- m.post(server.metadata.baseurl, json=response_json)
- actual = server.metadata.query("fake query")
-
- datasources = actual["data"]
-
- assert EXPECTED_DICT == datasources
-
-
-def test_paged_metadata_query(server: TSC.Server) -> None:
- with open(EXPECTED_PAGED_DICT, "rb") as f:
- expected = eval(f.read())
-
- # prepare the 3 pages of results
- with open(METADATA_PAGE_1, "rb") as f:
- result_1 = f.read().decode()
- with open(METADATA_PAGE_2, "rb") as f:
- result_2 = f.read().decode()
- with open(METADATA_PAGE_3, "rb") as f:
- result_3 = f.read().decode()
-
- with requests_mock.mock() as m:
- m.post(
- server.metadata.baseurl,
- [
- {"text": result_1, "status_code": 200},
- {"text": result_2, "status_code": 200},
- {"text": result_3, "status_code": 200},
- ],
- )
-
- # validation checks for endCursor and hasNextPage,
- # but the query text doesn't matter for the test
- actual = server.metadata.paginated_query(
- "fake query endCursor hasNextPage", variables={"first": 1, "afterToken": None}
- )
-
- assert expected == actual
-
-
-def test_metadata_query_ignore_error(server: TSC.Server) -> None:
- with open(METADATA_QUERY_ERROR, "rb") as f:
- response_json = json.loads(f.read().decode())
- with requests_mock.mock() as m:
- m.post(server.metadata.baseurl, json=response_json)
- actual = server.metadata.query("fake query")
- datasources = actual["data"]
-
- assert actual.get("errors", None) is not None
- assert EXPECTED_DICT_ERROR == actual["errors"]
- assert EXPECTED_DICT == datasources
-
-
-def test_metadata_query_abort_on_error(server: TSC.Server) -> None:
- with open(METADATA_QUERY_ERROR, "rb") as f:
- response_json = json.loads(f.read().decode())
- with requests_mock.mock() as m:
- m.post(server.metadata.baseurl, json=response_json)
-
- with pytest.raises(GraphQLError) as e:
- server.metadata.query("fake query", abort_on_error=True)
- assert e.error == EXPECTED_DICT_ERROR # type: ignore[attr-defined]
diff --git a/test/test_metrics.py b/test/test_metrics.py
deleted file mode 100644
index fdb21f8f0..000000000
--- a/test/test_metrics.py
+++ /dev/null
@@ -1,111 +0,0 @@
-import requests_mock
-from pathlib import Path
-
-import pytest
-
-import tableauserverclient as TSC
-from tableauserverclient.datetime_helpers import format_datetime
-
-assets = Path(__file__).parent / "assets"
-METRICS_GET = assets / "metrics_get.xml"
-METRICS_GET_BY_ID = assets / "metrics_get_by_id.xml"
-METRICS_UPDATE = assets / "metrics_update.xml"
-
-
-@pytest.fixture(scope="function")
-def server() -> TSC.Server:
- """Fixture to create a TSC.Server instance for testing."""
- server = TSC.Server("http://test", False)
-
- # Fake signin
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
- server.version = "3.9"
-
- return server
-
-
-def test_metrics_get(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.get(server.metrics.baseurl, text=METRICS_GET.read_text())
- all_metrics, pagination_item = server.metrics.get()
-
- assert len(all_metrics) == 2
- assert pagination_item.total_available == 27
- assert all_metrics[0].id == "6561daa3-20e8-407f-ba09-709b178c0b4a"
- assert all_metrics[0].name == "Example metric"
- assert all_metrics[0].description == "Description of my metric."
- assert all_metrics[0].webpage_url == "https://test/#/site/site-name/metrics/3"
- assert format_datetime(all_metrics[0].created_at) == "2020-01-02T01:02:03Z"
- assert format_datetime(all_metrics[0].updated_at) == "2020-01-02T01:02:03Z"
- assert all_metrics[0].suspended
- assert all_metrics[0].project_id == "32e79edb-6cfd-47dc-ad79-e8ec2fbb1d33"
- assert all_metrics[0].project_name == "Default"
- assert all_metrics[0].owner_id == "32e79edb-6cfd-47dc-ad79-e8ec2fbb1d33"
- assert all_metrics[0].view_id == "29dae0cd-1862-4a20-a638-e2c2dfa682d4"
- assert len(all_metrics[0].tags) == 0
-
- assert all_metrics[1].id == "721760d9-0aa4-4029-87ae-371c956cea07"
- assert all_metrics[1].name == "Another Example metric"
- assert all_metrics[1].description == "Description of another metric."
- assert all_metrics[1].webpage_url == "https://test/#/site/site-name/metrics/4"
- assert format_datetime(all_metrics[1].created_at) == "2020-01-03T01:02:03Z"
- assert format_datetime(all_metrics[1].updated_at) == "2020-01-04T01:02:03Z"
- assert all_metrics[1].suspended is False
- assert all_metrics[1].project_id == "486e0de0-2258-45bd-99cf-b62013e19f4e"
- assert all_metrics[1].project_name == "Assets"
- assert all_metrics[1].owner_id == "1bbbc2b9-847d-443c-9a1f-dbcf112b8814"
- assert all_metrics[1].view_id == "7dbfdb63-a6ca-4723-93ee-4fefc71992d3"
- assert len(all_metrics[1].tags) == 2
- assert "Test" in all_metrics[1].tags
- assert "Asset" in all_metrics[1].tags
-
-
-def test_metrics_get_by_id(server: TSC.Server) -> None:
- luid = "6561daa3-20e8-407f-ba09-709b178c0b4a"
- with requests_mock.mock() as m:
- m.get(f"{server.metrics.baseurl}/{luid}", text=METRICS_GET_BY_ID.read_text())
- metric = server.metrics.get_by_id(luid)
-
- assert metric.id == "6561daa3-20e8-407f-ba09-709b178c0b4a"
- assert metric.name == "Example metric"
- assert metric.description == "Description of my metric."
- assert metric.webpage_url == "https://test/#/site/site-name/metrics/3"
- assert format_datetime(metric.created_at) == "2020-01-02T01:02:03Z"
- assert format_datetime(metric.updated_at) == "2020-01-02T01:02:03Z"
- assert metric.suspended
- assert metric.project_id == "32e79edb-6cfd-47dc-ad79-e8ec2fbb1d33"
- assert metric.project_name == "Default"
- assert metric.owner_id == "32e79edb-6cfd-47dc-ad79-e8ec2fbb1d33"
- assert metric.view_id == "29dae0cd-1862-4a20-a638-e2c2dfa682d4"
- assert len(metric.tags) == 0
-
-
-def test_metrics_delete(server: TSC.Server) -> None:
- luid = "6561daa3-20e8-407f-ba09-709b178c0b4a"
- with requests_mock.mock() as m:
- m.delete(f"{server.metrics.baseurl}/{luid}")
- server.metrics.delete(luid)
-
-
-def test_metrics_update(server: TSC.Server) -> None:
- luid = "6561daa3-20e8-407f-ba09-709b178c0b4a"
- metric = TSC.MetricItem()
- metric._id = luid
-
- with requests_mock.mock() as m:
- m.put(f"{server.metrics.baseurl}/{luid}", text=METRICS_UPDATE.read_text())
- metric = server.metrics.update(metric)
-
- assert metric.id == "6561daa3-20e8-407f-ba09-709b178c0b4a"
- assert metric.name == "Example metric"
- assert metric.description == "Description of my metric."
- assert metric.webpage_url == "https://test/#/site/site-name/metrics/3"
- assert format_datetime(metric.created_at) == "2020-01-02T01:02:03Z"
- assert format_datetime(metric.updated_at) == "2020-01-02T01:02:03Z"
- assert metric.suspended
- assert metric.project_id == "32e79edb-6cfd-47dc-ad79-e8ec2fbb1d33"
- assert metric.project_name == "Default"
- assert metric.owner_id == "32e79edb-6cfd-47dc-ad79-e8ec2fbb1d33"
- assert metric.view_id == "29dae0cd-1862-4a20-a638-e2c2dfa682d4"
- assert len(metric.tags) == 0
diff --git a/test/test_oidc.py b/test/test_oidc.py
deleted file mode 100644
index 476d902a1..000000000
--- a/test/test_oidc.py
+++ /dev/null
@@ -1,159 +0,0 @@
-import requests_mock
-from pathlib import Path
-
-import pytest
-
-import tableauserverclient as TSC
-
-assets = Path(__file__).parent / "assets"
-OIDC_GET = assets / "oidc_get.xml"
-OIDC_GET_BY_ID = assets / "oidc_get_by_id.xml"
-OIDC_UPDATE = assets / "oidc_update.xml"
-OIDC_CREATE = assets / "oidc_create.xml"
-
-
-@pytest.fixture(scope="function")
-def server():
- """Fixture to create a TSC.Server instance for testing."""
- server = TSC.Server("http://test", False)
-
- # Fake signin
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
- server.version = "3.24"
-
- return server
-
-
-def test_oidc_get_by_id(server: TSC.Server) -> None:
- luid = "6561daa3-20e8-407f-ba09-709b178c0b4a"
- with requests_mock.mock() as m:
- m.get(f"{server.oidc.baseurl}/{luid}", text=OIDC_GET.read_text())
- oidc = server.oidc.get_by_id(luid)
-
- assert oidc.enabled is True
- assert (
- oidc.test_login_url
- == "https://sso.online.tableau.com/public/testLogin?alias=8a04d825-e5d4-408f-bbc2-1042b8bb4818&authSetting=OIDC&idpConfigurationId=78c985b4-5494-4436-bcee-f595e287ba4a"
- )
- assert oidc.known_provider_alias == "Google"
- assert oidc.allow_embedded_authentication is False
- assert oidc.use_full_name is False
- assert oidc.idp_configuration_name == "GoogleOIDC"
- assert oidc.idp_configuration_id == "78c985b4-5494-4436-bcee-f595e287ba4a"
- assert oidc.client_id == "ICcGeDt3XHwzZ1D0nCZt"
- assert oidc.client_secret == "omit"
- assert oidc.authorization_endpoint == "https://myidp.com/oauth2/v1/authorize"
- assert oidc.token_endpoint == "https://myidp.com/oauth2/v1/token"
- assert oidc.userinfo_endpoint == "https://myidp.com/oauth2/v1/userinfo"
- assert oidc.jwks_uri == "https://myidp.com/oauth2/v1/keys"
- assert oidc.end_session_endpoint == "https://myidp.com/oauth2/v1/logout"
- assert oidc.custom_scope == "openid, email, profile"
- assert oidc.prompt == "login,consent"
- assert oidc.client_authentication == "client_secret_basic"
- assert oidc.essential_acr_values == "phr"
- assert oidc.email_mapping == "email"
- assert oidc.first_name_mapping == "given_name"
- assert oidc.last_name_mapping == "family_name"
- assert oidc.full_name_mapping == "name"
-
-
-def test_oidc_delete(server: TSC.Server) -> None:
- luid = "6561daa3-20e8-407f-ba09-709b178c0b4a"
- with requests_mock.mock() as m:
- m.put(f"{server.baseurl}/sites/{server.site_id}/disable-site-oidc-configuration")
- server.oidc.delete_configuration(luid)
- history = m.request_history[0]
-
- assert "idpconfigurationid" in history.qs
- assert history.qs["idpconfigurationid"][0] == luid
-
-
-def test_oidc_update(server: TSC.Server) -> None:
- luid = "6561daa3-20e8-407f-ba09-709b178c0b4a"
- oidc = TSC.SiteOIDCConfiguration()
- oidc.idp_configuration_id = luid
-
- # Only include the required fields for updates
- oidc.enabled = True
- oidc.idp_configuration_name = "GoogleOIDC"
- oidc.client_id = "ICcGeDt3XHwzZ1D0nCZt"
- oidc.client_secret = "omit"
- oidc.authorization_endpoint = "https://myidp.com/oauth2/v1/authorize"
- oidc.token_endpoint = "https://myidp.com/oauth2/v1/token"
- oidc.userinfo_endpoint = "https://myidp.com/oauth2/v1/userinfo"
- oidc.jwks_uri = "https://myidp.com/oauth2/v1/keys"
-
- with requests_mock.mock() as m:
- m.put(f"{server.oidc.baseurl}/{luid}", text=OIDC_UPDATE.read_text())
- oidc = server.oidc.update(oidc)
-
- assert oidc.enabled is True
- assert (
- oidc.test_login_url
- == "https://sso.online.tableau.com/public/testLogin?alias=8a04d825-e5d4-408f-bbc2-1042b8bb4818&authSetting=OIDC&idpConfigurationId=78c985b4-5494-4436-bcee-f595e287ba4a"
- )
- assert oidc.known_provider_alias == "Google"
- assert oidc.allow_embedded_authentication is False
- assert oidc.use_full_name is False
- assert oidc.idp_configuration_name == "GoogleOIDC"
- assert oidc.idp_configuration_id == "78c985b4-5494-4436-bcee-f595e287ba4a"
- assert oidc.client_id == "ICcGeDt3XHwzZ1D0nCZt"
- assert oidc.client_secret == "omit"
- assert oidc.authorization_endpoint == "https://myidp.com/oauth2/v1/authorize"
- assert oidc.token_endpoint == "https://myidp.com/oauth2/v1/token"
- assert oidc.userinfo_endpoint == "https://myidp.com/oauth2/v1/userinfo"
- assert oidc.jwks_uri == "https://myidp.com/oauth2/v1/keys"
- assert oidc.end_session_endpoint == "https://myidp.com/oauth2/v1/logout"
- assert oidc.custom_scope == "openid, email, profile"
- assert oidc.prompt == "login,consent"
- assert oidc.client_authentication == "client_secret_basic"
- assert oidc.essential_acr_values == "phr"
- assert oidc.email_mapping == "email"
- assert oidc.first_name_mapping == "given_name"
- assert oidc.last_name_mapping == "family_name"
- assert oidc.full_name_mapping == "name"
-
-
-def test_oidc_create(server: TSC.Server) -> None:
- oidc = TSC.SiteOIDCConfiguration()
-
- # Only include the required fields for creation
- oidc.enabled = True
- oidc.idp_configuration_name = "GoogleOIDC"
- oidc.client_id = "ICcGeDt3XHwzZ1D0nCZt"
- oidc.client_secret = "omit"
- oidc.authorization_endpoint = "https://myidp.com/oauth2/v1/authorize"
- oidc.token_endpoint = "https://myidp.com/oauth2/v1/token"
- oidc.userinfo_endpoint = "https://myidp.com/oauth2/v1/userinfo"
- oidc.jwks_uri = "https://myidp.com/oauth2/v1/keys"
-
- with requests_mock.mock() as m:
- m.put(server.oidc.baseurl, text=OIDC_CREATE.read_text())
- oidc = server.oidc.create(oidc)
-
- assert oidc.enabled is True
- assert (
- oidc.test_login_url
- == "https://sso.online.tableau.com/public/testLogin?alias=8a04d825-e5d4-408f-bbc2-1042b8bb4818&authSetting=OIDC&idpConfigurationId=78c985b4-5494-4436-bcee-f595e287ba4a"
- )
- assert oidc.known_provider_alias == "Google"
- assert oidc.allow_embedded_authentication is False
- assert oidc.use_full_name is False
- assert oidc.idp_configuration_name == "GoogleOIDC"
- assert oidc.idp_configuration_id == "78c985b4-5494-4436-bcee-f595e287ba4a"
- assert oidc.client_id == "ICcGeDt3XHwzZ1D0nCZt"
- assert oidc.client_secret == "omit"
- assert oidc.authorization_endpoint == "https://myidp.com/oauth2/v1/authorize"
- assert oidc.token_endpoint == "https://myidp.com/oauth2/v1/token"
- assert oidc.userinfo_endpoint == "https://myidp.com/oauth2/v1/userinfo"
- assert oidc.jwks_uri == "https://myidp.com/oauth2/v1/keys"
- assert oidc.end_session_endpoint == "https://myidp.com/oauth2/v1/logout"
- assert oidc.custom_scope == "openid, email, profile"
- assert oidc.prompt == "login,consent"
- assert oidc.client_authentication == "client_secret_basic"
- assert oidc.essential_acr_values == "phr"
- assert oidc.email_mapping == "email"
- assert oidc.first_name_mapping == "given_name"
- assert oidc.last_name_mapping == "family_name"
- assert oidc.full_name_mapping == "name"
diff --git a/test/test_packaging.py b/test/test_packaging.py
deleted file mode 100644
index 3674a938b..000000000
--- a/test/test_packaging.py
+++ /dev/null
@@ -1,19 +0,0 @@
-import zipfile
-from pathlib import Path
-import pytest
-
-pytestmark = pytest.mark.packaging
-
-
-def _find_wheel():
- wheels = list(Path("dist").glob("tableauserverclient-*.whl"))
- if not wheels:
- pytest.skip("No wheel in dist/ -- run 'python -m build --wheel' first")
- return max(wheels, key=lambda p: p.stat().st_mtime)
-
-
-def test_wheel_only_tableauserverclient_at_root():
- with zipfile.ZipFile(_find_wheel()) as whl:
- top_dirs = {n.split("/")[0] for n in whl.namelist() if "/" in n}
- non_dist_info = {d for d in top_dirs if not d.endswith(".dist-info") and not d.endswith(".data")}
- assert non_dist_info == {"tableauserverclient"}, f"Unexpected top-level entries: {non_dist_info}"
diff --git a/test/test_pager.py b/test/test_pager.py
deleted file mode 100644
index 82b79016c..000000000
--- a/test/test_pager.py
+++ /dev/null
@@ -1,171 +0,0 @@
-import contextlib
-import os
-from pathlib import Path
-import xml.etree.ElementTree as ET
-
-import pytest
-import requests_mock
-
-import tableauserverclient as TSC
-from tableauserverclient.config import config
-
-TEST_ASSET_DIR = Path(__file__).parent / "assets"
-
-GET_VIEW_XML = TEST_ASSET_DIR / "view_get.xml"
-GET_XML_PAGE1 = TEST_ASSET_DIR / "workbook_get_page_1.xml"
-GET_XML_PAGE2 = TEST_ASSET_DIR / "workbook_get_page_2.xml"
-GET_XML_PAGE3 = TEST_ASSET_DIR / "workbook_get_page_3.xml"
-
-
-@pytest.fixture(scope="function")
-def server():
- """Fixture to create a TSC.Server instance for testing."""
- server = TSC.Server("http://test", False)
-
- # Fake signin
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
-
- return server
-
-
-@contextlib.contextmanager
-def set_env(**environ):
- old_environ = dict(os.environ)
- os.environ.update(environ)
- try:
- yield
- finally:
- os.environ.clear()
- os.environ.update(old_environ)
-
-
-def test_pager_with_no_options(server: TSC.Server) -> None:
- page_1 = GET_XML_PAGE1.read_text()
- page_2 = GET_XML_PAGE2.read_text()
- page_3 = GET_XML_PAGE3.read_text()
- with requests_mock.mock() as m:
- # Register Pager with default request options
- m.get(server.workbooks.baseurl, text=page_1)
-
- # Register Pager with some pages
- m.get(server.workbooks.baseurl + "?pageNumber=1&pageSize=1", text=page_1)
- m.get(server.workbooks.baseurl + "?pageNumber=2&pageSize=1", text=page_2)
- m.get(server.workbooks.baseurl + "?pageNumber=3&pageSize=1", text=page_3)
-
- # No options should get all 3
- workbooks = list(TSC.Pager(server.workbooks))
- assert len(workbooks) == 3
-
- # Let's check that workbook items aren't duplicates
- wb1, wb2, wb3 = workbooks
- assert wb1.name == "Page1Workbook"
- assert wb2.name == "Page2Workbook"
- assert wb3.name == "Page3Workbook"
-
-
-def test_pager_with_options(server: TSC.Server) -> None:
- page_1 = GET_XML_PAGE1.read_text()
- page_2 = GET_XML_PAGE2.read_text()
- page_3 = GET_XML_PAGE3.read_text()
- with requests_mock.mock() as m:
- # Register Pager with some pages
- m.get(server.workbooks.baseurl + "?pageNumber=1&pageSize=1", complete_qs=True, text=page_1)
- m.get(server.workbooks.baseurl + "?pageNumber=2&pageSize=1", complete_qs=True, text=page_2)
- m.get(server.workbooks.baseurl + "?pageNumber=3&pageSize=1", complete_qs=True, text=page_3)
- m.get(server.workbooks.baseurl + "?pageNumber=1&pageSize=3", complete_qs=True, text=page_1)
-
- # Starting on page 2 should get 2 out of 3
- opts = TSC.RequestOptions(2, 1)
- workbooks = list(TSC.Pager(server.workbooks, opts))
- assert len(workbooks) == 2
-
- # Check that the workbooks are the 2 we think they should be
- wb2, wb3 = workbooks
- assert wb2.name == "Page2Workbook"
- assert wb3.name == "Page3Workbook"
-
- # Starting on 1 with pagesize of 3 should get all 3
- opts = TSC.RequestOptions(1, 3)
- workbooks = list(TSC.Pager(server.workbooks, opts))
- assert len(workbooks) == 3
- wb1, wb2, wb3 = workbooks
- assert wb1.name == "Page1Workbook"
- assert wb2.name == "Page2Workbook"
- assert wb3.name == "Page3Workbook"
-
- # Starting on 3 with pagesize of 1 should get the last item
- opts = TSC.RequestOptions(3, 1)
- workbooks = list(TSC.Pager(server.workbooks, opts))
- assert len(workbooks) == 1
- # Should have the last workbook
- wb3 = workbooks.pop()
- assert wb3.name == "Page3Workbook"
-
-
-def test_pager_with_env_var(server: TSC.Server) -> None:
- with set_env(TSC_PAGE_SIZE="1000"):
- assert config.PAGE_SIZE == 1000
- loop = TSC.Pager(server.workbooks)
- assert loop._options.pagesize == 1000
-
-
-def test_queryset_with_env_var(server: TSC.Server) -> None:
- with set_env(TSC_PAGE_SIZE="1000"):
- assert config.PAGE_SIZE == 1000
- loop = server.workbooks.all()
- assert loop.request_options.pagesize == 1000
-
-
-def test_pager_view(server: TSC.Server) -> None:
- with open(GET_VIEW_XML, "rb") as f:
- view_xml = f.read().decode("utf-8")
- with requests_mock.mock() as m:
- m.get(server.views.baseurl, text=view_xml)
- for view in TSC.Pager(server.views):
- assert view.name is not None
-
-
-def test_queryset_no_matches(server: TSC.Server) -> None:
- elem = ET.Element("tsResponse", xmlns="http://tableau.com/api")
- ET.SubElement(elem, "pagination", totalAvailable="0")
- ET.SubElement(elem, "groups")
- xml = ET.tostring(elem).decode("utf-8")
- with requests_mock.mock() as m:
- m.get(server.groups.baseurl, text=xml)
- all_groups = server.groups.all()
- groups = list(all_groups)
- assert len(groups) == 0
-
-
-def test_queryset_400006_returns_cleanly() -> None:
- """Regression test for PEP 479: 400006 must not raise RuntimeError.
-
- Before the fix, QuerySet.__iter__ raised StopIteration which PEP 479
- converts to RuntimeError inside a generator. This test directly exercises
- the __iter__ method with a mock that raises 400006 on the second call.
- """
- from tableauserverclient.server.endpoint.exceptions import ServerResponseError
- from tableauserverclient.server.query import QuerySet
-
- calls = [0]
-
- class MockEndpoint:
- def get(self, req_options=None):
- calls[0] += 1
- assert calls[0] <= 10, f"get() called {calls[0]} times β infinite loop detected"
- if calls[0] >= 2:
- raise ServerResponseError("400006", "Bad Request", "Invalid page number", "http://test")
- item = TSC.ProjectItem(name="Test")
- item._id = "abc"
- pagination = TSC.PaginationItem()
- pagination._total_available = None # unknown size β triggers loop
- return [item], pagination
-
- qs: QuerySet[TSC.ProjectItem] = QuerySet(MockEndpoint()) # type: ignore[arg-type]
-
- # Use a generator expression to bypass list()'s __len__ optimization,
- # which would consume the first page before __iter__ starts.
- # Before the fix this raised: RuntimeError: generator raised StopIteration
- results = [x for x in qs]
- assert len(results) == 1
diff --git a/test/test_permissionsrule.py b/test/test_permissionsrule.py
deleted file mode 100644
index 3d016057e..000000000
--- a/test/test_permissionsrule.py
+++ /dev/null
@@ -1,104 +0,0 @@
-import tableauserverclient as TSC
-from tableauserverclient.models.reference_item import ResourceReference
-
-
-def test_and() -> None:
- grantee = ResourceReference("a", "user")
- rule1 = TSC.PermissionsRule(
- grantee,
- {
- TSC.Permission.Capability.ExportData: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.Delete: TSC.Permission.Mode.Deny,
- TSC.Permission.Capability.ViewComments: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.ExportXml: TSC.Permission.Mode.Deny,
- },
- )
- rule2 = TSC.PermissionsRule(
- grantee,
- {
- TSC.Permission.Capability.ExportData: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.Delete: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.ExportXml: TSC.Permission.Mode.Deny,
- },
- )
-
- composite = rule1 & rule2
-
- assert composite.capabilities.get(TSC.Permission.Capability.ExportData) == TSC.Permission.Mode.Allow
- assert composite.capabilities.get(TSC.Permission.Capability.Delete) == TSC.Permission.Mode.Deny
- assert composite.capabilities.get(TSC.Permission.Capability.ViewComments) == None
- assert composite.capabilities.get(TSC.Permission.Capability.ExportXml) == TSC.Permission.Mode.Deny
-
-
-def test_or() -> None:
- grantee = ResourceReference("a", "user")
- rule1 = TSC.PermissionsRule(
- grantee,
- {
- TSC.Permission.Capability.ExportData: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.Delete: TSC.Permission.Mode.Deny,
- TSC.Permission.Capability.ViewComments: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.ExportXml: TSC.Permission.Mode.Deny,
- },
- )
- rule2 = TSC.PermissionsRule(
- grantee,
- {
- TSC.Permission.Capability.ExportData: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.Delete: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.ExportXml: TSC.Permission.Mode.Deny,
- },
- )
-
- composite = rule1 | rule2
-
- assert composite.capabilities.get(TSC.Permission.Capability.ExportData) == TSC.Permission.Mode.Allow
- assert composite.capabilities.get(TSC.Permission.Capability.Delete) == TSC.Permission.Mode.Allow
- assert composite.capabilities.get(TSC.Permission.Capability.ViewComments) == TSC.Permission.Mode.Allow
- assert composite.capabilities.get(TSC.Permission.Capability.ExportXml) == TSC.Permission.Mode.Deny
-
-
-def test_eq_false() -> None:
- grantee = ResourceReference("a", "user")
- rule1 = TSC.PermissionsRule(
- grantee,
- {
- TSC.Permission.Capability.ExportData: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.Delete: TSC.Permission.Mode.Deny,
- TSC.Permission.Capability.ViewComments: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.ExportXml: TSC.Permission.Mode.Deny,
- },
- )
- rule2 = TSC.PermissionsRule(
- grantee,
- {
- TSC.Permission.Capability.ExportData: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.Delete: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.ExportXml: TSC.Permission.Mode.Deny,
- },
- )
-
- assert rule1 != rule2
-
-
-def test_eq_true() -> None:
- grantee = ResourceReference("a", "user")
- rule1 = TSC.PermissionsRule(
- grantee,
- {
- TSC.Permission.Capability.ExportData: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.Delete: TSC.Permission.Mode.Deny,
- TSC.Permission.Capability.ViewComments: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.ExportXml: TSC.Permission.Mode.Deny,
- },
- )
- rule2 = TSC.PermissionsRule(
- grantee,
- {
- TSC.Permission.Capability.ExportData: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.Delete: TSC.Permission.Mode.Deny,
- TSC.Permission.Capability.ViewComments: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.ExportXml: TSC.Permission.Mode.Deny,
- },
- )
- assert rule1 == rule2
diff --git a/test/test_project.py b/test/test_project.py
deleted file mode 100644
index eb33f6732..000000000
--- a/test/test_project.py
+++ /dev/null
@@ -1,466 +0,0 @@
-from pathlib import Path
-
-import pytest
-import requests_mock
-
-import tableauserverclient as TSC
-from tableauserverclient import GroupItem
-
-TEST_ASSET_DIR = Path(__file__).parent / "assets"
-
-GET_XML = TEST_ASSET_DIR / "project_get.xml"
-GET_XML_ALL_FIELDS = TEST_ASSET_DIR / "project_get_all_fields.xml"
-UPDATE_XML = TEST_ASSET_DIR / "project_update.xml"
-SET_CONTENT_PERMISSIONS_XML = TEST_ASSET_DIR / "project_content_permission.xml"
-CREATE_XML = TEST_ASSET_DIR / "project_create.xml"
-POPULATE_PERMISSIONS_XML = TEST_ASSET_DIR / "project_populate_permissions.xml"
-POPULATE_WORKBOOK_DEFAULT_PERMISSIONS_XML = TEST_ASSET_DIR / "project_populate_workbook_default_permissions.xml"
-UPDATE_DATASOURCE_DEFAULT_PERMISSIONS_XML = TEST_ASSET_DIR / "project_update_datasource_default_permissions.xml"
-POPULATE_VIRTUALCONNECTION_DEFAULT_PERMISSIONS_XML = (
- TEST_ASSET_DIR / "project_populate_virtualconnection_default_permissions.xml"
-)
-UPDATE_VIRTUALCONNECTION_DEFAULT_PERMISSIONS_XML = (
- TEST_ASSET_DIR / "project_update_virtualconnection_default_permissions.xml"
-)
-
-
-@pytest.fixture(scope="function")
-def server():
- """Fixture to create a TSC.Server instance for testing."""
- server = TSC.Server("http://test", False)
-
- # Fake signin
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
-
- return server
-
-
-def test_get(server: TSC.Server) -> None:
- response_xml = GET_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.projects.baseurl, text=response_xml)
- all_projects, pagination_item = server.projects.get()
-
- assert 3 == pagination_item.total_available
- assert "ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" == all_projects[0].id
- assert "default" == all_projects[0].name
- assert "The default project that was automatically created by Tableau." == all_projects[0].description
- assert "ManagedByOwner" == all_projects[0].content_permissions
- assert None == all_projects[0].parent_id
- assert "dd2239f6-ddf1-4107-981a-4cf94e415794" == all_projects[0].owner_id
-
- assert "1d0304cd-3796-429f-b815-7258370b9b74" == all_projects[1].id
- assert "Tableau" == all_projects[1].name
- assert "ManagedByOwner" == all_projects[1].content_permissions
- assert None == all_projects[1].parent_id
- assert "2a47bbf8-8900-4ebb-b0a4-2723bd7c46c3" == all_projects[1].owner_id
-
- assert "4cc52973-5e3a-4d1f-a4fb-5b5f73796edf" == all_projects[2].id
- assert "Tableau > Child 1" == all_projects[2].name
- assert "ManagedByOwner" == all_projects[2].content_permissions
- assert "1d0304cd-3796-429f-b815-7258370b9b74" == all_projects[2].parent_id
- assert "dd2239f6-ddf1-4107-981a-4cf94e415794" == all_projects[2].owner_id
-
-
-def test_get_before_signin(server: TSC.Server) -> None:
- server._auth_token = None
- with pytest.raises(TSC.NotSignedInError):
- server.projects.get()
-
-
-def test_delete(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.delete(server.projects.baseurl + "/ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", status_code=204)
- server.projects.delete("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760")
-
-
-def test_delete_missing_id(server: TSC.Server) -> None:
- with pytest.raises(ValueError):
- server.projects.delete("")
-
-
-def test_get_by_id(server: TSC.Server) -> None:
- response_xml = UPDATE_XML.read_text()
- with requests_mock.mock() as m:
- m.put(server.projects.baseurl + "/1d0304cd-3796-429f-b815-7258370b9b74", text=response_xml)
- project = server.projects.get_by_id("1d0304cd-3796-429f-b815-7258370b9b74")
- assert "1d0304cd-3796-429f-b815-7258370b9b74" == project.id
- assert "Test Project" == project.name
- assert "Project created for testing" == project.description
- assert "LockedToProject" == project.content_permissions
- assert "9a8f2265-70f3-4494-96c5-e5949d7a1120" == project.parent_id
- assert "dd2239f6-ddf1-4107-981a-4cf94e415794" == project.owner_id
- assert "LockedToProject" == project.content_permissions
-
-
-def test_get_by_id_missing_id(server: TSC.Server) -> None:
- with pytest.raises(ValueError):
- server.projects.get_by_id("")
-
-
-def test_update(server: TSC.Server) -> None:
- response_xml = UPDATE_XML.read_text()
- with requests_mock.mock() as m:
- m.put(server.projects.baseurl + "/1d0304cd-3796-429f-b815-7258370b9b74", text=response_xml)
- single_project = TSC.ProjectItem(
- name="Test Project",
- content_permissions="LockedToProject",
- description="Project created for testing",
- parent_id="9a8f2265-70f3-4494-96c5-e5949d7a1120",
- )
- single_project._id = "1d0304cd-3796-429f-b815-7258370b9b74"
- single_project.owner_id = "dd2239f6-ddf1-4107-981a-4cf94e415794"
- single_project = server.projects.update(single_project)
-
- assert "1d0304cd-3796-429f-b815-7258370b9b74" == single_project.id
- assert "Test Project" == single_project.name
- assert "Project created for testing" == single_project.description
- assert "LockedToProject" == single_project.content_permissions
- assert "9a8f2265-70f3-4494-96c5-e5949d7a1120" == single_project.parent_id
- assert "dd2239f6-ddf1-4107-981a-4cf94e415794" == single_project.owner_id
-
-
-def test_content_permission_locked_to_project_without_nested(server: TSC.Server) -> None:
- response_xml = SET_CONTENT_PERMISSIONS_XML.read_text()
- with requests_mock.mock() as m:
- m.put(server.projects.baseurl + "/cb3759e5-da4a-4ade-b916-7e2b4ea7ec86", text=response_xml)
- project_item = TSC.ProjectItem(
- name="Test Project Permissions",
- content_permissions="LockedToProjectWithoutNested",
- description="Project created for testing",
- parent_id="7687bc43-a543-42f3-b86f-80caed03a813",
- )
- project_item._id = "cb3759e5-da4a-4ade-b916-7e2b4ea7ec86"
- project_item = server.projects.update(project_item)
- assert "cb3759e5-da4a-4ade-b916-7e2b4ea7ec86" == project_item.id
- assert "Test Project Permissions" == project_item.name
- assert "Project created for testing" == project_item.description
- assert "LockedToProjectWithoutNested" == project_item.content_permissions
- assert "7687bc43-a543-42f3-b86f-80caed03a813" == project_item.parent_id
-
-
-def test_update_datasource_default_permission(server: TSC.Server) -> None:
- response_xml = UPDATE_DATASOURCE_DEFAULT_PERMISSIONS_XML.read_text()
- with requests_mock.mock() as m:
- m.put(
- server.projects.baseurl + "/b4065286-80f0-11ea-af1b-cb7191f48e45/default-permissions/datasources",
- text=response_xml,
- )
- project = TSC.ProjectItem("test-project")
- project._id = "b4065286-80f0-11ea-af1b-cb7191f48e45"
-
- group = TSC.GroupItem("test-group")
- group._id = "b4488bce-80f0-11ea-af1c-976d0c1dab39"
-
- capabilities = {TSC.Permission.Capability.ExportXml: TSC.Permission.Mode.Deny}
-
- rules = [TSC.PermissionsRule(grantee=GroupItem.as_reference(group._id), capabilities=capabilities)]
-
- new_rules = server.projects.update_datasource_default_permissions(project, rules)
-
- assert "b4488bce-80f0-11ea-af1c-976d0c1dab39" == new_rules[0].grantee.id
-
- updated_capabilities = new_rules[0].capabilities
- assert 4 == len(updated_capabilities)
- assert "Deny" == updated_capabilities["ExportXml"]
- assert "Allow" == updated_capabilities["Read"]
- assert "Allow" == updated_capabilities["Write"]
- assert "Allow" == updated_capabilities["Connect"]
-
-
-def test_update_missing_id(server: TSC.Server) -> None:
- single_project = TSC.ProjectItem("test")
- with pytest.raises(TSC.MissingRequiredFieldError):
- server.projects.update(single_project)
-
-
-def test_create(server: TSC.Server) -> None:
- response_xml = CREATE_XML.read_text()
- with requests_mock.mock() as m:
- m.post(server.projects.baseurl, text=response_xml)
- new_project = TSC.ProjectItem(name="Test Project", description="Project created for testing")
- new_project.content_permissions = "ManagedByOwner"
- new_project.parent_id = "9a8f2265-70f3-4494-96c5-e5949d7a1120"
- new_project = server.projects.create(new_project)
-
- assert "ccbea03f-77c4-4209-8774-f67bc59c3cef" == new_project.id
- assert "Test Project" == new_project.name
- assert "Project created for testing" == new_project.description
- assert "ManagedByOwner" == new_project.content_permissions
- assert "9a8f2265-70f3-4494-96c5-e5949d7a1120" == new_project.parent_id
-
-
-def test_create_missing_name() -> None:
- TSC.ProjectItem()
-
-
-def test_populate_permissions(server: TSC.Server) -> None:
- response_xml = POPULATE_PERMISSIONS_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.projects.baseurl + "/0448d2ed-590d-4fa0-b272-a2a8a24555b5/permissions", text=response_xml)
- single_project = TSC.ProjectItem("Project3")
- single_project._id = "0448d2ed-590d-4fa0-b272-a2a8a24555b5"
-
- server.projects.populate_permissions(single_project)
- permissions = single_project.permissions
-
- assert permissions[0].grantee.tag_name == "group"
- assert permissions[0].grantee.id == "c8f2773a-c83a-11e8-8c8f-33e6d787b506"
- assert permissions[0].capabilities == {
- TSC.Permission.Capability.Write: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.Read: TSC.Permission.Mode.Allow,
- }
-
-
-def test_populate_workbooks(server: TSC.Server) -> None:
- response_xml = POPULATE_WORKBOOK_DEFAULT_PERMISSIONS_XML.read_text()
- with requests_mock.mock() as m:
- m.get(
- server.projects.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/default-permissions/workbooks",
- text=response_xml,
- )
- single_project = TSC.ProjectItem("test", "1d0304cd-3796-429f-b815-7258370b9b74")
- single_project.owner_id = "dd2239f6-ddf1-4107-981a-4cf94e415794"
- single_project._id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb"
-
- server.projects.populate_workbook_default_permissions(single_project)
- permissions = single_project.default_workbook_permissions
-
- rule1 = permissions.pop()
-
- assert "c8f2773a-c83a-11e8-8c8f-33e6d787b506" == rule1.grantee.id
- assert "group" == rule1.grantee.tag_name
- assert rule1.capabilities == {
- TSC.Permission.Capability.Write: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.Read: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.Filter: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.ChangePermissions: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.WebAuthoring: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.ExportData: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.ExportXml: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.ExportImage: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.Delete: TSC.Permission.Mode.Deny,
- TSC.Permission.Capability.ShareView: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.ViewUnderlyingData: TSC.Permission.Mode.Deny,
- TSC.Permission.Capability.ViewComments: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.AddComment: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.ChangeHierarchy: TSC.Permission.Mode.Allow,
- }
-
-
-def test_delete_permission(server: TSC.Server) -> None:
- response_xml = POPULATE_PERMISSIONS_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.projects.baseurl + "/0448d2ed-590d-4fa0-b272-a2a8a24555b5/permissions", text=response_xml)
-
- single_group = TSC.GroupItem("Group1")
- single_group._id = "c8f2773a-c83a-11e8-8c8f-33e6d787b506"
-
- single_project = TSC.ProjectItem("Project3")
- single_project._id = "0448d2ed-590d-4fa0-b272-a2a8a24555b5"
-
- server.projects.populate_permissions(single_project)
- permissions = single_project.permissions
-
- capabilities = {}
-
- for permission in permissions:
- if permission.grantee.tag_name == "group":
- if permission.grantee.id == single_group._id:
- capabilities = permission.capabilities
-
- rules = TSC.PermissionsRule(grantee=GroupItem.as_reference(single_group._id), capabilities=capabilities)
-
- endpoint = f"{single_project._id}/permissions/groups/{single_group._id}"
- m.delete(f"{server.projects.baseurl}/{endpoint}/Read/Allow", status_code=204)
- m.delete(f"{server.projects.baseurl}/{endpoint}/Write/Allow", status_code=204)
- server.projects.delete_permission(item=single_project, rules=rules)
-
-
-def test_delete_workbook_default_permission(server: TSC.Server) -> None:
- response_xml = POPULATE_WORKBOOK_DEFAULT_PERMISSIONS_XML.read_text()
-
- with requests_mock.mock() as m:
- m.get(
- server.projects.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/default-permissions/workbooks",
- text=response_xml,
- )
-
- single_group = TSC.GroupItem("Group1")
- single_group._id = "c8f2773a-c83a-11e8-8c8f-33e6d787b506"
-
- single_project = TSC.ProjectItem("test", "1d0304cd-3796-429f-b815-7258370b9b74")
- single_project._owner_id = "dd2239f6-ddf1-4107-981a-4cf94e415794"
- single_project._id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb"
-
- server.projects.populate_workbook_default_permissions(single_project)
- permissions = single_project.default_workbook_permissions
-
- capabilities = {
- # View
- TSC.Permission.Capability.Read: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.ExportImage: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.ExportData: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.ViewComments: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.AddComment: TSC.Permission.Mode.Allow,
- # Interact/Edit
- TSC.Permission.Capability.Filter: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.ViewUnderlyingData: TSC.Permission.Mode.Deny,
- TSC.Permission.Capability.ShareView: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.WebAuthoring: TSC.Permission.Mode.Allow,
- # Edit
- TSC.Permission.Capability.Write: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.ExportXml: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.ChangeHierarchy: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.Delete: TSC.Permission.Mode.Deny,
- TSC.Permission.Capability.ChangePermissions: TSC.Permission.Mode.Allow,
- }
-
- rules = TSC.PermissionsRule(grantee=GroupItem.as_reference(single_group._id), capabilities=capabilities)
-
- endpoint = f"{single_project._id}/default-permissions/workbooks/groups/{single_group._id}"
- m.delete(f"{server.projects.baseurl}/{endpoint}/Read/Allow", status_code=204)
- m.delete(f"{server.projects.baseurl}/{endpoint}/ExportImage/Allow", status_code=204)
- m.delete(f"{server.projects.baseurl}/{endpoint}/ExportData/Allow", status_code=204)
- m.delete(f"{server.projects.baseurl}/{endpoint}/ViewComments/Allow", status_code=204)
- m.delete(f"{server.projects.baseurl}/{endpoint}/AddComment/Allow", status_code=204)
- m.delete(f"{server.projects.baseurl}/{endpoint}/Filter/Allow", status_code=204)
- m.delete(f"{server.projects.baseurl}/{endpoint}/ViewUnderlyingData/Deny", status_code=204)
- m.delete(f"{server.projects.baseurl}/{endpoint}/ShareView/Allow", status_code=204)
- m.delete(f"{server.projects.baseurl}/{endpoint}/WebAuthoring/Allow", status_code=204)
- m.delete(f"{server.projects.baseurl}/{endpoint}/Write/Allow", status_code=204)
- m.delete(f"{server.projects.baseurl}/{endpoint}/ExportXml/Allow", status_code=204)
- m.delete(f"{server.projects.baseurl}/{endpoint}/ChangeHierarchy/Allow", status_code=204)
- m.delete(f"{server.projects.baseurl}/{endpoint}/Delete/Deny", status_code=204)
- m.delete(f"{server.projects.baseurl}/{endpoint}/ChangePermissions/Allow", status_code=204)
- server.projects.delete_workbook_default_permissions(item=single_project, rule=rules)
-
-
-def test_populate_virtualconnection_default_permissions(server: TSC.Server) -> None:
- response_xml = POPULATE_VIRTUALCONNECTION_DEFAULT_PERMISSIONS_XML.read_text()
-
- server.version = "3.23"
- base_url = server.projects.baseurl
-
- with requests_mock.mock() as m:
- m.get(
- base_url + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/default-permissions/virtualConnections",
- text=response_xml,
- )
- project = TSC.ProjectItem("test", "1d0304cd-3796-429f-b815-7258370b9b74")
- project._id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb"
-
- server.projects.populate_virtualconnection_default_permissions(project)
- permissions = project.default_virtualconnection_permissions
-
- rule = permissions.pop()
-
- assert "c8f2773a-c83a-11e8-8c8f-33e6d787b506" == rule.grantee.id
- assert "group" == rule.grantee.tag_name
- assert rule.capabilities == {
- TSC.Permission.Capability.Read: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.Connect: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.ChangeHierarchy: TSC.Permission.Mode.Deny,
- TSC.Permission.Capability.Delete: TSC.Permission.Mode.Deny,
- TSC.Permission.Capability.ChangePermissions: TSC.Permission.Mode.Deny,
- }
-
-
-def test_update_virtualconnection_default_permissions(server: TSC.Server) -> None:
- response_xml = UPDATE_VIRTUALCONNECTION_DEFAULT_PERMISSIONS_XML.read_text()
-
- server.version = "3.23"
- base_url = server.projects.baseurl
-
- with requests_mock.mock() as m:
- m.put(
- base_url + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/default-permissions/virtualConnections",
- text=response_xml,
- )
- project = TSC.ProjectItem("test", "1d0304cd-3796-429f-b815-7258370b9b74")
- project._id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb"
-
- group = TSC.GroupItem("test-group")
- group._id = "c8f2773a-c83a-11e8-8c8f-33e6d787b506"
-
- capabilities = {
- TSC.Permission.Capability.ChangeHierarchy: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.Delete: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.Read: TSC.Permission.Mode.Deny,
- }
-
- assert group.id is not None
- rules = [TSC.PermissionsRule(GroupItem.as_reference(group.id), capabilities)]
- new_rules = server.projects.update_virtualconnection_default_permissions(project, rules)
-
- rule = new_rules.pop()
-
- assert group.id == rule.grantee.id
- assert "group" == rule.grantee.tag_name
- assert rule.capabilities == {
- TSC.Permission.Capability.ChangeHierarchy: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.Delete: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.Read: TSC.Permission.Mode.Deny,
- }
-
-
-def test_delete_virtualconnection_default_permimssions(server: TSC.Server) -> None:
- response_xml = POPULATE_VIRTUALCONNECTION_DEFAULT_PERMISSIONS_XML.read_text()
-
- server.version = "3.23"
- base_url = server.projects.baseurl
-
- with requests_mock.mock() as m:
- m.get(
- base_url + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/default-permissions/virtualConnections",
- text=response_xml,
- )
-
- project = TSC.ProjectItem("test", "1d0304cd-3796-429f-b815-7258370b9b74")
- project._id = "9dbd2263-16b5-46e1-9c43-a76bb8ab65fb"
-
- group = TSC.GroupItem("test-group")
- group._id = "c8f2773a-c83a-11e8-8c8f-33e6d787b506"
-
- server.projects.populate_virtualconnection_default_permissions(project)
- permissions = project.default_virtualconnection_permissions
-
- del_caps = {
- TSC.Permission.Capability.ChangeHierarchy: TSC.Permission.Mode.Deny,
- TSC.Permission.Capability.Connect: TSC.Permission.Mode.Allow,
- }
-
- assert group.id is not None
- rule = TSC.PermissionsRule(GroupItem.as_reference(group.id), del_caps)
-
- endpoint = f"{project.id}/default-permissions/virtualConnections/groups/{group.id}"
- m.delete(f"{base_url}/{endpoint}/ChangeHierarchy/Deny", status_code=204)
- m.delete(f"{base_url}/{endpoint}/Connect/Allow", status_code=204)
-
- server.projects.delete_virtualconnection_default_permissions(project, rule)
-
-
-def test_get_all_fields(server: TSC.Server) -> None:
- server.version = "3.23"
- base_url = server.projects.baseurl
- response_xml = GET_XML_ALL_FIELDS.read_text()
-
- ro = TSC.RequestOptions()
- ro.all_fields = True
-
- with requests_mock.mock() as m:
- m.get(f"{base_url}?fields=_all_", text=response_xml)
- all_projects, pagination_item = server.projects.get(req_options=ro)
-
- assert pagination_item.total_available == 3
- assert len(all_projects) == 1
- project: TSC.ProjectItem = all_projects[0]
- assert isinstance(project, TSC.ProjectItem)
- assert project.id == "ee8c6e70-43b6-11e6-af4f-f7b0d8e20760"
- assert project.name == "Samples"
- assert project.description == "This project includes automatically uploaded samples."
- assert project.top_level_project is True
- assert project.content_permissions == "ManagedByOwner"
- assert project.parent_id is None
- assert project.writeable is True
diff --git a/test/test_project_model.py b/test/test_project_model.py
deleted file mode 100644
index b51a218ec..000000000
--- a/test/test_project_model.py
+++ /dev/null
@@ -1,22 +0,0 @@
-import pytest
-
-import tableauserverclient as TSC
-
-
-def test_nullable_name():
- TSC.ProjectItem(None)
- TSC.ProjectItem("")
- project = TSC.ProjectItem("proj")
- project.name = None
-
-
-def test_invalid_content_permissions():
- project = TSC.ProjectItem("proj")
- with pytest.raises(ValueError):
- project.content_permissions = "Hello"
-
-
-def test_parent_id():
- project = TSC.ProjectItem("proj")
- project.parent_id = "foo"
- assert project.parent_id == "foo"
diff --git a/test/test_regression_tests.py b/test/test_regression_tests.py
deleted file mode 100644
index 21fbf3848..000000000
--- a/test/test_regression_tests.py
+++ /dev/null
@@ -1,84 +0,0 @@
-from unittest import mock
-
-import tableauserverclient.server.request_factory as factory
-from tableauserverclient.helpers.strings import redact_xml
-from tableauserverclient.filesys_helpers import to_filename, make_download_path
-
-
-def test_empty_request_works():
- result = factory.EmptyRequest().empty_req()
- assert b" " == result
-
-
-def test_to_filename():
- invalid = [
- "23brhafbjrjhkbbea.txt",
- "a_b_C.txt",
- "windows space.txt",
- "abc#def.txt",
- "t@bL3A()",
- ]
-
- valid = [
- "23brhafbjrjhkbbea.txt",
- "a_b_C.txt",
- "windows space.txt",
- "abcdef.txt",
- "tbL3A",
- ]
-
- assert all([(to_filename(i) == v) for i, v in zip(invalid, valid)])
-
-
-def test_make_download_path():
- no_file_path = (None, "file.ext")
- has_file_path_folder = ("/root/folder/", "file.ext")
- has_file_path_file = ("outx", "file.ext")
-
- assert "file.ext" == make_download_path(*no_file_path)
- assert "outx.ext" == make_download_path(*has_file_path_file)
-
- with mock.patch("os.path.isdir") as mocked_isdir:
- mocked_isdir.return_value = True
- assert "/root/folder/file.ext" == make_download_path(*has_file_path_folder)
-
-
-def test_redact_password_string():
- redacted = redact_xml(
- "this is password: my_super_secret_passphrase_which_nobody_should_ever_see password: value "
- )
- assert redacted.find("value") == -1
- assert redacted.find("secret") == -1
- assert redacted.find("ever_see") == -1
- assert redacted.find("my_super_secret_passphrase_which_nobody_should_ever_see") == -1
-
-
-def test_redact_password_bytes():
- redacted = redact_xml(
- b" "
- )
- assert redacted.find(b"value") == -1
- assert redacted.find(b"secret") == -1
-
-
-def test_redact_password_with_special_char():
- redacted = redact_xml(
- " "
- )
- assert redacted.find("my_s per_secre>_passphrase_which_nobody_should_ever_see with password: value") == -1
-
-
-def test_redact_password_not_xml():
- redacted = redact_xml(
- " "
- )
- assert redacted.find("my_s per_secre>_passphrase_which_nobody_should_ever_see") == -1
-
-
-def test_redact_password_really_not_xml():
- redacted = redact_xml(
- "value='this is a nondescript text line which is public' password='my_s per_secre>_passphrase_which_nobody_should_ever_see with password: value and then a cookie "
- )
- assert redacted.find("my_s per_secre>_passphrase_which_nobody_should_ever_see") == -1
- assert redacted.find("passphrase") == -1, redacted
- assert redacted.find("cookie") == -1, redacted
diff --git a/test/test_request_option.py b/test/test_request_option.py
deleted file mode 100644
index 3709e18d3..000000000
--- a/test/test_request_option.py
+++ /dev/null
@@ -1,478 +0,0 @@
-from pathlib import Path
-from urllib.parse import parse_qs
-
-import pytest
-import requests_mock
-
-import tableauserverclient as TSC
-
-TEST_ASSET_DIR = Path(__file__).parent / "assets"
-
-PAGINATION_XML = TEST_ASSET_DIR / "request_option_pagination.xml"
-PAGE_NUMBER_XML = TEST_ASSET_DIR / "request_option_page_number.xml"
-PAGE_SIZE_XML = TEST_ASSET_DIR / "request_option_page_size.xml"
-FILTER_EQUALS = TEST_ASSET_DIR / "request_option_filter_equals.xml"
-FILTER_NAME_IN = TEST_ASSET_DIR / "request_option_filter_name_in.xml"
-FILTER_TAGS_IN = TEST_ASSET_DIR / "request_option_filter_tags_in.xml"
-FILTER_MULTIPLE = TEST_ASSET_DIR / "request_option_filter_tags_in.xml"
-SLICING_QUERYSET = TEST_ASSET_DIR / "request_option_slicing_queryset.xml"
-SLICING_QUERYSET_PAGE_1 = TEST_ASSET_DIR / "queryset_slicing_page_1.xml"
-SLICING_QUERYSET_PAGE_2 = TEST_ASSET_DIR / "queryset_slicing_page_2.xml"
-
-
-@pytest.fixture(scope="function")
-def server():
- """Fixture to create a TSC.Server instance for testing."""
- server = TSC.Server("http://test", False)
-
- # Fake signin
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
-
- return server
-
-
-def test_pagination(server: TSC.Server) -> None:
- response_xml = PAGINATION_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.views.baseurl + "?pageNumber=1&pageSize=10", text=response_xml)
- req_option = TSC.RequestOptions().page_size(10)
- all_views, pagination_item = server.views.get(req_option)
-
- assert 1 == pagination_item.page_number
- assert 10 == pagination_item.page_size
- assert 33 == pagination_item.total_available
- assert 10 == len(all_views)
-
-
-def test_page_number(server: TSC.Server) -> None:
- response_xml = PAGE_NUMBER_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.views.baseurl + "?pageNumber=3", text=response_xml)
- req_option = TSC.RequestOptions().page_number(3)
- all_views, pagination_item = server.views.get(req_option)
-
- assert 3 == pagination_item.page_number
- assert 100 == pagination_item.page_size
- assert 210 == pagination_item.total_available
- assert 10 == len(all_views)
-
-
-def test_page_size(server: TSC.Server) -> None:
- response_xml = PAGE_SIZE_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.views.baseurl + "?pageSize=5", text=response_xml)
- req_option = TSC.RequestOptions().page_size(5)
- all_views, pagination_item = server.views.get(req_option)
-
- assert 1 == pagination_item.page_number
- assert 5 == pagination_item.page_size
- assert 33 == pagination_item.total_available
- assert 5 == len(all_views)
-
-
-def test_filter_equals(server: TSC.Server) -> None:
- response_xml = FILTER_EQUALS.read_text()
- with requests_mock.mock() as m:
- m.get(server.workbooks.baseurl + "?filter=name:eq:RESTAPISample", text=response_xml)
- req_option = TSC.RequestOptions()
- req_option.filter.add(
- TSC.Filter(TSC.RequestOptions.Field.Name, TSC.RequestOptions.Operator.Equals, "RESTAPISample")
- )
- matching_workbooks, pagination_item = server.workbooks.get(req_option)
-
- assert 2 == pagination_item.total_available
- assert "RESTAPISample" == matching_workbooks[0].name
- assert "RESTAPISample" == matching_workbooks[1].name
-
-
-def test_filter_equals_shorthand(server: TSC.Server) -> None:
- response_xml = FILTER_EQUALS.read_text()
- with requests_mock.mock() as m:
- m.get(server.workbooks.baseurl + "?filter=name:eq:RESTAPISample", text=response_xml)
- matching_workbooks = server.workbooks.filter(name="RESTAPISample").order_by("name")
-
- assert 2 == matching_workbooks.total_available
- assert "RESTAPISample" == matching_workbooks[0].name
- assert "RESTAPISample" == matching_workbooks[1].name
-
-
-def test_filter_tags_in(server: TSC.Server) -> None:
- response_xml = FILTER_TAGS_IN.read_text()
- with requests_mock.mock() as m:
- m.get(server.workbooks.baseurl + "?filter=tags:in:[sample,safari,weather]", text=response_xml)
- req_option = TSC.RequestOptions()
- req_option.filter.add(
- TSC.Filter(TSC.RequestOptions.Field.Tags, TSC.RequestOptions.Operator.In, ["sample", "safari", "weather"])
- )
- matching_workbooks, pagination_item = server.workbooks.get(req_option)
-
- assert 3 == pagination_item.total_available
- assert {"weather"} == matching_workbooks[0].tags
- assert {"safari"} == matching_workbooks[1].tags
- assert {"sample"} == matching_workbooks[2].tags
-
-
-# check if filtered projects with spaces & special characters
-# get correctly returned
-def test_filter_name_in(server: TSC.Server) -> None:
- response_xml = FILTER_NAME_IN.read_text("utf8")
- with requests_mock.mock() as m:
- m.get(
- server.projects.baseurl + "?filter=name%3Ain%3A%5Bdefault%2CSalesforce+Sales+Proje%C5%9Bt%5D",
- text=response_xml,
- )
- req_option = TSC.RequestOptions()
- req_option.filter.add(
- TSC.Filter(
- TSC.RequestOptions.Field.Name,
- TSC.RequestOptions.Operator.In,
- ["default", "Salesforce Sales ProjeΕt"],
- )
- )
- matching_projects, pagination_item = server.projects.get(req_option)
-
- assert 2 == pagination_item.total_available
- assert "default" == matching_projects[0].name
- assert "Salesforce Sales ProjeΕt" == matching_projects[1].name
-
-
-def test_filter_tags_in_shorthand(server: TSC.Server) -> None:
- response_xml = FILTER_TAGS_IN.read_text()
- with requests_mock.mock() as m:
- m.get(server.workbooks.baseurl + "?filter=tags:in:[sample,safari,weather]", text=response_xml)
- matching_workbooks = server.workbooks.filter(tags__in=["sample", "safari", "weather"])
-
- assert 3 == matching_workbooks.total_available
- assert {"weather"} == matching_workbooks[0].tags
- assert {"safari"} == matching_workbooks[1].tags
- assert {"sample"} == matching_workbooks[2].tags
-
-
-def test_invalid_shorthand_option(server: TSC.Server) -> None:
- with pytest.raises(ValueError):
- server.workbooks.filter(nonexistant__in=["sample", "safari"])
-
-
-def test_multiple_filter_options(server: TSC.Server) -> None:
- response_xml = FILTER_MULTIPLE.read_text()
- # To ensure that this is deterministic, run this a few times
- with requests_mock.mock() as m:
- # Sometimes pep8 requires you to do things you might not otherwise do
- url = "".join(
- (
- server.workbooks.baseurl,
- "?pageNumber=1&pageSize=100&",
- "filter=name:eq:foo,tags:in:[sample,safari,weather]",
- )
- )
- m.get(url, text=response_xml)
- req_option = TSC.RequestOptions()
- req_option.filter.add(
- TSC.Filter(TSC.RequestOptions.Field.Tags, TSC.RequestOptions.Operator.In, ["sample", "safari", "weather"])
- )
- req_option.filter.add(TSC.Filter(TSC.RequestOptions.Field.Name, TSC.RequestOptions.Operator.Equals, "foo"))
- for _ in range(5):
- matching_workbooks, pagination_item = server.workbooks.get(req_option)
- assert 3 == pagination_item.total_available
-
-
-# Test req_options if url already has query params
-def test_double_query_params(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.get(requests_mock.ANY)
- url = server.workbooks.baseurl + "?queryParamExists=true"
- opts = TSC.RequestOptions()
-
- opts.filter.add(TSC.Filter(TSC.RequestOptions.Field.Tags, TSC.RequestOptions.Operator.In, ["stocks", "market"]))
- opts.sort.add(TSC.Sort(TSC.RequestOptions.Field.Name, TSC.RequestOptions.Direction.Asc))
-
- resp = server.workbooks.get_request(url, request_object=opts)
- query_string = parse_qs(resp.request.query)
- assert "queryparamexists" in query_string
- assert "true" in query_string["queryparamexists"]
- assert "filter" in query_string
- assert "tags:in:[stocks,market]" in query_string["filter"]
- assert "sort" in query_string
- assert "name:asc" in query_string["sort"]
-
-
-# Test req_options for versions below 3.7
-def test_filter_sort_legacy(server: TSC.Server) -> None:
- server.version = "3.6"
- with requests_mock.mock() as m:
- m.get(requests_mock.ANY)
- url = server.workbooks.baseurl + "?queryParamExists=true"
- opts = TSC.RequestOptions()
-
- opts.filter.add(TSC.Filter(TSC.RequestOptions.Field.Tags, TSC.RequestOptions.Operator.In, ["stocks", "market"]))
- opts.sort.add(TSC.Sort(TSC.RequestOptions.Field.Name, TSC.RequestOptions.Direction.Asc))
-
- resp = server.workbooks.get_request(url, request_object=opts)
- query_string = parse_qs(resp.request.query)
- assert "queryparamexists" in query_string
- assert "true" in query_string["queryparamexists"]
- assert "filter" in query_string
- assert "tags:in:[stocks,market]" in query_string["filter"]
- assert "sort" in query_string
- assert "name:asc" in query_string["sort"]
-
-
-def test_vf(server: TSC.Server) -> None:
- server.version = "3.10"
- with requests_mock.mock() as m:
- m.get(requests_mock.ANY)
- url = server.workbooks.baseurl + "/456/data"
- opts = TSC.PDFRequestOptions()
- opts.vf("name1#", "value1")
- opts.vf("name2$", "value2")
- opts.page_type = TSC.PDFRequestOptions.PageType.Tabloid
-
- resp = server.workbooks.get_request(url, request_object=opts)
- query_string = parse_qs(resp.request.query)
- assert "vf_name1#" in query_string
- assert "value1" in query_string["vf_name1#"]
- assert "vf_name2$" in query_string
- assert "value2" in query_string["vf_name2$"]
- assert "type" in query_string
- assert "tabloid" in query_string["type"]
-
-
-# Test req_options for versions below 3.7
-def test_vf_legacy(server: TSC.Server) -> None:
- server.version = "3.6"
- with requests_mock.mock() as m:
- m.get(requests_mock.ANY)
- url = server.workbooks.baseurl
- opts = TSC.PDFRequestOptions()
- opts.vf("name1@", "value1")
- opts.vf("name2$", "value2")
- opts.page_type = TSC.PDFRequestOptions.PageType.Tabloid
-
- resp = server.workbooks.get_request(url, request_object=opts)
- query_string = parse_qs(resp.request.query)
- assert "vf_name1@" in query_string
- assert "value1" in query_string["vf_name1@"]
- assert "vf_name2$" in query_string
- assert "value2" in query_string["vf_name2$"]
- assert "type" in query_string
- assert "tabloid" in query_string["type"]
-
-
-def test_all_fields(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.get(requests_mock.ANY)
- url = server.views.baseurl + "/456/data"
- opts = TSC.RequestOptions()
- opts.all_fields = True
-
- resp = server.users.get_request(url, request_object=opts)
- query_string = parse_qs(resp.request.query)
- assert "fields" in query_string
- assert ["_all_"] == query_string["fields"]
-
-
-def test_multiple_filter_options_shorthand(server: TSC.Server) -> None:
- response_xml = FILTER_MULTIPLE.read_text()
- # To ensure that this is deterministic, run this a few times
- with requests_mock.mock() as m:
- # Sometimes pep8 requires you to do things you might not otherwise do
- url = "".join(
- (
- server.workbooks.baseurl,
- "?pageNumber=1&pageSize=100&",
- "filter=name:eq:foo,tags:in:[sample,safari,weather]",
- )
- )
- m.get(url, text=response_xml)
-
- for _ in range(5):
- matching_workbooks = server.workbooks.filter(tags__in=["sample", "safari", "weather"], name="foo")
- assert 3 == matching_workbooks.total_available
-
-
-def test_slicing_queryset(server: TSC.Server) -> None:
- response_xml = SLICING_QUERYSET.read_text()
- with requests_mock.mock() as m:
- m.get(server.views.baseurl + "?pageNumber=1", text=response_xml)
- all_views = server.views.all()
-
- assert 10 == len(all_views[::])
- assert 5 == len(all_views[::2])
- assert 8 == len(all_views[2:])
- assert 2 == len(all_views[:2])
- assert 3 == len(all_views[2:5])
- assert 3 == len(all_views[-3:])
- assert 3 == len(all_views[-6:-3])
- assert 3 == len(all_views[3:6:-1])
- assert 3 == len(all_views[6:3:-1])
- assert 10 == len(all_views[::-1])
- assert all_views[3:6] == list(reversed(all_views[3:6:-1]))
-
- assert all_views[-3].id == "2df55de2-3a2d-4e34-b515-6d4e70b830e9"
-
- with pytest.raises(IndexError):
- all_views[100]
-
-
-def test_slicing_queryset_multi_page(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.get(server.views.baseurl + "?pageNumber=1", text=SLICING_QUERYSET_PAGE_1.read_text())
- m.get(server.views.baseurl + "?pageNumber=2", text=SLICING_QUERYSET_PAGE_2.read_text())
- sliced_views = server.views.all()[9:12]
-
- assert sliced_views[0].id == "2e6d6c81-da71-4b41-892c-ba80d4e7a6d0"
- assert sliced_views[1].id == "47ffcb8e-3f7a-4ecf-8ab3-605da9febe20"
- assert sliced_views[2].id == "6757fea8-0aa9-4160-a87c-9be27b1d1c8c"
-
-
-def test_queryset_filter_args_error(server: TSC.Server) -> None:
- with pytest.raises(RuntimeError):
- workbooks = server.workbooks.filter("argument")
-
-
-def test_filtering_parameters(server: TSC.Server) -> None:
- server.version = "3.6"
- with requests_mock.mock() as m:
- m.get(requests_mock.ANY)
- url = server.workbooks.baseurl + "/456/data"
- opts = TSC.PDFRequestOptions()
- opts.parameter("name1@", "value1")
- opts.parameter("name2$", "value2")
- opts.parameter("Parameters.name3", "value3")
- opts.parameter("vf_Parameters.name4", "value4")
- opts.page_type = TSC.PDFRequestOptions.PageType.Tabloid
-
- # While Tableau Server side IS case sensitive with the query string,
- # requiring the prefix to be "vf_Parameters", requests does not end
- # up preserving the case sensitivity with the Response.Request
- # object. It also shows up lowercased in the requests_mock request
- # history.
- resp = server.workbooks.get_request(url, request_object=opts)
- query_params = parse_qs(resp.request.query)
- assert "vf_parameters.name1@" in query_params
- assert "value1" in query_params["vf_parameters.name1@"]
- assert "vf_parameters.name2$" in query_params
- assert "value2" in query_params["vf_parameters.name2$"]
- assert "vf_parameters.name3" in query_params
- assert "value3" in query_params["vf_parameters.name3"]
- assert "vf_parameters.name4" in query_params
- assert "value4" in query_params["vf_parameters.name4"]
- assert "type" in query_params
- assert "tabloid" in query_params["type"]
-
-
-@pytest.mark.parametrize("page_size", [1, 10, 100, 1_000])
-def test_queryset_endpoint_pagesize_all(server: TSC.Server, page_size: int) -> None:
- with requests_mock.mock() as m:
- m.get(f"{server.views.baseurl}?pageSize={page_size}", text=SLICING_QUERYSET_PAGE_1.read_text())
- queryset = server.views.all(page_size=page_size)
- assert queryset.request_options.pagesize == page_size
- _ = list(queryset)
-
-
-@pytest.mark.parametrize("page_size", [1, 10, 100, 1_000])
-def test_queryset_endpoint_pagesize_filter(server: TSC.Server, page_size: int) -> None:
- with requests_mock.mock() as m:
- m.get(f"{server.views.baseurl}?pageSize={page_size}", text=SLICING_QUERYSET_PAGE_1.read_text())
- queryset = server.views.filter(page_size=page_size)
- assert queryset.request_options.pagesize == page_size
- _ = list(queryset)
-
-
-@pytest.mark.parametrize("page_size", [1, 10, 100, 1_000])
-def test_queryset_pagesize_filter(server: TSC.Server, page_size: int) -> None:
- with requests_mock.mock() as m:
- m.get(f"{server.views.baseurl}?pageSize={page_size}", text=SLICING_QUERYSET_PAGE_1.read_text())
- queryset = server.views.all().filter(page_size=page_size)
- assert queryset.request_options.pagesize == page_size
- _ = list(queryset)
-
-
-def test_language_export(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.get(requests_mock.ANY)
- url = server.views.baseurl + "/456/data"
- opts = TSC.PDFRequestOptions()
- opts.language = "en-US"
-
- resp = server.users.get_request(url, request_object=opts)
- query_string = parse_qs(resp.request.query)
- assert "language" in query_string
- assert "en-us" in query_string["language"]
-
-
-def test_queryset_fields(server: TSC.Server) -> None:
- loop = server.users.fields("id")
- assert "id" in loop.request_options.fields
- assert "_default_" in loop.request_options.fields
-
-
-def test_queryset_only_fields(server: TSC.Server) -> None:
- loop = server.users.only_fields("id")
- assert "id" in loop.request_options.fields
- assert "_default_" not in loop.request_options.fields
-
-
-def test_queryset_field_order(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.get(server.views.baseurl, text=SLICING_QUERYSET_PAGE_1.read_text())
- loop = server.views.fields("id", "name")
- list(loop)
- history = m.request_history[0]
-
- fields = history.qs.get("fields", [""])[0].split(",")
-
- assert fields[0] == "_default_"
- assert "id" in fields
- assert "name" in fields
-
-
-def test_queryset_field_all(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.get(server.views.baseurl, text=SLICING_QUERYSET_PAGE_1.read_text())
- loop = server.views.fields("id", "name", "_all_")
- list(loop)
- history = m.request_history[0]
-
- fields = history.qs.get("fields", [""])[0]
-
- assert fields == "_all_"
-
-
-def test_pdf_viz_dimensions_query_params() -> None:
- opts = TSC.PDFRequestOptions(viz_width=1920, viz_height=1080)
- params = opts.get_query_params()
- assert params["vizWidth"] == 1920
- assert params["vizHeight"] == 1080
-
-
-def test_pdf_viz_dimensions_only_one_raises() -> None:
- opts = TSC.PDFRequestOptions(viz_width=1920)
- with pytest.raises(ValueError):
- opts.get_query_params()
-
- opts2 = TSC.PDFRequestOptions(viz_height=1080)
- with pytest.raises(ValueError):
- opts2.get_query_params()
-
-
-def test_pdf_viz_dimensions_none_by_default() -> None:
- opts = TSC.PDFRequestOptions()
- params = opts.get_query_params()
- assert "vizWidth" not in params
- assert "vizHeight" not in params
-
-
-def test_pdf_viz_dimensions_via_request(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.get(requests_mock.ANY)
- url = server.views.baseurl + "/abc/pdf"
- opts = TSC.PDFRequestOptions(viz_width=800, viz_height=600)
-
- resp = server.views.get_request(url, request_object=opts)
- query_string = parse_qs(resp.request.query)
- assert "vizwidth" in query_string
- assert ["800"] == query_string["vizwidth"]
- assert "vizheight" in query_string
- assert ["600"] == query_string["vizheight"]
diff --git a/test/test_requests.py b/test/test_requests.py
deleted file mode 100644
index 5ee68b020..000000000
--- a/test/test_requests.py
+++ /dev/null
@@ -1,69 +0,0 @@
-from urllib.parse import parse_qs
-
-import pytest
-import requests
-import requests_mock
-
-import tableauserverclient as TSC
-from tableauserverclient.server.endpoint.exceptions import InternalServerError, NonXMLResponseError
-
-
-@pytest.fixture(scope="function")
-def server():
- """Fixture to create a TSC.Server instance for testing."""
- server = TSC.Server("http://test", False)
-
- # Fake signin
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
-
- return server
-
-
-def test_make_get_request(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.get(requests_mock.ANY)
- url = "http://test/api/2.3/sites/dad65087-b08b-4603-af4e-2887b8aafc67/workbooks"
- opts = TSC.RequestOptions(pagesize=13, pagenumber=15)
- resp = server.workbooks.get_request(url, request_object=opts)
-
- query = parse_qs(resp.request.query)
- assert query.get("pagesize") == ["13"]
- assert query.get("pagenumber") == ["15"]
-
-
-def test_make_post_request(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.post(requests_mock.ANY)
- url = "http://test/api/2.3/sites/dad65087-b08b-4603-af4e-2887b8aafc67/workbooks"
- resp = server.workbooks._make_request(
- requests.post,
- url,
- content=b"1337",
- auth_token="j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM",
- content_type="multipart/mixed",
- )
- assert resp.request.headers["x-tableau-auth"] == "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
- assert resp.request.headers["content-type"] == "multipart/mixed"
- assert "Tableau Server Client" in resp.request.headers["user-agent"]
- assert resp.request.body == b"1337"
-
-
-# Test that 500 server errors are handled properly
-def test_internal_server_error(server: TSC.Server) -> None:
- server.version = "3.2"
- server_response = "500: Internal Server Error"
- with requests_mock.mock() as m:
- m.register_uri("GET", server.server_info.baseurl, status_code=500, text=server_response)
- with pytest.raises(InternalServerError, match=server_response):
- server.server_info.get()
-
-
-# Test that non-xml server errors are handled properly
-def test_non_xml_error(server: TSC.Server) -> None:
- server.version = "3.2"
- server_response = "this is not xml"
- with requests_mock.mock() as m:
- m.register_uri("GET", server.server_info.baseurl, status_code=499, text=server_response)
- with pytest.raises(NonXMLResponseError, match=server_response):
- server.server_info.get()
diff --git a/test/test_schedule.py b/test/test_schedule.py
deleted file mode 100644
index 823a87607..000000000
--- a/test/test_schedule.py
+++ /dev/null
@@ -1,494 +0,0 @@
-from pathlib import Path
-from datetime import time
-
-import pytest
-import requests_mock
-
-import tableauserverclient as TSC
-from tableauserverclient.datetime_helpers import format_datetime
-
-TEST_ASSET_DIR = Path(__file__).parent / "assets"
-
-GET_XML = TEST_ASSET_DIR / "schedule_get.xml"
-GET_BY_ID_XML = TEST_ASSET_DIR / "schedule_get_by_id.xml"
-GET_HOURLY_ID_XML = TEST_ASSET_DIR / "schedule_get_hourly_id.xml"
-GET_DAILY_ID_XML = TEST_ASSET_DIR / "schedule_get_daily_id.xml"
-GET_MONTHLY_ID_XML = TEST_ASSET_DIR / "schedule_get_monthly_id.xml"
-GET_MONTHLY_ID_2_XML = TEST_ASSET_DIR / "schedule_get_monthly_id_2.xml"
-GET_CUSTOMIZED_MONTHLY_ID_XML = TEST_ASSET_DIR / "schedule_get_customized_monthly_id.xml"
-GET_EMPTY_XML = TEST_ASSET_DIR / "schedule_get_empty.xml"
-CREATE_HOURLY_XML = TEST_ASSET_DIR / "schedule_create_hourly.xml"
-CREATE_DAILY_XML = TEST_ASSET_DIR / "schedule_create_daily.xml"
-CREATE_WEEKLY_XML = TEST_ASSET_DIR / "schedule_create_weekly.xml"
-CREATE_MONTHLY_XML = TEST_ASSET_DIR / "schedule_create_monthly.xml"
-UPDATE_XML = TEST_ASSET_DIR / "schedule_update.xml"
-ADD_WORKBOOK_TO_SCHEDULE = TEST_ASSET_DIR / "schedule_add_workbook.xml"
-ADD_WORKBOOK_TO_SCHEDULE_WITH_WARNINGS = TEST_ASSET_DIR / "schedule_add_workbook_with_warnings.xml"
-ADD_DATASOURCE_TO_SCHEDULE = TEST_ASSET_DIR / "schedule_add_datasource.xml"
-ADD_FLOW_TO_SCHEDULE = TEST_ASSET_DIR / "schedule_add_flow.xml"
-GET_EXTRACT_TASKS_XML = TEST_ASSET_DIR / "schedule_get_extract_refresh_tasks.xml"
-BATCH_UPDATE_STATE = TEST_ASSET_DIR / "schedule_batch_update_state.xml"
-
-WORKBOOK_GET_BY_ID_XML = TEST_ASSET_DIR / "workbook_get_by_id.xml"
-DATASOURCE_GET_BY_ID_XML = TEST_ASSET_DIR / "datasource_get_by_id.xml"
-FLOW_GET_BY_ID_XML = TEST_ASSET_DIR / "flow_get_by_id.xml"
-
-
-@pytest.fixture(scope="function")
-def server():
- """Fixture to create a TSC.Server instance for testing."""
- server = TSC.Server("http://test", False)
-
- # Fake signin
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
-
- return server
-
-
-def test_get(server: TSC.Server) -> None:
- response_xml = GET_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.schedules.baseurl, text=response_xml)
- all_schedules, pagination_item = server.schedules.get()
-
- extract = all_schedules[0]
- subscription = all_schedules[1]
- flow = all_schedules[2]
- system = all_schedules[3]
-
- assert 2 == pagination_item.total_available
- assert "c9cff7f9-309c-4361-99ff-d4ba8c9f5467" == extract.id
- assert "Weekday early mornings" == extract.name
- assert "Active" == extract.state
- assert 50 == extract.priority
- assert "2016-07-06T20:19:00Z" == format_datetime(extract.created_at)
- assert "2016-09-13T11:00:32Z" == format_datetime(extract.updated_at)
- assert "Extract" == extract.schedule_type
- assert "2016-09-14T11:00:00Z" == format_datetime(extract.next_run_at)
-
- assert "bcb79d07-6e47-472f-8a65-d7f51f40c36c" == subscription.id
- assert "Saturday night" == subscription.name
- assert "Active" == subscription.state
- assert 80 == subscription.priority
- assert "2016-07-07T20:19:00Z" == format_datetime(subscription.created_at)
- assert "2016-09-12T16:39:38Z" == format_datetime(subscription.updated_at)
- assert "Subscription" == subscription.schedule_type
- assert "2016-09-18T06:00:00Z" == format_datetime(subscription.next_run_at)
-
- assert "f456e8f2-aeb2-4a8e-b823-00b6f08640f0" == flow.id
- assert "First of the month 1:00AM" == flow.name
- assert "Active" == flow.state
- assert 50 == flow.priority
- assert "2019-02-19T18:52:19Z" == format_datetime(flow.created_at)
- assert "2019-02-19T18:55:51Z" == format_datetime(flow.updated_at)
- assert "Flow" == flow.schedule_type
- assert "2019-03-01T09:00:00Z" == format_datetime(flow.next_run_at)
-
- assert "3cfa4713-ce7c-4fa7-aa2e-f752bfc8dd04" == system.id
- assert "First of the month 2:00AM" == system.name
- assert "Active" == system.state
- assert 30 == system.priority
- assert "2019-02-19T18:52:19Z" == format_datetime(system.created_at)
- assert "2019-02-19T18:55:51Z" == format_datetime(system.updated_at)
- assert "System" == system.schedule_type
- assert "2019-03-01T09:00:00Z" == format_datetime(system.next_run_at)
-
-
-def test_get_empty(server: TSC.Server) -> None:
- response_xml = GET_EMPTY_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.schedules.baseurl, text=response_xml)
- all_schedules, pagination_item = server.schedules.get()
-
- assert 0 == pagination_item.total_available
- assert [] == all_schedules
-
-
-def test_get_by_id(server: TSC.Server) -> None:
- server.version = "3.8"
- response_xml = GET_BY_ID_XML.read_text()
- with requests_mock.mock() as m:
- schedule_id = "c9cff7f9-309c-4361-99ff-d4ba8c9f5467"
- baseurl = f"{server.baseurl}/schedules/{schedule_id}"
- m.get(baseurl, text=response_xml)
- schedule = server.schedules.get_by_id(schedule_id)
- assert schedule is not None
- assert schedule_id == schedule.id
- assert "Weekday early mornings" == schedule.name
- assert "Active" == schedule.state
-
-
-def test_get_hourly_by_id(server: TSC.Server) -> None:
- server.version = "3.8"
- response_xml = GET_HOURLY_ID_XML.read_text()
- with requests_mock.mock() as m:
- schedule_id = "c9cff7f9-309c-4361-99ff-d4ba8c9f5467"
- baseurl = f"{server.baseurl}/schedules/{schedule_id}"
- m.get(baseurl, text=response_xml)
- schedule = server.schedules.get_by_id(schedule_id)
- assert schedule is not None
- assert schedule_id == schedule.id
- assert "Hourly schedule" == schedule.name
- assert "Active" == schedule.state
- assert ("Monday", 0.5) == schedule.interval_item.interval
-
-
-def test_get_daily_by_id(server: TSC.Server) -> None:
- server.version = "3.8"
- response_xml = GET_DAILY_ID_XML.read_text()
- with requests_mock.mock() as m:
- schedule_id = "c9cff7f9-309c-4361-99ff-d4ba8c9f5467"
- baseurl = f"{server.baseurl}/schedules/{schedule_id}"
- m.get(baseurl, text=response_xml)
- schedule = server.schedules.get_by_id(schedule_id)
- assert schedule is not None
- assert schedule_id == schedule.id
- assert "Daily schedule" == schedule.name
- assert "Active" == schedule.state
- assert ("Monday", 2.0) == schedule.interval_item.interval
-
-
-def test_get_monthly_by_id(server: TSC.Server) -> None:
- server.version = "3.8"
- response_xml = GET_MONTHLY_ID_XML.read_text()
- with requests_mock.mock() as m:
- schedule_id = "c9cff7f9-309c-4361-99ff-d4ba8c9f5467"
- baseurl = f"{server.baseurl}/schedules/{schedule_id}"
- m.get(baseurl, text=response_xml)
- schedule = server.schedules.get_by_id(schedule_id)
- assert schedule is not None
- assert schedule_id == schedule.id
- assert "Monthly multiple days" == schedule.name
- assert "Active" == schedule.state
- assert ("1", "2") == schedule.interval_item.interval
-
-
-def test_get_monthly_by_id_2(server: TSC.Server) -> None:
- server.version = "3.15"
- response_xml = GET_MONTHLY_ID_2_XML.read_text()
- with requests_mock.mock() as m:
- schedule_id = "8c5caf33-6223-4724-83c3-ccdc1e730a07"
- baseurl = f"{server.baseurl}/schedules/{schedule_id}"
- m.get(baseurl, text=response_xml)
- schedule = server.schedules.get_by_id(schedule_id)
- assert schedule is not None
- assert schedule_id == schedule.id
- assert "Monthly First Monday!" == schedule.name
- assert "Active" == schedule.state
- assert ("Monday", "First") == schedule.interval_item.interval
-
-
-def test_get_customized_monthly_by_id(server: TSC.Server) -> None:
- server.version = "3.15"
- response_xml = GET_CUSTOMIZED_MONTHLY_ID_XML.read_text()
- with requests_mock.mock() as m:
- schedule_id = "f048d794-90dc-40b0-bfad-2ca78e437369"
- baseurl = f"{server.baseurl}/schedules/{schedule_id}"
- m.get(baseurl, text=response_xml)
- schedule = server.schedules.get_by_id(schedule_id)
- assert schedule is not None
- assert schedule_id == schedule.id
- assert "Monthly customized" == schedule.name
- assert "Active" == schedule.state
- assert ("Customized Monthly",) == schedule.interval_item.interval
-
-
-def test_delete(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.delete(server.schedules.baseurl + "/c9cff7f9-309c-4361-99ff-d4ba8c9f5467", status_code=204)
- server.schedules.delete("c9cff7f9-309c-4361-99ff-d4ba8c9f5467")
-
-
-def test_create_hourly(server: TSC.Server) -> None:
- response_xml = CREATE_HOURLY_XML.read_text()
- with requests_mock.mock() as m:
- m.post(server.schedules.baseurl, text=response_xml)
- hourly_interval = TSC.HourlyInterval(start_time=time(2, 30), end_time=time(23, 0), interval_value=2)
- new_schedule = TSC.ScheduleItem(
- "hourly-schedule-1",
- 50,
- TSC.ScheduleItem.Type.Extract,
- TSC.ScheduleItem.ExecutionOrder.Parallel,
- hourly_interval,
- )
- new_schedule = server.schedules.create(new_schedule)
-
- assert "5f42be25-8a43-47ba-971a-63f2d4e7029c" == new_schedule.id
- assert "hourly-schedule-1" == new_schedule.name
- assert "Active" == new_schedule.state
- assert 50 == new_schedule.priority
- assert "2016-09-15T20:47:33Z" == format_datetime(new_schedule.created_at)
- assert "2016-09-15T20:47:33Z" == format_datetime(new_schedule.updated_at)
- assert TSC.ScheduleItem.Type.Extract == new_schedule.schedule_type
- assert "2016-09-16T01:30:00Z" == format_datetime(new_schedule.next_run_at)
- assert TSC.ScheduleItem.ExecutionOrder.Parallel == new_schedule.execution_order
- assert time(2, 30) == new_schedule.interval_item.start_time
- assert time(23) == new_schedule.interval_item.end_time # type: ignore[union-attr]
- assert ("8",) == new_schedule.interval_item.interval # type: ignore[union-attr]
-
-
-def test_create_daily(server: TSC.Server) -> None:
- response_xml = CREATE_DAILY_XML.read_text()
- with requests_mock.mock() as m:
- m.post(server.schedules.baseurl, text=response_xml)
- daily_interval = TSC.DailyInterval(time(4, 50))
- new_schedule = TSC.ScheduleItem(
- "daily-schedule-1",
- 90,
- TSC.ScheduleItem.Type.Subscription,
- TSC.ScheduleItem.ExecutionOrder.Serial,
- daily_interval,
- )
- new_schedule = server.schedules.create(new_schedule)
-
- assert "907cae38-72fd-417c-892a-95540c4664cd" == new_schedule.id
- assert "daily-schedule-1" == new_schedule.name
- assert "Active" == new_schedule.state
- assert 90 == new_schedule.priority
- assert "2016-09-15T21:01:09Z" == format_datetime(new_schedule.created_at)
- assert "2016-09-15T21:01:09Z" == format_datetime(new_schedule.updated_at)
- assert TSC.ScheduleItem.Type.Subscription == new_schedule.schedule_type
- assert "2016-09-16T11:45:00Z" == format_datetime(new_schedule.next_run_at)
- assert TSC.ScheduleItem.ExecutionOrder.Serial == new_schedule.execution_order
- assert time(4, 45) == new_schedule.interval_item.start_time
-
-
-def test_create_weekly(server: TSC.Server) -> None:
- response_xml = CREATE_WEEKLY_XML.read_text()
- with requests_mock.mock() as m:
- m.post(server.schedules.baseurl, text=response_xml)
- weekly_interval = TSC.WeeklyInterval(
- time(9, 15), TSC.IntervalItem.Day.Monday, TSC.IntervalItem.Day.Wednesday, TSC.IntervalItem.Day.Friday
- )
- new_schedule = TSC.ScheduleItem(
- "weekly-schedule-1",
- 80,
- TSC.ScheduleItem.Type.Extract,
- TSC.ScheduleItem.ExecutionOrder.Parallel,
- weekly_interval,
- )
- new_schedule = server.schedules.create(new_schedule)
-
- assert "1adff386-6be0-4958-9f81-a35e676932bf" == new_schedule.id
- assert "weekly-schedule-1" == new_schedule.name
- assert "Active" == new_schedule.state
- assert 80 == new_schedule.priority
- assert "2016-09-15T21:12:50Z" == format_datetime(new_schedule.created_at)
- assert "2016-09-15T21:12:50Z" == format_datetime(new_schedule.updated_at)
- assert TSC.ScheduleItem.Type.Extract == new_schedule.schedule_type
- assert "2016-09-16T16:15:00Z" == format_datetime(new_schedule.next_run_at)
- assert TSC.ScheduleItem.ExecutionOrder.Parallel == new_schedule.execution_order
- assert time(9, 15) == new_schedule.interval_item.start_time
- assert ("Monday", "Wednesday", "Friday") == new_schedule.interval_item.interval
- assert 2 == len(new_schedule.warnings)
- assert "warning 1" == new_schedule.warnings[0]
- assert "warning 2" == new_schedule.warnings[1]
-
-
-def test_create_monthly(server: TSC.Server) -> None:
- response_xml = CREATE_MONTHLY_XML.read_text()
- with requests_mock.mock() as m:
- m.post(server.schedules.baseurl, text=response_xml)
- monthly_interval = TSC.MonthlyInterval(time(7), 12)
- new_schedule = TSC.ScheduleItem(
- "monthly-schedule-1",
- 20,
- TSC.ScheduleItem.Type.Extract,
- TSC.ScheduleItem.ExecutionOrder.Serial,
- monthly_interval,
- )
- new_schedule = server.schedules.create(new_schedule)
-
- assert "e06a7c75-5576-4f68-882d-8909d0219326" == new_schedule.id
- assert "monthly-schedule-1" == new_schedule.name
- assert "Active" == new_schedule.state
- assert 20 == new_schedule.priority
- assert "2016-09-15T21:16:56Z" == format_datetime(new_schedule.created_at)
- assert "2016-09-15T21:16:56Z" == format_datetime(new_schedule.updated_at)
- assert TSC.ScheduleItem.Type.Extract == new_schedule.schedule_type
- assert "2016-10-12T14:00:00Z" == format_datetime(new_schedule.next_run_at)
- assert TSC.ScheduleItem.ExecutionOrder.Serial == new_schedule.execution_order
- assert time(7) == new_schedule.interval_item.start_time
- assert ("12",) == new_schedule.interval_item.interval # type: ignore[union-attr]
-
-
-def test_update(server: TSC.Server) -> None:
- response_xml = UPDATE_XML.read_text()
- with requests_mock.mock() as m:
- m.put(server.schedules.baseurl + "/7bea1766-1543-4052-9753-9d224bc069b5", text=response_xml)
- new_interval = TSC.WeeklyInterval(time(7), TSC.IntervalItem.Day.Monday, TSC.IntervalItem.Day.Friday)
- single_schedule = TSC.ScheduleItem(
- "weekly-schedule-1",
- 90,
- TSC.ScheduleItem.Type.Extract,
- TSC.ScheduleItem.ExecutionOrder.Parallel,
- new_interval,
- )
- single_schedule._id = "7bea1766-1543-4052-9753-9d224bc069b5"
- single_schedule.state = TSC.ScheduleItem.State.Suspended
- single_schedule = server.schedules.update(single_schedule)
-
- assert "7bea1766-1543-4052-9753-9d224bc069b5" == single_schedule.id
- assert "weekly-schedule-1" == single_schedule.name
- assert 90 == single_schedule.priority
- assert "2016-09-15T23:50:02Z" == format_datetime(single_schedule.updated_at)
- assert TSC.ScheduleItem.Type.Extract == single_schedule.schedule_type
- assert "2016-09-16T14:00:00Z" == format_datetime(single_schedule.next_run_at)
- assert TSC.ScheduleItem.ExecutionOrder.Parallel == single_schedule.execution_order
- assert time(7) == single_schedule.interval_item.start_time
- assert ("Monday", "Friday") == single_schedule.interval_item.interval # type: ignore[union-attr]
- assert TSC.ScheduleItem.State.Suspended == single_schedule.state
-
-
-# Tests calling update with a schedule item returned from the server
-def test_update_after_get(server: TSC.Server) -> None:
- get_response_xml = GET_XML.read_text()
- update_response_xml = UPDATE_XML.read_text()
-
- # Get a schedule
- with requests_mock.mock() as m:
- m.get(server.schedules.baseurl, text=get_response_xml)
- all_schedules, pagination_item = server.schedules.get()
- schedule_item = all_schedules[0]
- assert TSC.ScheduleItem.State.Active == schedule_item.state
- assert "Weekday early mornings" == schedule_item.name
-
- # Update the schedule
- with requests_mock.mock() as m:
- m.put(server.schedules.baseurl + "/c9cff7f9-309c-4361-99ff-d4ba8c9f5467", text=update_response_xml)
- schedule_item.state = TSC.ScheduleItem.State.Suspended
- schedule_item.name = "newName"
- schedule_item = server.schedules.update(schedule_item)
-
- assert TSC.ScheduleItem.State.Suspended == schedule_item.state
- assert "weekly-schedule-1" == schedule_item.name
-
-
-def test_add_workbook(server: TSC.Server) -> None:
- server.version = "2.8"
- baseurl = f"{server.baseurl}/sites/{server.site_id}/schedules"
-
- workbook_response = WORKBOOK_GET_BY_ID_XML.read_text()
- add_workbook_response = ADD_WORKBOOK_TO_SCHEDULE.read_text()
- with requests_mock.mock() as m:
- m.get(server.workbooks.baseurl + "/bar", text=workbook_response)
- m.put(baseurl + "/foo/workbooks", text=add_workbook_response)
- workbook = server.workbooks.get_by_id("bar")
- result = server.schedules.add_to_schedule("foo", workbook=workbook)
- assert 0 == len(result), "Added properly"
-
-
-def test_add_workbook_with_warnings(server: TSC.Server) -> None:
- server.version = "2.8"
- baseurl = f"{server.baseurl}/sites/{server.site_id}/schedules"
-
- workbook_response = WORKBOOK_GET_BY_ID_XML.read_text()
- add_workbook_response = ADD_WORKBOOK_TO_SCHEDULE_WITH_WARNINGS.read_text()
- with requests_mock.mock() as m:
- m.get(server.workbooks.baseurl + "/bar", text=workbook_response)
- m.put(baseurl + "/foo/workbooks", text=add_workbook_response)
- workbook = server.workbooks.get_by_id("bar")
- result = server.schedules.add_to_schedule("foo", workbook=workbook)
- assert 1 == len(result), "Not added properly"
- assert 2 == len(result[0].warnings)
-
-
-def test_add_datasource(server: TSC.Server) -> None:
- server.version = "2.8"
- baseurl = f"{server.baseurl}/sites/{server.site_id}/schedules"
-
- datasource_response = DATASOURCE_GET_BY_ID_XML.read_text()
- add_datasource_response = ADD_DATASOURCE_TO_SCHEDULE.read_text()
- with requests_mock.mock() as m:
- m.get(server.datasources.baseurl + "/bar", text=datasource_response)
- m.put(baseurl + "/foo/datasources", text=add_datasource_response)
- datasource = server.datasources.get_by_id("bar")
- result = server.schedules.add_to_schedule("foo", datasource=datasource)
- assert 0 == len(result), "Added properly"
-
-
-def test_add_flow(server: TSC.Server) -> None:
- server.version = "3.3"
- baseurl = f"{server.baseurl}/sites/{server.site_id}/schedules"
-
- flow_response = FLOW_GET_BY_ID_XML.read_text()
- add_flow_response = ADD_FLOW_TO_SCHEDULE.read_text()
- with requests_mock.mock() as m:
- m.get(server.flows.baseurl + "/bar", text=flow_response)
- m.put(baseurl + "/foo/flows", text=flow_response)
- flow = server.flows.get_by_id("bar")
- result = server.schedules.add_to_schedule("foo", flow=flow)
- assert 0 == len(result), "Added properly"
-
-
-def test_get_extract_refresh_tasks(server: TSC.Server) -> None:
- server.version = "2.3"
-
- response_xml = GET_EXTRACT_TASKS_XML.read_text()
- with requests_mock.mock() as m:
- schedule_id = "c9cff7f9-309c-4361-99ff-d4ba8c9f5467"
- baseurl = f"{server.baseurl}/sites/{server.site_id}/schedules/{schedule_id}/extracts"
- m.get(baseurl, text=response_xml)
-
- extracts = server.schedules.get_extract_refresh_tasks(schedule_id)
-
- assert extracts is not None
- assert isinstance(extracts[0], list)
- assert 2 == len(extracts[0])
- assert "task1" == extracts[0][0].id
-
-
-def test_batch_update_state_items(server: TSC.Server) -> None:
- server.version = "3.27"
- hourly_interval = TSC.HourlyInterval(start_time=time(2, 30), end_time=time(23, 0), interval_value=2)
- args = ("hourly", 50, TSC.ScheduleItem.Type.Extract, TSC.ScheduleItem.ExecutionOrder.Parallel, hourly_interval)
- new_schedules = [TSC.ScheduleItem(*args), TSC.ScheduleItem(*args), TSC.ScheduleItem(*args)]
- new_schedules[0]._id = "593d2ebf-0d18-4deb-9d21-b113a4902583"
- new_schedules[1]._id = "cecbb71e-def0-4030-8068-5ae50f51db1c"
- new_schedules[2]._id = "f39a6e7d-405e-4c07-8c18-95845f9da80e"
-
- state = "active"
- with requests_mock.mock() as m:
- m.put(f"{server.schedules.baseurl}?state={state}", text=BATCH_UPDATE_STATE.read_text())
- resp = server.schedules.batch_update_state(new_schedules, state)
-
- assert len(resp) == 3
- for sch, r in zip(new_schedules, resp):
- assert sch.id == r
-
-
-def test_batch_update_state_str(server: TSC.Server) -> None:
- server.version = "3.27"
- new_schedules = [
- "593d2ebf-0d18-4deb-9d21-b113a4902583",
- "cecbb71e-def0-4030-8068-5ae50f51db1c",
- "f39a6e7d-405e-4c07-8c18-95845f9da80e",
- ]
-
- state = "suspended"
- with requests_mock.mock() as m:
- m.put(f"{server.schedules.baseurl}?state={state}", text=BATCH_UPDATE_STATE.read_text())
- resp = server.schedules.batch_update_state(new_schedules, state)
-
- assert len(resp) == 3
- for sch, r in zip(new_schedules, resp):
- assert sch == r
-
-
-def test_batch_update_state_all(server: TSC.Server) -> None:
- server.version = "3.27"
- new_schedules = [
- "593d2ebf-0d18-4deb-9d21-b113a4902583",
- "cecbb71e-def0-4030-8068-5ae50f51db1c",
- "f39a6e7d-405e-4c07-8c18-95845f9da80e",
- ]
-
- state = "suspended"
- with requests_mock.mock() as m:
- m.put(f"{server.schedules.baseurl}?state={state}&updateAll=true", text=BATCH_UPDATE_STATE.read_text())
- _ = server.schedules.batch_update_state(new_schedules, state, True)
-
- history = m.request_history[0]
-
- assert history.text == " "
diff --git a/test/test_server_info.py b/test/test_server_info.py
deleted file mode 100644
index 6eefab8a8..000000000
--- a/test/test_server_info.py
+++ /dev/null
@@ -1,81 +0,0 @@
-from pathlib import Path
-
-import pytest
-import requests_mock
-
-import tableauserverclient as TSC
-from tableauserverclient.server.endpoint.exceptions import NonXMLResponseError
-
-TEST_ASSET_DIR = Path(__file__).parent / "assets"
-
-SERVER_INFO_GET_XML = TEST_ASSET_DIR / "server_info_get.xml"
-SERVER_INFO_25_XML = TEST_ASSET_DIR / "server_info_25.xml"
-SERVER_INFO_404 = TEST_ASSET_DIR / "server_info_404.xml"
-SERVER_INFO_AUTH_INFO_XML = TEST_ASSET_DIR / "server_info_auth_info.xml"
-SERVER_INFO_WRONG_SITE = TEST_ASSET_DIR / "server_info_wrong_site.html"
-
-
-@pytest.fixture(scope="function")
-def server():
- """Fixture to create a TSC.Server instance for testing."""
- server = TSC.Server("http://test", False)
-
- # Fake signin
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
- server.version = "2.4"
-
- return server
-
-
-def test_server_info_get(server: TSC.Server) -> None:
- response_xml = SERVER_INFO_GET_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.server_info.baseurl, text=response_xml)
- actual = server.server_info.get()
-
- assert actual is not None
- assert "10.1.0" == actual.product_version
- assert "10100.16.1024.2100" == actual.build_number
- assert "3.10" == actual.rest_api_version
-
-
-def test_server_info_use_highest_version_downgrades(server: TSC.Server) -> None:
- # This is the auth.xml endpoint present back to 9.0 Servers
- auth_response_xml = SERVER_INFO_AUTH_INFO_XML.read_text()
- # 10.1 serverInfo response
- si_response_xml = SERVER_INFO_404.read_text()
- with requests_mock.mock() as m:
- # Return a 404 for serverInfo so we can pretend this is an old Server
- m.get(server.server_address + "/api/2.4/serverInfo", text=si_response_xml, status_code=404)
- m.get(server.server_address + "/auth?format=xml", text=auth_response_xml)
- server.use_server_version()
- # does server-version[9.2] lookup in PRODUCT_TO_REST_VERSION
- assert server.version == "2.2"
-
-
-def test_server_info_use_highest_version_upgrades(server: TSC.Server) -> None:
- si_response_xml = SERVER_INFO_GET_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.server_address + "/api/2.8/serverInfo", text=si_response_xml)
- # Pretend we're old
- server.version = "2.8"
- server.use_server_version()
- # Did we upgrade to 3.10?
- assert server.version == "3.10"
-
-
-def test_server_use_server_version_flag(server: TSC.Server) -> None:
- # use_server_version=True defers version detection to sign_in, so the
- # version is not updated at construction time
- server = TSC.Server("http://test", use_server_version=True)
- assert server._use_server_version is True
- assert server.version == "2.4" # still at default until sign_in
-
-
-def test_server_wrong_site(server: TSC.Server) -> None:
- response = SERVER_INFO_WRONG_SITE.read_text()
- with requests_mock.mock() as m:
- m.get(server.server_info.baseurl, text=response, status_code=404)
- with pytest.raises(NonXMLResponseError):
- server.server_info.get()
diff --git a/test/test_site.py b/test/test_site.py
deleted file mode 100644
index e976bc1d2..000000000
--- a/test/test_site.py
+++ /dev/null
@@ -1,339 +0,0 @@
-from itertools import product
-from pathlib import Path
-
-from defusedxml import ElementTree as ET
-import pytest
-import requests_mock
-
-import tableauserverclient as TSC
-from tableauserverclient.server.request_factory import RequestFactory
-
-from . import _utils
-
-TEST_ASSET_DIR = Path(__file__).parent / "assets"
-
-GET_XML = TEST_ASSET_DIR / "site_get.xml"
-GET_BY_ID_XML = TEST_ASSET_DIR / "site_get_by_id.xml"
-GET_BY_NAME_XML = TEST_ASSET_DIR / "site_get_by_name.xml"
-UPDATE_XML = TEST_ASSET_DIR / "site_update.xml"
-CREATE_XML = TEST_ASSET_DIR / "site_create.xml"
-SITE_AUTH_CONFIG_XML = TEST_ASSET_DIR / "site_auth_configurations.xml"
-
-
-@pytest.fixture(scope="function")
-def server():
- """Fixture to create a TSC.Server instance for testing."""
- server = TSC.Server("http://test", False)
-
- # Fake signin
- server._site_id = "0626857c-1def-4503-a7d8-7907c3ff9d9f"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
- server.version = "3.10"
-
- return server
-
-
-def test_get(server: TSC.Server) -> None:
- response_xml = GET_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.sites.baseurl, text=response_xml)
- all_sites, pagination_item = server.sites.get()
-
- assert 2 == pagination_item.total_available
- assert "dad65087-b08b-4603-af4e-2887b8aafc67" == all_sites[0].id
- assert "Active" == all_sites[0].state
- assert "Default" == all_sites[0].name
- assert "ContentOnly" == all_sites[0].admin_mode
- assert all_sites[0].revision_history_enabled is False
- assert all_sites[0].subscribe_others_enabled is True
- assert 25 == all_sites[0].revision_limit
- assert None == all_sites[0].num_users
- assert None == all_sites[0].storage
- assert all_sites[0].cataloging_enabled is True
- assert all_sites[0].editing_flows_enabled is False
- assert all_sites[0].scheduling_flows_enabled is False
- assert all_sites[0].allow_subscription_attachments is True
- assert "6b7179ba-b82b-4f0f-91ed-812074ac5da6" == all_sites[1].id
- assert "Active" == all_sites[1].state
- assert "Samples" == all_sites[1].name
- assert "ContentOnly" == all_sites[1].admin_mode
- assert all_sites[1].revision_history_enabled is False
- assert all_sites[1].subscribe_others_enabled is True
- assert all_sites[1].guest_access_enabled is False
- assert all_sites[1].cache_warmup_enabled is True
- assert all_sites[1].commenting_enabled is True
- assert all_sites[1].cache_warmup_enabled is True
- assert all_sites[1].request_access_enabled is False
- assert all_sites[1].run_now_enabled is True
- assert 1 == all_sites[1].tier_explorer_capacity
- assert 2 == all_sites[1].tier_creator_capacity
- assert 1 == all_sites[1].tier_viewer_capacity
- assert all_sites[1].flows_enabled is False
- assert None == all_sites[1].data_acceleration_mode
-
-
-def test_get_before_signin(server: TSC.Server) -> None:
- server._auth_token = None
- with pytest.raises(TSC.NotSignedInError):
- server.sites.get()
-
-
-def test_get_by_id(server: TSC.Server) -> None:
- response_xml = GET_BY_ID_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.sites.baseurl + "/" + server.site_id, text=response_xml)
- single_site = server.sites.get_by_id(server.site_id)
-
- assert server.site_id == single_site.id
- assert "Active" == single_site.state
- assert "Default" == single_site.name
- assert "ContentOnly" == single_site.admin_mode
- assert single_site.revision_history_enabled is False
- assert single_site.subscribe_others_enabled is True
- assert single_site.disable_subscriptions is False
- assert single_site.data_alerts_enabled is False
- assert single_site.commenting_mentions_enabled is False
- assert single_site.catalog_obfuscation_enabled is True
-
-
-def test_get_by_id_missing_id(server: TSC.Server) -> None:
- with pytest.raises(ValueError):
- server.sites.get_by_id("")
-
-
-def test_get_by_name(server: TSC.Server) -> None:
- response_xml = GET_BY_NAME_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.sites.baseurl + "/testsite?key=name", text=response_xml)
- single_site = server.sites.get_by_name("testsite")
-
- assert server.site_id == single_site.id
- assert "Active" == single_site.state
- assert "testsite" == single_site.name
- assert "ContentOnly" == single_site.admin_mode
- assert single_site.revision_history_enabled is False
- assert single_site.subscribe_others_enabled is True
- assert single_site.disable_subscriptions is False
-
-
-def test_get_by_name_missing_name(server: TSC.Server) -> None:
- with pytest.raises(ValueError):
- server.sites.get_by_name("")
-
-
-@pytest.mark.filterwarnings("ignore:Tiered license level is set")
-@pytest.mark.filterwarnings("ignore:FlowsEnabled has been removed")
-def test_update(server: TSC.Server) -> None:
- response_xml = UPDATE_XML.read_text()
- with requests_mock.mock() as m:
- m.put(server.sites.baseurl + "/" + server.site_id, text=response_xml)
- single_site = TSC.SiteItem(
- name="Tableau",
- content_url="tableau",
- admin_mode=TSC.SiteItem.AdminMode.ContentAndUsers,
- user_quota=15,
- storage_quota=1000,
- disable_subscriptions=True,
- revision_history_enabled=False,
- data_acceleration_mode="disable",
- flow_auto_save_enabled=True,
- web_extraction_enabled=False,
- metrics_content_type_enabled=True,
- notify_site_admins_on_throttle=False,
- authoring_enabled=True,
- custom_subscription_email_enabled=True,
- custom_subscription_email="test@test.com",
- custom_subscription_footer_enabled=True,
- custom_subscription_footer="example_footer",
- ask_data_mode="EnabledByDefault",
- named_sharing_enabled=False,
- mobile_biometrics_enabled=True,
- sheet_image_enabled=False,
- derived_permissions_enabled=True,
- user_visibility_mode="FULL",
- use_default_time_zone=False,
- time_zone="America/Los_Angeles",
- auto_suspend_refresh_enabled=True,
- auto_suspend_refresh_inactivity_window=55,
- tier_creator_capacity=5,
- tier_explorer_capacity=5,
- tier_viewer_capacity=5,
- )
- single_site._id = server.site_id
- server.sites.parent_srv = server
- single_site = server.sites.update(single_site)
-
- assert server.site_id == single_site.id
- assert "tableau" == single_site.content_url
- assert "Suspended" == single_site.state
- assert "Tableau" == single_site.name
- assert "ContentAndUsers" == single_site.admin_mode
- assert single_site.revision_history_enabled is True
- assert 13 == single_site.revision_limit
- assert single_site.disable_subscriptions is True
- assert None == single_site.user_quota
- assert 5 == single_site.tier_creator_capacity
- assert 5 == single_site.tier_explorer_capacity
- assert 5 == single_site.tier_viewer_capacity
- assert "disable" == single_site.data_acceleration_mode
- assert single_site.flows_enabled is True
- assert single_site.cataloging_enabled is True
- assert single_site.flow_auto_save_enabled is True
- assert single_site.web_extraction_enabled is False
- assert single_site.metrics_content_type_enabled is True
- assert single_site.notify_site_admins_on_throttle is False
- assert single_site.authoring_enabled is True
- assert single_site.custom_subscription_email_enabled is True
- assert "test@test.com" == single_site.custom_subscription_email
- assert single_site.custom_subscription_footer_enabled is True
- assert "example_footer" == single_site.custom_subscription_footer
- assert "EnabledByDefault" == single_site.ask_data_mode
- assert single_site.named_sharing_enabled is False
- assert single_site.mobile_biometrics_enabled is True
- assert single_site.sheet_image_enabled is False
- assert single_site.derived_permissions_enabled is True
- assert "FULL" == single_site.user_visibility_mode
- assert single_site.use_default_time_zone is False
- assert "America/Los_Angeles" == single_site.time_zone
- assert single_site.auto_suspend_refresh_enabled is True
- assert 55 == single_site.auto_suspend_refresh_inactivity_window
-
-
-def test_update_missing_id(server: TSC.Server) -> None:
- single_site = TSC.SiteItem("test", "test")
- with pytest.raises(TSC.MissingRequiredFieldError):
- server.sites.update(single_site)
-
-
-def test_null_site_quota(server: TSC.Server) -> None:
- test_site = TSC.SiteItem("testname", "testcontenturl", tier_explorer_capacity=1, user_quota=None)
- assert test_site.tier_explorer_capacity == 1
- with pytest.raises(ValueError):
- test_site.user_quota = 1
- test_site.tier_explorer_capacity = None
- test_site.user_quota = 1
-
-
-def test_replace_license_tiers_with_user_quota(server: TSC.Server) -> None:
- test_site = TSC.SiteItem("testname", "testcontenturl", tier_explorer_capacity=1, user_quota=None)
- assert test_site.tier_explorer_capacity == 1
- with pytest.raises(ValueError):
- test_site.user_quota = 1
- test_site.replace_license_tiers_with_user_quota(1)
- assert 1 == test_site.user_quota
- assert test_site.tier_explorer_capacity is None
-
-
-@pytest.mark.filterwarnings("ignore:FlowsEnabled has been removed")
-def test_create(server: TSC.Server) -> None:
- response_xml = CREATE_XML.read_text()
- with requests_mock.mock() as m:
- m.post(server.sites.baseurl, text=response_xml)
- new_site = TSC.SiteItem(
- name="Tableau",
- content_url="tableau",
- admin_mode=TSC.SiteItem.AdminMode.ContentAndUsers,
- user_quota=15,
- storage_quota=1000,
- disable_subscriptions=True,
- )
- new_site = server.sites.create(new_site)
-
- new_site._tier_viewer_capacity = None
- new_site._tier_creator_capacity = None
- new_site._tier_explorer_capacity = None
- assert "0626857c-1def-4503-a7d8-7907c3ff9d9f" == new_site.id
- assert "tableau" == new_site.content_url
- assert "Tableau" == new_site.name
- assert "Active" == new_site.state
- assert "ContentAndUsers" == new_site.admin_mode
- assert new_site.revision_history_enabled is False
- assert new_site.subscribe_others_enabled is True
- assert new_site.disable_subscriptions is True
- assert 15 == new_site.user_quota
-
-
-def test_delete(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.delete(server.sites.baseurl + "/0626857c-1def-4503-a7d8-7907c3ff9d9f", status_code=204)
- server.sites.delete("0626857c-1def-4503-a7d8-7907c3ff9d9f")
-
-
-def test_delete_missing_id(server: TSC.Server) -> None:
- with pytest.raises(ValueError):
- server.sites.delete("")
-
-
-def test_encrypt(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.post(server.sites.baseurl + "/0626857c-1def-4503-a7d8-7907c3ff9d9f/encrypt-extracts", status_code=200)
- server.sites.encrypt_extracts("0626857c-1def-4503-a7d8-7907c3ff9d9f")
-
-
-def test_recrypt(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.post(server.sites.baseurl + "/0626857c-1def-4503-a7d8-7907c3ff9d9f/reencrypt-extracts", status_code=200)
- server.sites.re_encrypt_extracts("0626857c-1def-4503-a7d8-7907c3ff9d9f")
-
-
-def test_decrypt(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.post(server.sites.baseurl + "/0626857c-1def-4503-a7d8-7907c3ff9d9f/decrypt-extracts", status_code=200)
- server.sites.decrypt_extracts("0626857c-1def-4503-a7d8-7907c3ff9d9f")
-
-
-def test_list_auth_configurations(server: TSC.Server) -> None:
- server.version = "3.24"
- response_xml = SITE_AUTH_CONFIG_XML.read_text()
-
- assert server.sites.baseurl == server.sites.baseurl
-
- with requests_mock.mock() as m:
- m.get(f"{server.sites.baseurl}/{server.site_id}/site-auth-configurations", status_code=200, text=response_xml)
- configs = server.sites.list_auth_configurations()
-
- assert len(configs) == 2, "Expected 2 auth configurations"
-
- assert configs[0].auth_setting == "OIDC"
- assert configs[0].enabled
- assert configs[0].idp_configuration_id == "00000000-0000-0000-0000-000000000000"
- assert configs[0].idp_configuration_name == "Initial Salesforce"
- assert configs[0].known_provider_alias == "Salesforce"
- assert configs[1].auth_setting == "SAML"
- assert configs[1].enabled
- assert configs[1].idp_configuration_id == "11111111-1111-1111-1111-111111111111"
- assert configs[1].idp_configuration_name == "Initial SAML"
- assert configs[1].known_provider_alias is None
-
-
-@pytest.mark.parametrize("capture", [True, False, None])
-def test_parsing_attr_capture(capture):
- server = TSC.Server("http://test", False)
- server.version = "3.10"
- attrs = {"contentUrl": "test", "name": "test"}
- if capture is not None:
- attrs |= {"attributeCaptureEnabled": str(capture).lower()}
- xml = _utils.server_response_factory("site", **attrs)
- site = TSC.SiteItem.from_response(xml, server.namespace)[0]
-
- assert site.attribute_capture_enabled is capture, "Attribute capture not captured correctly"
-
-
-@pytest.mark.filterwarnings("ignore:FlowsEnabled has been removed")
-@pytest.mark.parametrize("req, capture", product(["create_req", "update_req"], [True, False, None]))
-def test_encoding_attr_capture(req, capture):
- site = TSC.SiteItem(
- content_url="test",
- name="test",
- attribute_capture_enabled=capture,
- )
- xml = getattr(RequestFactory.Site, req)(site)
- site_elem = ET.fromstring(xml).find(".//site")
- assert site_elem is not None, "Site element missing from XML body."
-
- if capture is not None:
- assert (
- site_elem.attrib["attributeCaptureEnabled"] == str(capture).lower()
- ), "Attribute capture not encoded correctly"
- else:
- assert "attributeCaptureEnabled" not in site_elem.attrib, "Attribute capture should not be encoded when None"
diff --git a/test/test_site_model.py b/test/test_site_model.py
deleted file mode 100644
index 14914b875..000000000
--- a/test/test_site_model.py
+++ /dev/null
@@ -1,73 +0,0 @@
-import pytest
-
-import tableauserverclient as TSC
-
-
-def test_invalid_name():
- with pytest.raises(ValueError):
- TSC.SiteItem(None, "url")
- with pytest.raises(ValueError):
- TSC.SiteItem("", "url")
- site = TSC.SiteItem("site", "url")
- with pytest.raises(ValueError):
- site.name = None
-
- with pytest.raises(ValueError):
- site.name = ""
-
-
-def test_invalid_admin_mode():
- site = TSC.SiteItem("site", "url")
- with pytest.raises(ValueError):
- site.admin_mode = "Hello"
-
-
-def test_invalid_content_url():
- with pytest.raises(ValueError):
- site = TSC.SiteItem(name="θ΅δ»η
", content_url="θ΅δ»η
")
-
- with pytest.raises(ValueError):
- site = TSC.SiteItem(name="θ΅δ»η
", content_url=None)
-
-
-def test_set_valid_content_url():
- # Default Site
- site = TSC.SiteItem(name="Default", content_url="")
- assert site.content_url == ""
-
- # Unicode Name and ascii content_url
- site = TSC.SiteItem(name="θ΅δ»η
", content_url="omlette")
- assert site.content_url == "omlette"
-
-
-def test_invalid_disable_subscriptions():
- site = TSC.SiteItem("site", "url")
- with pytest.raises(ValueError):
- site.disable_subscriptions = "Hello"
-
- with pytest.raises(ValueError):
- site.disable_subscriptions = None
-
-
-def test_invalid_revision_history_enabled():
- site = TSC.SiteItem("site", "url")
- with pytest.raises(ValueError):
- site.revision_history_enabled = "Hello"
-
- with pytest.raises(ValueError):
- site.revision_history_enabled = None
-
-
-def test_invalid_state():
- site = TSC.SiteItem("site", "url")
- with pytest.raises(ValueError):
- site.state = "Hello"
-
-
-def test_invalid_subscribe_others_enabled():
- site = TSC.SiteItem("site", "url")
- with pytest.raises(ValueError):
- site.subscribe_others_enabled = "Hello"
-
- with pytest.raises(ValueError):
- site.subscribe_others_enabled = None
diff --git a/test/test_sort.py b/test/test_sort.py
deleted file mode 100644
index f6ae576f4..000000000
--- a/test/test_sort.py
+++ /dev/null
@@ -1,107 +0,0 @@
-from urllib.parse import parse_qs
-
-import pytest
-import requests_mock
-
-import tableauserverclient as TSC
-
-
-@pytest.fixture(scope="function")
-def server():
- """Fixture to create a TSC.Server instance for testing."""
- server = TSC.Server("http://test", False)
-
- # Fake signin
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
-
- return server
-
-
-def test_empty_filter() -> None:
- with pytest.raises(TypeError):
- TSC.Filter("") # type: ignore
-
-
-def test_filter_equals(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.get(requests_mock.ANY)
- url = "http://test/api/2.3/sites/dad65087-b08b-4603-af4e-2887b8aafc67/workbooks"
- opts = TSC.RequestOptions(pagesize=13, pagenumber=13)
- opts.filter.add(TSC.Filter(TSC.RequestOptions.Field.Name, TSC.RequestOptions.Operator.Equals, "Superstore"))
-
- resp = server.workbooks.get_request(url, request_object=opts)
- query = parse_qs(resp.request.query)
- assert "pagenumber" in query
- assert query["pagenumber"] == ["13"]
- assert "pagesize" in query
- assert query["pagesize"] == ["13"]
- assert "filter" in query
- assert query["filter"] == ["name:eq:superstore"]
-
-
-def test_filter_equals_list() -> None:
- with pytest.raises(ValueError, match="Filter values can only be a list if the operator is 'in'.") as cm:
- TSC.Filter(TSC.RequestOptions.Field.Tags, TSC.RequestOptions.Operator.Equals, ["foo", "bar"])
-
-
-def test_filter_in(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.get(requests_mock.ANY)
- url = "http://test/api/2.3/sites/dad65087-b08b-4603-af4e-2887b8aafc67/workbooks"
- opts = TSC.RequestOptions(pagesize=13, pagenumber=13)
-
- opts.filter.add(TSC.Filter(TSC.RequestOptions.Field.Tags, TSC.RequestOptions.Operator.In, ["stocks", "market"]))
-
- resp = server.workbooks.get_request(url, request_object=opts)
- query = parse_qs(resp.request.query)
- assert "pagenumber" in query
- assert query["pagenumber"] == ["13"]
- assert "pagesize" in query
- assert query["pagesize"] == ["13"]
- assert "filter" in query
- assert query["filter"] == ["tags:in:[stocks,market]"]
-
-
-def test_sort_asc(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.get(requests_mock.ANY)
- url = "http://test/api/2.3/sites/dad65087-b08b-4603-af4e-2887b8aafc67/workbooks"
- opts = TSC.RequestOptions(pagesize=13, pagenumber=13)
- opts.sort.add(TSC.Sort(TSC.RequestOptions.Field.Name, TSC.RequestOptions.Direction.Asc))
-
- resp = server.workbooks.get_request(url, request_object=opts)
- query = parse_qs(resp.request.query)
- assert "pagenumber" in query
- assert query["pagenumber"] == ["13"]
- assert "pagesize" in query
- assert query["pagesize"] == ["13"]
- assert "sort" in query
- assert query["sort"] == ["name:asc"]
-
-
-def test_filter_combo(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.get(requests_mock.ANY)
- url = "http://test/api/2.3/sites/dad65087-b08b-4603-af4e-2887b8aafc67/users"
- opts = TSC.RequestOptions(pagesize=13, pagenumber=13)
-
- opts.filter.add(
- TSC.Filter(
- TSC.RequestOptions.Field.LastLogin,
- TSC.RequestOptions.Operator.GreaterThanOrEqual,
- "2017-01-15T00:00:00:00Z",
- )
- )
-
- opts.filter.add(TSC.Filter(TSC.RequestOptions.Field.SiteRole, TSC.RequestOptions.Operator.Equals, "Publisher"))
-
- resp = server.workbooks.get_request(url, request_object=opts)
-
- query = parse_qs(resp.request.query)
- assert "pagenumber" in query
- assert query["pagenumber"] == ["13"]
- assert "pagesize" in query
- assert query["pagesize"] == ["13"]
- assert "filter" in query
- assert query["filter"] == ["lastlogin:gte:2017-01-15t00:00:00:00z,siterole:eq:publisher"]
diff --git a/test/test_ssl_config.py b/test/test_ssl_config.py
deleted file mode 100644
index 28ef3fc5e..000000000
--- a/test/test_ssl_config.py
+++ /dev/null
@@ -1,68 +0,0 @@
-import logging
-from unittest.mock import MagicMock
-
-import pytest
-
-import tableauserverclient as TSC
-
-
-@pytest.fixture(scope="function")
-def server():
- """Fixture to create a TSC.Server instance for testing."""
- server = TSC.Server("http://test", False)
-
- # Fake signin
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
-
- return server
-
-
-def test_default_ssl_config(server):
- """Test that by default, no custom SSL context is used"""
- assert server._ssl_context is None
- assert "verify" not in server.http_options
-
-
-def test_weak_dh_config(server, monkeypatch):
- """Test that weak DH keys can be allowed when configured"""
- mock_context = MagicMock()
- mock_create_context = MagicMock(return_value=mock_context)
- monkeypatch.setattr("ssl.create_default_context", mock_create_context)
-
- server.configure_ssl(allow_weak_dh=True)
-
- mock_create_context.assert_called_once()
- mock_context.set_dh_parameters.assert_called_once_with(min_key_bits=512)
- assert server.http_options["verify"] == mock_context
-
-
-def test_disable_weak_dh_config(server, monkeypatch):
- """Test that SSL config can be reset to defaults"""
- mock_context = MagicMock()
- mock_create_context = MagicMock(return_value=mock_context)
- monkeypatch.setattr("ssl.create_default_context", mock_create_context)
-
- # First enable weak DH
- server.configure_ssl(allow_weak_dh=True)
- assert server._ssl_context is not None
- assert "verify" in server.http_options
-
- # Then disable it
- server.configure_ssl(allow_weak_dh=False)
- assert server._ssl_context is None
- assert "verify" not in server.http_options
-
-
-def test_warning_on_weak_dh(server, monkeypatch, caplog):
- """Test that a warning is logged when enabling weak DH keys"""
- mock_context = MagicMock()
- mock_create_context = MagicMock(return_value=mock_context)
- monkeypatch.setattr("ssl.create_default_context", mock_create_context)
-
- with caplog.at_level(logging.WARNING):
- server.configure_ssl(allow_weak_dh=True)
-
- assert any(
- "Allowing weak Diffie-Hellman keys" in record.getMessage() for record in caplog.records
- ), "Expected warning about weak DH keys was not logged"
diff --git a/test/test_subscription.py b/test/test_subscription.py
deleted file mode 100644
index 7c78cc57d..000000000
--- a/test/test_subscription.py
+++ /dev/null
@@ -1,102 +0,0 @@
-from pathlib import Path
-
-import pytest
-import requests_mock
-
-import tableauserverclient as TSC
-
-TEST_ASSET_DIR = Path(__file__).parent / "assets"
-
-CREATE_XML = TEST_ASSET_DIR / "subscription_create.xml"
-GET_XML = TEST_ASSET_DIR / "subscription_get.xml"
-GET_XML_BY_ID = TEST_ASSET_DIR / "subscription_get_by_id.xml"
-
-
-@pytest.fixture(scope="function")
-def server():
- """Fixture to create a TSC.Server instance for testing."""
- server = TSC.Server("http://test", False)
-
- # Fake signin
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
- server.version = "2.6"
-
- return server
-
-
-def test_get_subscriptions(server: TSC.Server) -> None:
- response_xml = GET_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.subscriptions.baseurl, text=response_xml)
- all_subscriptions, pagination_item = server.subscriptions.get()
-
- assert 2 == pagination_item.total_available
- subscription = all_subscriptions[0]
- assert "382e9a6e-0c08-4a95-b6c1-c14df7bac3e4" == subscription.id
- assert "NOT FOUND!" == subscription.message
- assert subscription.attach_image is True
- assert subscription.attach_pdf is False
- assert subscription.suspended is False
- assert subscription.send_if_view_empty is False
- assert subscription.page_orientation is None
- assert subscription.page_size_option is None
- assert "Not Found Alert" == subscription.subject
- assert "cdd716ca-5818-470e-8bec-086885dbadee" == subscription.target.id
- assert "View" == subscription.target.type
- assert "c0d5fc44-ad8c-4957-bec0-b70ed0f8df1e" == subscription.user_id
- assert "7617c389-cdca-4940-a66e-69956fcebf3e" == subscription.schedule_id
-
- subscription = all_subscriptions[1]
- assert "23cb7630-afc8-4c8e-b6cd-83ae0322ec66" == subscription.id
- assert "overview" == subscription.message
- assert subscription.attach_image is False
- assert subscription.attach_pdf is True
- assert subscription.suspended is True
- assert subscription.send_if_view_empty is True
- assert "PORTRAIT" == subscription.page_orientation
- assert "A5" == subscription.page_size_option
- assert "Last 7 Days" == subscription.subject
- assert "2e6b4e8f-22dd-4061-8f75-bf33703da7e5" == subscription.target.id
- assert "Workbook" == subscription.target.type
- assert "c0d5fc44-ad8c-4957-bec0-b70ed0f8df1e" == subscription.user_id
- assert "3407cd38-7b39-4983-86a6-67a1506a5e3f" == subscription.schedule_id
-
-
-def test_get_subscription_by_id(server: TSC.Server) -> None:
- response_xml = GET_XML_BY_ID.read_text()
- with requests_mock.mock() as m:
- m.get(server.subscriptions.baseurl + "/382e9a6e-0c08-4a95-b6c1-c14df7bac3e4", text=response_xml)
- subscription = server.subscriptions.get_by_id("382e9a6e-0c08-4a95-b6c1-c14df7bac3e4")
-
- assert "382e9a6e-0c08-4a95-b6c1-c14df7bac3e4" == subscription.id
- assert "View" == subscription.target.type
- assert "cdd716ca-5818-470e-8bec-086885dbadee" == subscription.target.id
- assert "c0d5fc44-ad8c-4957-bec0-b70ed0f8df1e" == subscription.user_id
- assert "Not Found Alert" == subscription.subject
- assert "7617c389-cdca-4940-a66e-69956fcebf3e" == subscription.schedule_id
-
-
-def test_create_subscription(server: TSC.Server) -> None:
- response_xml = CREATE_XML.read_text()
- with requests_mock.mock() as m:
- m.post(server.subscriptions.baseurl, text=response_xml)
-
- target_item = TSC.Target("960e61f2-1838-40b2-bba2-340c9492f943", "workbook")
- new_subscription = TSC.SubscriptionItem(
- "subject", "4906c453-d5ec-4972-9ff4-789b629bdfa2", "8d30c8de-0a5f-4bee-b266-c621b4f3eed0", target_item
- )
- new_subscription = server.subscriptions.create(new_subscription)
-
- assert "78e9318d-2d29-4d67-b60f-3f2f5fd89ecc" == new_subscription.id
- assert "sub_name" == new_subscription.subject
- assert "960e61f2-1838-40b2-bba2-340c9492f943" == new_subscription.target.id
- assert "Workbook" == new_subscription.target.type
- assert "4906c453-d5ec-4972-9ff4-789b629bdfa2" == new_subscription.schedule_id
- assert "8d30c8de-0a5f-4bee-b266-c621b4f3eed0" == new_subscription.user_id
-
-
-def test_delete_subscription(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.delete(server.subscriptions.baseurl + "/78e9318d-2d29-4d67-b60f-3f2f5fd89ecc", status_code=204)
- server.subscriptions.delete("78e9318d-2d29-4d67-b60f-3f2f5fd89ecc")
diff --git a/test/test_table.py b/test/test_table.py
deleted file mode 100644
index 2f3c3c8d6..000000000
--- a/test/test_table.py
+++ /dev/null
@@ -1,65 +0,0 @@
-from pathlib import Path
-
-import pytest
-import requests_mock
-
-import tableauserverclient as TSC
-
-TEST_ASSET_DIR = Path(__file__).parent / "assets"
-
-GET_XML = TEST_ASSET_DIR / "table_get.xml"
-UPDATE_XML = TEST_ASSET_DIR / "table_update.xml"
-
-
-@pytest.fixture(scope="function")
-def server():
- """Fixture to create a TSC.Server instance for testing."""
- server = TSC.Server("http://test", False)
-
- # Fake signin
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
- server.version = "3.5"
-
- return server
-
-
-def test_get(server: TSC.Server) -> None:
- response_xml = GET_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.tables.baseurl, text=response_xml)
- all_tables, pagination_item = server.tables.get()
-
- assert 4 == pagination_item.total_available
- assert "10224773-ecee-42ac-b822-d786b0b8e4d9" == all_tables[0].id
- assert "dim_Product" == all_tables[0].name
-
- assert "53c77bc1-fb41-4342-a75a-f68ac0656d0d" == all_tables[1].id
- assert "customer" == all_tables[1].name
- assert "dbo" == all_tables[1].schema
- assert "9324cf6b-ba72-4b8e-b895-ac3f28d2f0e0" == all_tables[1].contact_id
- assert False == all_tables[1].certified
-
-
-def test_update(server: TSC.Server) -> None:
- response_xml = UPDATE_XML.read_text()
- with requests_mock.mock() as m:
- m.put(server.tables.baseurl + "/10224773-ecee-42ac-b822-d786b0b8e4d9", text=response_xml)
- single_table = TSC.TableItem("test")
- single_table._id = "10224773-ecee-42ac-b822-d786b0b8e4d9"
-
- single_table.contact_id = "8e1a8235-c9ee-4d61-ae82-2ffacceed8e0"
- single_table.certified = True
- single_table.certification_note = "Test"
- single_table = server.tables.update(single_table)
-
- assert "10224773-ecee-42ac-b822-d786b0b8e4d9" == single_table.id
- assert "8e1a8235-c9ee-4d61-ae82-2ffacceed8e0" == single_table.contact_id
- assert True == single_table.certified
- assert "Test" == single_table.certification_note
-
-
-def test_delete(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.delete(server.tables.baseurl + "/0448d2ed-590d-4fa0-b272-a2a8a24555b5", status_code=204)
- server.tables.delete("0448d2ed-590d-4fa0-b272-a2a8a24555b5")
diff --git a/test/test_tableauauth_model.py b/test/test_tableauauth_model.py
deleted file mode 100644
index 17db52770..000000000
--- a/test/test_tableauauth_model.py
+++ /dev/null
@@ -1,8 +0,0 @@
-import pytest
-
-import tableauserverclient as TSC
-
-
-def test_username_password_required():
- with pytest.raises(TypeError):
- TSC.TableauAuth()
diff --git a/test/test_tagging.py b/test/test_tagging.py
deleted file mode 100644
index 87f444fce..000000000
--- a/test/test_tagging.py
+++ /dev/null
@@ -1,282 +0,0 @@
-from contextlib import ExitStack
-import re
-from collections.abc import Iterable
-from typing import Protocol
-import uuid
-from xml.etree import ElementTree as ET
-
-import pytest
-import requests_mock
-import tableauserverclient as TSC
-
-
-@pytest.fixture
-def get_server() -> TSC.Server:
- server = TSC.Server("http://test", False)
-
- # Fake sign in
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
- server.version = "3.28"
- return server
-
-
-def add_tag_xml_response_factory(tags: Iterable[str] | str) -> str:
- if isinstance(tags, str):
- tags = [tags]
- root = ET.Element("tsResponse")
- tags_element = ET.SubElement(root, "tags")
- for tag in tags:
- tag_element = ET.SubElement(tags_element, "tag")
- tag_element.attrib["label"] = tag
- root.attrib["xmlns"] = "http://tableau.com/api"
- return ET.tostring(root, encoding="utf-8").decode("utf-8")
-
-
-def batch_add_tags_xml_response_factory(tags, content):
- root = ET.Element("tsResponse")
- tag_batch = ET.SubElement(root, "tagBatch")
- tags_element = ET.SubElement(tag_batch, "tags")
- for tag in tags:
- tag_element = ET.SubElement(tags_element, "tag")
- tag_element.attrib["label"] = tag
- contents_element = ET.SubElement(tag_batch, "contents")
- for item in content:
- content_elem = ET.SubElement(contents_element, "content")
- content_elem.attrib["id"] = item.id or "some_id"
- t = item.__class__.__name__.replace("Item", "") or ""
- content_elem.attrib["contentType"] = t
- root.attrib["xmlns"] = "http://tableau.com/api"
- return ET.tostring(root, encoding="utf-8").decode("utf-8")
-
-
-def make_workbook() -> TSC.WorkbookItem:
- workbook = TSC.WorkbookItem("project", "test")
- workbook._id = str(uuid.uuid4())
- return workbook
-
-
-def make_view() -> TSC.ViewItem:
- view = TSC.ViewItem()
- view._id = str(uuid.uuid4())
- return view
-
-
-def make_datasource() -> TSC.DatasourceItem:
- datasource = TSC.DatasourceItem("project", "test")
- datasource._id = str(uuid.uuid4())
- return datasource
-
-
-def make_table() -> TSC.TableItem:
- table = TSC.TableItem("project", "test")
- table._id = str(uuid.uuid4())
- return table
-
-
-def make_database() -> TSC.DatabaseItem:
- database = TSC.DatabaseItem("project", "test")
- database._id = str(uuid.uuid4())
- return database
-
-
-def make_flow() -> TSC.FlowItem:
- flow = TSC.FlowItem("project", "test")
- flow._id = str(uuid.uuid4())
- return flow
-
-
-def make_vconn() -> TSC.VirtualConnectionItem:
- vconn = TSC.VirtualConnectionItem("test")
- vconn._id = str(uuid.uuid4())
- return vconn
-
-
-sample_taggable_items = (
- [
- ("workbooks", make_workbook()),
- ("workbooks", "some_id"),
- ("views", make_view()),
- ("views", "some_id"),
- ("datasources", make_datasource()),
- ("datasources", "some_id"),
- ("tables", make_table()),
- ("tables", "some_id"),
- ("databases", make_database()),
- ("databases", "some_id"),
- ("flows", make_flow()),
- ("flows", "some_id"),
- ("virtual_connections", make_vconn()),
- ("virtual_connections", "some_id"),
- ],
-)
-
-sample_tags = [
- "a",
- ["a", "b"],
- ["a", "b", "c", "c"],
-]
-
-
-@pytest.mark.parametrize("endpoint_type, item", *sample_taggable_items)
-@pytest.mark.parametrize("tags", sample_tags)
-def test_add_tags(get_server, endpoint_type, item, tags) -> None:
- add_tags_xml = add_tag_xml_response_factory(tags)
- endpoint = getattr(get_server, endpoint_type)
- id_ = getattr(item, "id", item)
-
- with requests_mock.mock() as m:
- m.put(
- f"{endpoint.baseurl}/{id_}/tags",
- status_code=200,
- text=add_tags_xml,
- )
- tag_result = endpoint.add_tags(item, tags)
-
- if isinstance(tags, str):
- tags = [tags]
- assert set(tag_result) == set(tags)
-
-
-@pytest.mark.parametrize("endpoint_type, item", *sample_taggable_items)
-@pytest.mark.parametrize("tags", sample_tags)
-def test_delete_tags(get_server, endpoint_type, item, tags) -> None:
- add_tags_xml = add_tag_xml_response_factory(tags)
- endpoint = getattr(get_server, endpoint_type)
- id_ = getattr(item, "id", item)
-
- if isinstance(tags, str):
- tags = [tags]
- tag_paths = "|".join(tags)
- tag_paths = f"({tag_paths})"
- matcher = re.compile(rf"{endpoint.baseurl}\/{id_}\/tags\/{tag_paths}")
- with requests_mock.mock() as m:
- m.delete(
- matcher,
- status_code=200,
- text=add_tags_xml,
- )
- endpoint.delete_tags(item, tags)
- history = m.request_history
-
- tag_set = set(tags)
- assert len(history) == len(tag_set)
- urls = {r.url.split("/")[-1] for r in history}
- assert urls == tag_set
-
-
-@pytest.mark.parametrize("endpoint_type, item", *sample_taggable_items)
-@pytest.mark.parametrize("tags", sample_tags)
-def test_update_tags(get_server, endpoint_type, item, tags) -> None:
- endpoint = getattr(get_server, endpoint_type)
- id_ = getattr(item, "id", item)
- tags = set([tags] if isinstance(tags, str) else tags)
- with ExitStack() as stack:
- if isinstance(item, str):
- stack.enter_context(pytest.raises((ValueError, NotImplementedError)))
- elif hasattr(item, "_initial_tags"):
- initial_tags = {"x", "y", "z"}
- item._initial_tags = initial_tags
- add_tags_xml = add_tag_xml_response_factory(tags - initial_tags)
- delete_tags_xml = add_tag_xml_response_factory(initial_tags - tags)
- m = stack.enter_context(requests_mock.mock())
- m.put(
- f"{endpoint.baseurl}/{id_}/tags",
- status_code=200,
- text=add_tags_xml,
- )
-
- tag_paths = "|".join(initial_tags - tags)
- tag_paths = f"({tag_paths})"
- matcher = re.compile(rf"{endpoint.baseurl}\/{id_}\/tags\/{tag_paths}")
- m.delete(
- matcher,
- status_code=200,
- text=delete_tags_xml,
- )
-
- else:
- stack.enter_context(pytest.raises(NotImplementedError))
-
- endpoint.update_tags(item)
-
-
-class HasID(Protocol):
- @property
- def id(self) -> str | None: ...
-
-
-def test_tags_batch_add(get_server) -> None:
- server = get_server
- content: list[HasID] = [make_workbook(), make_view(), make_datasource(), make_table(), make_database()]
- tags = ["a", "b"]
- add_tags_xml = batch_add_tags_xml_response_factory(tags, content)
- with requests_mock.mock() as m:
- m.put(
- f"{server.tags.baseurl}:batchCreate",
- status_code=200,
- text=add_tags_xml,
- )
- tag_result = server.tags.batch_add(tags, content)
- history = m.request_history
-
- assert set(tag_result) == set(tags)
- assert len(history) == 1
- body = ET.fromstring(history[0].body)
- id_types = {c.id: c.__class__.__name__.replace("Item", "") for c in content}
- for tag in body.findall(".//content"):
- content_type = tag.attrib.get("contentType", "")
- content_id = tag.attrib.get("id", "")
- assert content_type == id_types.get(content_id, ""), f"Content type mismatch for {content_id}"
-
-
-def test_tags_batch_delete(get_server) -> None:
- server = get_server
- content = [make_workbook(), make_view(), make_datasource(), make_table(), make_database()]
- tags = ["a", "b"]
- add_tags_xml = batch_add_tags_xml_response_factory(tags, content)
- with requests_mock.mock() as m:
- m.put(
- f"{server.tags.baseurl}:batchDelete",
- status_code=200,
- text=add_tags_xml,
- )
- tag_result = server.tags.batch_delete(tags, content)
-
- assert set(tag_result) == set(tags)
-
-
-def test_tag_with_spaces_is_quoted_in_request() -> None:
- """Tags containing spaces must be quoted in the XML request to prevent server-side splitting."""
- from tableauserverclient.server.request_factory import RequestFactory
-
- tag_set = {"Yearly Sales", "simple"}
- xml_bytes = RequestFactory.Tag.add_req(tag_set)
- root = ET.fromstring(xml_bytes)
- labels = {tag.get("label") for tag in root.findall(".//tag")}
- assert '"Yearly Sales"' in labels
- assert "simple" in labels
-
-
-@pytest.mark.parametrize(
- "tag, expected_encoded",
- [
- ("tag#name", "tag%23name"), # issue #675: hash must be percent-encoded
- ("tag.name", "tag.name"), # issue #994: dot is safe, no encoding needed
- ("tag+name", "tag%2Bname"), # plus must be percent-encoded
- ("tag/name", "tag%2Fname"), # slash must be percent-encoded (safe='' fix)
- ("tag name", "tag%20name"), # space must be percent-encoded
- ],
-)
-def test_delete_tags_special_characters_encoded(get_server, tag, expected_encoded) -> None:
- """Verify delete_tags percent-encodes special characters in the tag path segment."""
- server = get_server
- workbook = make_workbook()
-
- with requests_mock.mock() as m:
- m.delete(requests_mock.ANY, status_code=200)
- server.workbooks.delete_tags(workbook, tag)
- history = m.request_history
-
- assert len(history) == 1
- assert history[0].url.endswith(f"/tags/{expected_encoded}")
diff --git a/test/test_task.py b/test/test_task.py
deleted file mode 100644
index fb99d58e4..000000000
--- a/test/test_task.py
+++ /dev/null
@@ -1,199 +0,0 @@
-from datetime import time
-from pathlib import Path
-
-import pytest
-import requests_mock
-
-import tableauserverclient as TSC
-from tableauserverclient.datetime_helpers import parse_datetime
-from tableauserverclient.models.task_item import TaskItem
-
-TEST_ASSET_DIR = Path(__file__).parent / "assets"
-
-GET_XML_NO_WORKBOOK = TEST_ASSET_DIR / "tasks_no_workbook_or_datasource.xml"
-GET_XML_WITH_WORKBOOK = TEST_ASSET_DIR / "tasks_with_workbook.xml"
-GET_XML_WITH_DATASOURCE = TEST_ASSET_DIR / "tasks_with_datasource.xml"
-GET_XML_WITH_WORKBOOK_AND_DATASOURCE = TEST_ASSET_DIR / "tasks_with_workbook_and_datasource.xml"
-GET_XML_DATAACCELERATION_TASK = TEST_ASSET_DIR / "tasks_with_dataacceleration_task.xml"
-GET_XML_RUN_NOW_RESPONSE = TEST_ASSET_DIR / "tasks_run_now_response.xml"
-GET_XML_CREATE_TASK_RESPONSE = TEST_ASSET_DIR / "tasks_create_extract_task.xml"
-GET_XML_WITHOUT_SCHEDULE = TEST_ASSET_DIR / "tasks_without_schedule.xml"
-GET_XML_WITH_INTERVAL = TEST_ASSET_DIR / "tasks_with_interval.xml"
-
-
-@pytest.fixture(scope="function")
-def server():
- """Fixture to create a TSC.Server instance for testing."""
- server = TSC.Server("http://test", False)
-
- # Fake signin
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
- server.version = "3.19"
-
- return server
-
-
-@pytest.fixture(scope="function")
-def baseurl(server: TSC.Server) -> str:
- return f"{server.tasks.baseurl}/extractRefreshes"
-
-
-def test_get_tasks_with_no_workbook(server: TSC.Server, baseurl: str) -> None:
- response_xml = GET_XML_NO_WORKBOOK.read_text()
- with requests_mock.mock() as m:
- m.get(baseurl, text=response_xml)
- all_tasks, pagination_item = server.tasks.get()
-
- task = all_tasks[0]
- assert task.target is None
-
-
-def test_get_tasks_with_workbook(server: TSC.Server, baseurl: str) -> None:
- response_xml = GET_XML_WITH_WORKBOOK.read_text()
- with requests_mock.mock() as m:
- m.get(baseurl, text=response_xml)
- all_tasks, pagination_item = server.tasks.get()
-
- task = all_tasks[0]
- assert "c7a9327e-1cda-4504-b026-ddb43b976d1d" == task.target.id
- assert "workbook" == task.target.type
-
-
-def test_get_tasks_with_datasource(server: TSC.Server, baseurl: str) -> None:
- response_xml = GET_XML_WITH_DATASOURCE.read_text()
- with requests_mock.mock() as m:
- m.get(baseurl, text=response_xml)
- all_tasks, pagination_item = server.tasks.get()
-
- task = all_tasks[0]
- assert "c7a9327e-1cda-4504-b026-ddb43b976d1d" == task.target.id
- assert "datasource" == task.target.type
-
-
-def test_get_tasks_with_workbook_and_datasource(server: TSC.Server, baseurl: str) -> None:
- response_xml = GET_XML_WITH_WORKBOOK_AND_DATASOURCE.read_text()
- with requests_mock.mock() as m:
- m.get(baseurl, text=response_xml)
- all_tasks, pagination_item = server.tasks.get()
-
- assert "workbook" == all_tasks[0].target.type
- assert "datasource" == all_tasks[1].target.type
- assert "workbook" == all_tasks[2].target.type
-
-
-def test_get_task_with_schedule(server: TSC.Server, baseurl: str) -> None:
- response_xml = GET_XML_WITH_WORKBOOK.read_text()
- with requests_mock.mock() as m:
- m.get(baseurl, text=response_xml)
- all_tasks, pagination_item = server.tasks.get()
-
- task = all_tasks[0]
- assert "c7a9327e-1cda-4504-b026-ddb43b976d1d" == task.target.id
- assert "workbook" == task.target.type
- assert "b60b4efd-a6f7-4599-beb3-cb677e7abac1" == task.schedule_id
-
-
-def test_get_task_without_schedule(server: TSC.Server, baseurl: str) -> None:
- with requests_mock.mock() as m:
- m.get(baseurl, text=GET_XML_WITHOUT_SCHEDULE.read_text())
- all_tasks, pagination_item = server.tasks.get()
-
- task = all_tasks[0]
- assert "c7a9327e-1cda-4504-b026-ddb43b976d1d" == task.target.id
- assert "datasource" == task.target.type
-
-
-def test_get_task_with_interval(server: TSC.Server, baseurl: str) -> None:
- with requests_mock.mock() as m:
- m.get(baseurl, text=GET_XML_WITH_INTERVAL.read_text())
- all_tasks, pagination_item = server.tasks.get()
-
- task = all_tasks[0]
- assert "e4de0575-fcc7-4232-5659-be09bb8e7654" == task.target.id
- assert "datasource" == task.target.type
-
-
-def test_delete(server: TSC.Server, baseurl: str) -> None:
- with requests_mock.mock() as m:
- m.delete(baseurl + "/c7a9327e-1cda-4504-b026-ddb43b976d1d", status_code=204)
- server.tasks.delete("c7a9327e-1cda-4504-b026-ddb43b976d1d")
-
-
-def test_delete_missing_id(server: TSC.Server, baseurl: str) -> None:
- with pytest.raises(ValueError):
- server.tasks.delete("")
-
-
-def test_get_materializeviews_tasks(server: TSC.Server, baseurl: str) -> None:
- response_xml = GET_XML_DATAACCELERATION_TASK.read_text()
- with requests_mock.mock() as m:
- m.get(f"{server.tasks.baseurl}/{TaskItem.Type.DataAcceleration}", text=response_xml)
- all_tasks, pagination_item = server.tasks.get(task_type=TaskItem.Type.DataAcceleration)
-
- task = all_tasks[0]
- assert "a462c148-fc40-4670-a8e4-39b7f0c58c7f" == task.target.id
- assert "workbook" == task.target.type
- assert "b22190b4-6ac2-4eed-9563-4afc03444413" == task.schedule_id
- assert parse_datetime("2019-12-09T22:30:00Z") == task.schedule_item.next_run_at
- assert parse_datetime("2019-12-09T20:45:04Z") == task.last_run_at
- assert TSC.TaskItem.Type.DataAcceleration == task.task_type
-
-
-def test_delete_data_acceleration(server: TSC.Server, baseurl: str) -> None:
- with requests_mock.mock() as m:
- m.delete(
- "{}/{}/{}".format(
- server.tasks.baseurl, TaskItem.Type.DataAcceleration, "c9cff7f9-309c-4361-99ff-d4ba8c9f5467"
- ),
- status_code=204,
- )
- server.tasks.delete("c9cff7f9-309c-4361-99ff-d4ba8c9f5467", TaskItem.Type.DataAcceleration)
-
-
-def test_get_by_id(server: TSC.Server, baseurl: str) -> None:
- response_xml = GET_XML_WITH_WORKBOOK.read_text()
- task_id = "f84901ac-72ad-4f9b-a87e-7a3500402ad6"
- with requests_mock.mock() as m:
- m.get(f"{baseurl}/{task_id}", text=response_xml)
- task = server.tasks.get_by_id(task_id)
-
- assert "c7a9327e-1cda-4504-b026-ddb43b976d1d" == task.target.id
- assert "workbook" == task.target.type
- assert "b60b4efd-a6f7-4599-beb3-cb677e7abac1" == task.schedule_id
- assert TSC.TaskItem.Type.ExtractRefresh == task.task_type
-
-
-def test_run_now(server: TSC.Server, baseurl: str) -> None:
- task_id = "f84901ac-72ad-4f9b-a87e-7a3500402ad6"
- task = TaskItem(task_id, TaskItem.Type.ExtractRefresh, 100)
- response_xml = GET_XML_RUN_NOW_RESPONSE.read_text()
- with requests_mock.mock() as m:
- m.post(f"{baseurl}/{task_id}/runNow", text=response_xml)
- job_response_content = server.tasks.run(task).decode("utf-8")
-
- assert "7b6b59a8-ac3c-4d1d-2e9e-0b5b4ba8a7b6" in job_response_content
- assert "RefreshExtract" in job_response_content
-
-
-def test_create_extract_task(server: TSC.Server, baseurl: str) -> None:
- monthly_interval = TSC.MonthlyInterval(start_time=time(23, 30), interval_value=15)
- monthly_schedule = TSC.ScheduleItem(
- None, # type: ignore[arg-type]
- None, # type: ignore[arg-type]
- None, # type: ignore[arg-type]
- None, # type: ignore[arg-type]
- monthly_interval,
- )
- target_item = TSC.Target("workbook_id", "workbook")
-
- task = TaskItem(None, "FullRefresh", None, schedule_item=monthly_schedule, target=target_item) # type: ignore[arg-type]
-
- response_xml = GET_XML_CREATE_TASK_RESPONSE.read_text()
- with requests_mock.mock() as m:
- m.post(f"{baseurl}", text=response_xml)
- create_response_content = server.tasks.create(task).decode("utf-8")
-
- assert "task_id" in create_response_content
- assert "workbook_id" in create_response_content
- assert "FullRefresh" in create_response_content
diff --git a/test/test_user.py b/test/test_user.py
deleted file mode 100644
index fec21d25f..000000000
--- a/test/test_user.py
+++ /dev/null
@@ -1,600 +0,0 @@
-import csv
-import io
-from pathlib import Path
-import re
-from unittest.mock import patch
-from pathlib import Path
-
-from defusedxml import ElementTree as ET
-import pytest
-import requests_mock
-
-import tableauserverclient as TSC
-from tableauserverclient.datetime_helpers import format_datetime, parse_datetime
-from tableauserverclient.server.endpoint.users_endpoint import create_users_csv, remove_users_csv
-
-TEST_ASSET_DIR = Path(__file__).parent / "assets"
-
-BULK_ADD_XML = TEST_ASSET_DIR / "users_bulk_add_job.xml"
-GET_XML = TEST_ASSET_DIR / "user_get.xml"
-GET_XML_ALL_FIELDS = TEST_ASSET_DIR / "user_get_all_fields.xml"
-GET_EMPTY_XML = TEST_ASSET_DIR / "user_get_empty.xml"
-GET_BY_ID_XML = TEST_ASSET_DIR / "user_get_by_id.xml"
-UPDATE_XML = TEST_ASSET_DIR / "user_update.xml"
-ADD_XML = TEST_ASSET_DIR / "user_add.xml"
-POPULATE_WORKBOOKS_XML = TEST_ASSET_DIR / "user_populate_workbooks.xml"
-GET_FAVORITES_XML = TEST_ASSET_DIR / "favorites_get.xml"
-POPULATE_GROUPS_XML = TEST_ASSET_DIR / "user_populate_groups.xml"
-
-USERNAMES = TEST_ASSET_DIR / "Data" / "usernames.csv"
-USERS = TEST_ASSET_DIR / "Data" / "user_details.csv"
-
-
-def make_user(
- name: str,
- site_role: str = "",
- auth_setting: str = "",
- domain: str = "",
- fullname: str = "",
- email: str = "",
- idp_id: str = "",
-) -> TSC.UserItem:
- user = TSC.UserItem(name, site_role or None)
- if auth_setting:
- user.auth_setting = auth_setting
- if domain:
- user._domain_name = domain
- if fullname:
- user.fullname = fullname
- if email:
- user.email = email
- if idp_id:
- user.idp_configuration_id = idp_id
- return user
-
-
-@pytest.fixture(scope="function")
-def server():
- """Fixture to create a TSC.Server instance for testing."""
- server = TSC.Server("http://test", False)
-
- # Fake signin
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
-
- return server
-
-
-def test_get(server: TSC.Server) -> None:
- response_xml = GET_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.users.baseurl + "?fields=_all_", text=response_xml)
- all_users, pagination_item = server.users.get()
-
- assert 2 == pagination_item.total_available
- assert 2 == len(all_users)
-
- assert any(user.id == "dd2239f6-ddf1-4107-981a-4cf94e415794" for user in all_users)
- single_user = next(user for user in all_users if user.id == "dd2239f6-ddf1-4107-981a-4cf94e415794")
- assert "alice" == single_user.name
- assert "Publisher" == single_user.site_role
- assert "2016-08-16T23:17:06Z" == format_datetime(single_user.last_login)
- assert "alice cook" == single_user.fullname
- assert "alicecook@test.com" == single_user.email
-
- assert any(user.id == "2a47bbf8-8900-4ebb-b0a4-2723bd7c46c3" for user in all_users)
- single_user = next(user for user in all_users if user.id == "2a47bbf8-8900-4ebb-b0a4-2723bd7c46c3")
- assert "Bob" == single_user.name
- assert "Interactor" == single_user.site_role
- assert "Bob Smith" == single_user.fullname
- assert "bob@test.com" == single_user.email
-
-
-def test_get_empty(server: TSC.Server) -> None:
- response_xml = GET_EMPTY_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.users.baseurl, text=response_xml)
- all_users, pagination_item = server.users.get()
-
- assert 0 == pagination_item.total_available
- assert [] == all_users
-
-
-def test_get_before_signin(server: TSC.Server) -> None:
- server._auth_token = None
- with pytest.raises(TSC.NotSignedInError):
- server.users.get()
-
-
-def test_get_by_id(server: TSC.Server) -> None:
- response_xml = GET_BY_ID_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.users.baseurl + "/dd2239f6-ddf1-4107-981a-4cf94e415794", text=response_xml)
- single_user = server.users.get_by_id("dd2239f6-ddf1-4107-981a-4cf94e415794")
-
- assert "dd2239f6-ddf1-4107-981a-4cf94e415794" == single_user.id
- assert "alice" == single_user.name
- assert "Alice" == single_user.fullname
- assert "Publisher" == single_user.site_role
- assert "ServerDefault" == single_user.auth_setting
- assert "2016-08-16T23:17:06Z" == format_datetime(single_user.last_login)
- assert "local" == single_user.domain_name
-
-
-def test_get_by_id_missing_id(server: TSC.Server) -> None:
- with pytest.raises(ValueError):
- server.users.get_by_id("")
-
-
-def test_update(server: TSC.Server) -> None:
- response_xml = UPDATE_XML.read_text()
- with requests_mock.mock() as m:
- m.put(server.users.baseurl + "/dd2239f6-ddf1-4107-981a-4cf94e415794", text=response_xml)
- single_user = TSC.UserItem("test", "Viewer")
- single_user._id = "dd2239f6-ddf1-4107-981a-4cf94e415794"
- single_user.name = "Cassie"
- single_user.fullname = "Cassie"
- single_user.email = "cassie@email.com"
- single_user = server.users.update(single_user)
-
- assert "Cassie" == single_user.name
- assert "Cassie" == single_user.fullname
- assert "cassie@email.com" == single_user.email
- assert "Viewer" == single_user.site_role
-
-
-def test_update_missing_id(server: TSC.Server) -> None:
- single_user = TSC.UserItem("test", "Interactor")
- with pytest.raises(TSC.MissingRequiredFieldError):
- server.users.update(single_user)
-
-
-def test_remove(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.delete(server.users.baseurl + "/dd2239f6-ddf1-4107-981a-4cf94e415794", status_code=204)
- server.users.remove("dd2239f6-ddf1-4107-981a-4cf94e415794")
-
-
-def test_remove_with_replacement(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.delete(
- server.users.baseurl
- + "/dd2239f6-ddf1-4107-981a-4cf94e415794"
- + "?mapAssetsTo=4cc4c17f-898a-4de4-abed-a1681c673ced",
- status_code=204,
- )
- server.users.remove("dd2239f6-ddf1-4107-981a-4cf94e415794", "4cc4c17f-898a-4de4-abed-a1681c673ced")
-
-
-def test_remove_missing_id(server: TSC.Server) -> None:
- with pytest.raises(ValueError):
- server.users.remove("")
-
-
-def test_add(server: TSC.Server) -> None:
- response_xml = ADD_XML.read_text()
- with requests_mock.mock() as m:
- m.post(server.users.baseurl + "", text=response_xml)
- new_user = TSC.UserItem(name="Cassie", site_role="Viewer", auth_setting="ServerDefault")
- new_user = server.users.add(new_user)
-
- assert "4cc4c17f-898a-4de4-abed-a1681c673ced" == new_user.id
- assert "Cassie" == new_user.name
- assert "Viewer" == new_user.site_role
- assert "ServerDefault" == new_user.auth_setting
-
-
-def test_populate_workbooks(server: TSC.Server) -> None:
- response_xml = POPULATE_WORKBOOKS_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.users.baseurl + "/dd2239f6-ddf1-4107-981a-4cf94e415794/workbooks", text=response_xml)
- single_user = TSC.UserItem("test", "Interactor")
- single_user._id = "dd2239f6-ddf1-4107-981a-4cf94e415794"
- server.users.populate_workbooks(single_user)
-
- workbook_list = list(single_user.workbooks)
- assert "3cc6cd06-89ce-4fdc-b935-5294135d6d42" == workbook_list[0].id
- assert "SafariSample" == workbook_list[0].name
- assert "SafariSample" == workbook_list[0].content_url
- assert False == workbook_list[0].show_tabs
- assert 26 == workbook_list[0].size
- assert "2016-07-26T20:34:56Z" == format_datetime(workbook_list[0].created_at)
- assert "2016-07-26T20:35:05Z" == format_datetime(workbook_list[0].updated_at)
- assert "ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" == workbook_list[0].project_id
- assert "default" == workbook_list[0].project_name
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == workbook_list[0].owner_id
- assert {"Safari", "Sample"} == workbook_list[0].tags
-
-
-def test_populate_owned_workbooks(server: TSC.Server) -> None:
- response_xml = POPULATE_WORKBOOKS_XML.read_text()
- # Query parameter ownedBy is case sensitive.
- with requests_mock.mock(case_sensitive=True) as m:
- m.get(server.users.baseurl + "/dd2239f6-ddf1-4107-981a-4cf94e415794/workbooks?ownedBy=true", text=response_xml)
- single_user = TSC.UserItem("test", "Interactor")
- single_user._id = "dd2239f6-ddf1-4107-981a-4cf94e415794"
- server.users.populate_workbooks(single_user, owned_only=True)
- list(single_user.workbooks)
-
- request_history = m.request_history[0]
-
- assert "ownedBy" in request_history.qs, "ownedBy not in request history"
- assert "true" in request_history.qs["ownedBy"], "ownedBy not set to true in request history"
-
-
-def test_populate_workbooks_missing_id(server: TSC.Server) -> None:
- single_user = TSC.UserItem("test", "Interactor")
- with pytest.raises(TSC.MissingRequiredFieldError):
- server.users.populate_workbooks(single_user)
-
-
-def test_populate_favorites(server: TSC.Server) -> None:
- server.version = "2.5"
- baseurl = server.favorites.baseurl
- single_user = TSC.UserItem("test", "Interactor")
- response_xml = GET_FAVORITES_XML.read_text()
- with requests_mock.mock() as m:
- m.get(f"{baseurl}/{single_user.id}", text=response_xml)
- server.users.populate_favorites(single_user)
- assert single_user._favorites is not None
- assert len(single_user.favorites["workbooks"]) == 1
- assert len(single_user.favorites["views"]) == 1
- assert len(single_user.favorites["projects"]) == 1
- assert len(single_user.favorites["datasources"]) == 1
-
- workbook = single_user.favorites["workbooks"][0]
- view = single_user.favorites["views"][0]
- datasource = single_user.favorites["datasources"][0]
- project = single_user.favorites["projects"][0]
-
- assert workbook.id == "6d13b0ca-043d-4d42-8c9d-3f3313ea3a00"
- assert view.id == "d79634e1-6063-4ec9-95ff-50acbf609ff5"
- assert datasource.id == "e76a1461-3b1d-4588-bf1b-17551a879ad9"
- assert project.id == "1d0304cd-3796-429f-b815-7258370b9b74"
-
-
-def test_populate_groups(server: TSC.Server) -> None:
- server.version = "3.7"
- response_xml = POPULATE_GROUPS_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.users.baseurl + "/dd2239f6-ddf1-4107-981a-4cf94e415794/groups", text=response_xml)
- single_user = TSC.UserItem("test", "Interactor")
- single_user._id = "dd2239f6-ddf1-4107-981a-4cf94e415794"
- server.users.populate_groups(single_user)
-
- group_list = list(single_user.groups)
-
- assert 3 == len(group_list)
- assert "ef8b19c0-43b6-11e6-af50-63f5805dbe3c" == group_list[0].id
- assert "All Users" == group_list[0].name
- assert "local" == group_list[0].domain_name
-
- assert "e7833b48-c6f7-47b5-a2a7-36e7dd232758" == group_list[1].id
- assert "Another group" == group_list[1].name
- assert "local" == group_list[1].domain_name
-
- assert "86a66d40-f289-472a-83d0-927b0f954dc8" == group_list[2].id
- assert "TableauExample" == group_list[2].name
- assert "local" == group_list[2].domain_name
-
-
-def test_get_usernames_from_file(server: TSC.Server):
- response_xml = ADD_XML.read_text()
- with requests_mock.mock() as m:
- m.post(server.users.baseurl, text=response_xml)
- with pytest.warns(DeprecationWarning):
- user_list, failures = server.users.create_from_file(str(USERNAMES))
- assert user_list[0].name == "Cassie", user_list
- assert failures == [], failures
-
-
-def test_get_users_from_file(server: TSC.Server):
- response_xml = ADD_XML.read_text()
- with requests_mock.mock() as m:
- m.post(server.users.baseurl, text=response_xml)
- with pytest.warns(DeprecationWarning):
- users, failures = server.users.create_from_file(str(USERS))
- assert users[0].name == "Cassie", users
- assert failures == []
-
-
-def test_get_users_all_fields(server: TSC.Server) -> None:
- server.version = "3.7"
- baseurl = server.users.baseurl
- response_xml = GET_XML_ALL_FIELDS.read_text()
-
- with requests_mock.mock() as m:
- m.get(f"{baseurl}?fields=_all_", text=response_xml)
- all_users, _ = server.users.get()
-
- assert all_users[0].auth_setting == "TableauIDWithMFA"
- assert all_users[0].email == "bob@example.com"
- assert all_users[0].external_auth_user_id == "38c870c3ac5e84ec66e6ced9fb23681835b07e56d5660371ac1f705cc65bd610"
- assert all_users[0].fullname == "Bob Smith"
- assert all_users[0].id == "ee8bc9ca-77fe-4ae0-8093-cf77f0ee67a9"
- assert all_users[0].last_login == parse_datetime("2025-02-04T06:39:20Z")
- assert all_users[0].name == "bob@example.com"
- assert all_users[0].site_role == "SiteAdministratorCreator"
- assert all_users[0].locale is None
- assert all_users[0].language == "en"
- assert all_users[0].idp_configuration_id == "22222222-2222-2222-2222-222222222222"
- assert all_users[0].domain_name == "TABID_WITH_MFA"
- assert all_users[1].auth_setting == "TableauIDWithMFA"
- assert all_users[1].email == "alice@example.com"
- assert all_users[1].external_auth_user_id == "96f66b893b22669cdfa632275d354cd1d92cea0266f3be7702151b9b8c52be29"
- assert all_users[1].fullname == "Alice Jones"
- assert all_users[1].id == "f6d72445-285b-48e5-8380-f90b519ce682"
- assert all_users[1].name == "alice@example.com"
- assert all_users[1].site_role == "ExplorerCanPublish"
- assert all_users[1].locale is None
- assert all_users[1].language == "en"
- assert all_users[1].idp_configuration_id == "22222222-2222-2222-2222-222222222222"
- assert all_users[1].domain_name == "TABID_WITH_MFA"
-
-
-def test_add_user_idp_configuration(server: TSC.Server) -> None:
- response_xml = ADD_XML.read_text()
- user = TSC.UserItem(name="Cassie", site_role="Viewer")
- user.idp_configuration_id = "012345"
-
- with requests_mock.mock() as m:
- m.post(server.users.baseurl, text=response_xml)
- user = server.users.add(user)
-
- history = m.request_history[0]
-
- tree = ET.fromstring(history.text)
- user_elem = tree.find(".//user")
- assert user_elem is not None
- assert user_elem.attrib["idpConfigurationId"] == "012345"
-
-
-def test_update_user_idp_configuration(server: TSC.Server) -> None:
- response_xml = ADD_XML.read_text()
- user = TSC.UserItem(name="Cassie", site_role="Viewer")
- user._id = "0123456789"
- user.idp_configuration_id = "012345"
-
- with requests_mock.mock() as m:
- m.put(f"{server.users.baseurl}/{user.id}", text=response_xml)
- user = server.users.update(user)
-
- history = m.request_history[0]
-
- tree = ET.fromstring(history.text)
- user_elem = tree.find(".//user")
- assert user_elem is not None
- assert user_elem.attrib["idpConfigurationId"] == "012345"
-
-
-def test_create_users_csv() -> None:
- users = [
- make_user("Alice", "Viewer"),
- make_user("Bob", "Explorer"),
- make_user("Charlie", "Creator", "SAML"),
- make_user("Dave"),
- make_user("Eve", "ServerAdministrator", "OpenID", "example.com", "Eve Example", "Eve@example.com"),
- make_user("Frank", "SiteAdministratorExplorer", "TableauIDWithMFA", email="Frank@example.com"),
- make_user("Grace", "SiteAdministratorCreator", "SAML", "example.com", "Grace Example", "gex@example.com"),
- make_user("Hank", "Unlicensed"),
- ]
-
- license_map = {
- "Viewer": "Viewer",
- "Explorer": "Explorer",
- "ExplorerCanPublish": "Explorer",
- "Creator": "Creator",
- "SiteAdministratorExplorer": "Explorer",
- "SiteAdministratorCreator": "Creator",
- "ServerAdministrator": "Creator",
- "Unlicensed": "Unlicensed",
- }
- publish_map = {
- "Unlicensed": 0,
- "Viewer": 0,
- "Explorer": 0,
- "Creator": 1,
- "ExplorerCanPublish": 1,
- "SiteAdministratorExplorer": 1,
- "SiteAdministratorCreator": 1,
- "ServerAdministrator": 1,
- }
- admin_map = {
- "SiteAdministratorExplorer": "Site",
- "SiteAdministratorCreator": "Site",
- "ServerAdministrator": "System",
- }
-
- csv_columns = ["name", "password", "fullname", "license", "admin", "publish", "email"]
- csv_data = create_users_csv(users)
- csv_file = io.StringIO(csv_data.decode("utf-8"))
- csv_reader = csv.reader(csv_file)
- for user, row in zip(users, csv_reader):
- site_role = user.site_role or "Unlicensed"
- name = f"{user.domain_name}\\{user.name}" if user.domain_name else user.name
- csv_user = dict(zip(csv_columns, row))
- assert name == csv_user["name"]
- assert (user.fullname or "") == csv_user["fullname"]
- assert (user.email or "") == csv_user["email"]
- assert license_map[site_role] == csv_user["license"]
- assert admin_map.get(site_role, "") == csv_user["admin"]
- assert publish_map[site_role] == int(csv_user["publish"])
-
-
-def test_bulk_add(server: TSC.Server) -> None:
- server.version = "3.15"
- users = [
- make_user("Alice", "Viewer"),
- make_user("Bob", "Explorer"),
- make_user("Charlie", "Creator", "SAML"),
- make_user("Dave"),
- make_user("Eve", "ServerAdministrator", "OpenID", "example.com", "Eve Example", "Eve@example.com"),
- make_user("Frank", "SiteAdministratorExplorer", "TableauIDWithMFA", email="Frank@example.com"),
- make_user("Grace", "SiteAdministratorCreator", "SAML", "example.com", "Grace Example", "gex@example.com"),
- make_user("Hank", "Unlicensed"),
- make_user("Ivy", "Unlicensed", idp_id="0123456789"),
- ]
- with requests_mock.mock() as m:
- m.post(f"{server.users.baseurl}/import", text=BULK_ADD_XML.read_text())
-
- job = server.users.bulk_add(users)
-
- assert isinstance(job, TSC.JobItem)
-
- assert m.last_request.method == "POST"
- assert m.last_request.url == f"{server.users.baseurl}/import"
-
- body = m.last_request.body.replace(b"\r\n", b"\n")
- assert body.startswith(b"--") # Check if it's a multipart request
- boundary = body.split(b"\n")[0].strip()
-
- # Body starts and ends with a boundary string. Split the body into
- # segments and ignore the empty sections at the start and end.
- segments = [seg for s in body.split(boundary) if (seg := s.strip()) not in [b"", b"--"]]
- assert len(segments) == 2 # Check if there are two segments
-
- # Check if the first segment is the csv file and the second segment is the xml
- assert b'Content-Disposition: form-data; name="tableau_user_import"' in segments[0]
- assert b'Content-Disposition: form-data; name="request_payload"' in segments[1]
- assert b"Content-Type: file" in segments[0]
- assert b"Content-Type: text/xml" in segments[1]
-
- xml_string = segments[1].split(b"\n\n")[1].strip()
- xml = ET.fromstring(xml_string)
- xml_users = xml.findall(".//user", namespaces={})
- assert len(xml_users) == len(users)
-
- for user, xml_user in zip(users, xml_users):
- assert user.name == xml_user.get("name")
- if user.idp_configuration_id is None:
- assert xml_user.get("authSetting") == user.auth_setting
- else:
- assert xml_user.get("idpConfigurationId") == user.idp_configuration_id
- assert xml_user.get("authSetting") is None
-
- csv_data = create_users_csv(users).replace(b"\r\n", b"\n")
- assert csv_data.strip() == segments[0].split(b"\n\n")[1].strip()
-
-
-def test_bulk_add_no_name(server: TSC.Server) -> None:
- server.version = "3.15"
- users = [
- TSC.UserItem(site_role="Viewer"),
- ]
- with requests_mock.mock() as m:
- m.post(f"{server.users.baseurl}/import", text=BULK_ADD_XML.read_text())
-
- with pytest.raises(ValueError, match="User name must be populated."):
- server.users.bulk_add(users)
-
-
-def test_bulk_remove(server: TSC.Server) -> None:
- server.version = "3.15"
- users = [
- make_user("Alice"),
- make_user("Bob", domain="example.com"),
- ]
- with requests_mock.mock() as m:
- m.post(f"{server.users.baseurl}/delete")
-
- server.users.bulk_remove(users)
-
- assert m.last_request.method == "POST"
- assert m.last_request.url == f"{server.users.baseurl}/delete"
-
- body = m.last_request.body.replace(b"\r\n", b"\n")
- assert body.startswith(b"--") # Check if it's a multipart request
- boundary = body.split(b"\n")[0].strip()
-
- content = next(seg for seg in body.split(boundary) if seg.strip())
- assert b'Content-Disposition: form-data; name="tableau_user_delete"' in content
- assert b"Content-Type: file" in content
-
- content = content.replace(b"\r\n", b"\n")
- csv_data = content.split(b"\n\n")[1].decode("utf-8")
- for user, row in zip(users, csv_data.split("\n")):
- name, *_ = row.split(",")
- assert name == f"{user.domain_name}\\{user.name}" if user.domain_name else user.name
-
-
-def test_add_all(server: TSC.Server) -> None:
- server.version = "2.0"
- users = [
- make_user("Alice", "Viewer"),
- make_user("Bob", "Explorer"),
- make_user("Charlie", "Creator", "SAML"),
- make_user("Dave"),
- ]
-
- with patch("tableauserverclient.server.endpoint.users_endpoint.Users.add", autospec=True) as mock_add:
- with pytest.warns(DeprecationWarning):
- server.users.add_all(users)
-
- assert mock_add.call_count == len(users)
-
-
-def test_add_idp_and_auth_error(server: TSC.Server) -> None:
- server.version = "3.24"
- users = [make_user("Alice", "Viewer", auth_setting="SAML", idp_id="01234")]
-
- with pytest.raises(ValueError, match="User cannot have both authSetting and idpConfigurationId."):
- server.users.bulk_add(users)
-
-
-def test_filter_by_email(server: TSC.Server) -> None:
- response_xml = GET_XML.read_text()
- with requests_mock.mock() as m:
- m.get(requests_mock.ANY, text=response_xml)
- qs = server.users.filter(email="test@example.com")
-
- # Access total_available to trigger the HTTP request and inspect the query string
- assert qs.total_available == 2
- assert len(m.request_history) >= 1
- first_request_qs = m.request_history[0].qs
- assert "filter" in first_request_qs
- assert "email:eq:test@example.com" in first_request_qs["filter"]
-
-
-def test_filter_by_email_in(server: TSC.Server) -> None:
- response_xml = GET_XML.read_text()
- with requests_mock.mock() as m:
- m.get(requests_mock.ANY, text=response_xml)
- qs = server.users.filter(email__in=["alice@example.com", "bob@example.com"])
-
- assert qs.total_available == 2
- assert len(m.request_history) >= 1
- first_request_qs = m.request_history[0].qs
- assert "filter" in first_request_qs
- assert "email:in:[alice@example.com,bob@example.com]" in first_request_qs["filter"]
-
-
-def test_remove_users_csv(server: TSC.Server) -> None:
- server.version = "3.15"
- users = [
- make_user("Alice", "Viewer"),
- make_user("Bob", "Explorer"),
- make_user("Charlie", "Creator", "SAML"),
- make_user("Dave"),
- make_user("Eve", "ServerAdministrator", "OpenID", "example.com", "Eve Example", "Eve@example.com"),
- make_user("Frank", "SiteAdministratorExplorer", "TableauIDWithMFA", email="Frank@example.com"),
- make_user("Grace", "SiteAdministratorCreator", "SAML", "example.com", "Grace Example", "gex@example.com"),
- make_user("Hank", "Unlicensed"),
- make_user("Ivy", "Unlicensed", idp_id="0123456789"),
- ]
-
- data = remove_users_csv(users)
- assert isinstance(data, bytes), "remove_users_csv should return bytes"
- csv_data = data.decode("utf-8")
- records = re.split(r"\r?\n", csv_data.strip())
- assert len(records) == len(users), "Number of records in csv does not match number of users"
-
- for user, record in zip(users, records):
- name, *rest = record.strip().split(",")
- assert len(rest) == 6, "Number of fields in csv does not match expected number"
- assert all([f == "" for f in rest]), "All fields except name should be empty"
- if user.domain_name is None:
- assert name == user.name, f"Name in csv does not match expected name: {user.name}"
- else:
- assert (
- name == f"{user.domain_name}\\{user.name}"
- ), f"Name in csv does not match expected name: {user.domain_name}\\{user.name}"
diff --git a/test/test_user_model.py b/test/test_user_model.py
deleted file mode 100644
index 49e8dc25c..000000000
--- a/test/test_user_model.py
+++ /dev/null
@@ -1,138 +0,0 @@
-import logging
-from unittest.mock import *
-import io
-
-import pytest
-
-import tableauserverclient as TSC
-
-
-def test_invalid_auth_setting():
- user = TSC.UserItem("me", TSC.UserItem.Roles.Publisher)
- with pytest.raises(ValueError):
- user.auth_setting = "Hello"
-
-
-def test_invalid_site_role():
- user = TSC.UserItem("me", TSC.UserItem.Roles.Publisher)
- with pytest.raises(ValueError):
- user.site_role = "Hello"
-
-
-logger = logging.getLogger("UserModelTest")
-
-
-role_inputs = [
- ["creator", "system", "yes", "SiteAdministrator"],
- ["None", "system", "no", "SiteAdministrator"],
- ["explorer", "SysTEm", "no", "SiteAdministrator"],
- ["creator", "site", "yes", "SiteAdministratorCreator"],
- ["explorer", "site", "yes", "SiteAdministratorExplorer"],
- ["creator", "SITE", "no", "SiteAdministratorCreator"],
- ["creator", "none", "yes", "Creator"],
- ["explorer", "none", "yes", "ExplorerCanPublish"],
- ["viewer", "None", "no", "Viewer"],
- ["explorer", "no", "yes", "ExplorerCanPublish"],
- ["EXPLORER", "noNO", "yes", "ExplorerCanPublish"],
- ["explorer", "no", "no", "Explorer"],
- ["unlicensed", "none", "no", "Unlicensed"],
- ["Chef", "none", "yes", "Unlicensed"],
- ["yes", "yes", "yes", "Unlicensed"],
-]
-
-valid_import_content = [
- "username, pword, fname, creator, site, yes, email",
- "username, pword, fname, explorer, none, no, email",
- "",
- "u",
- "p",
-]
-
-valid_username_content = ["jfitzgerald@tableau.com"]
-
-usernames = [
- "valid",
- "valid@email.com",
- "domain/valid",
- "domain/valid@tmail.com",
- "va!@#$%^&*()lid",
- "in@v@lid",
- "in valid",
- "",
-]
-
-
-def test_validate_usernames() -> None:
- TSC.UserItem.validate_username_or_throw(usernames[0])
- TSC.UserItem.validate_username_or_throw(usernames[1])
- TSC.UserItem.validate_username_or_throw(usernames[2])
- TSC.UserItem.validate_username_or_throw(usernames[3])
- TSC.UserItem.validate_username_or_throw(usernames[4])
- with pytest.raises(AttributeError):
- TSC.UserItem.validate_username_or_throw(usernames[5])
- with pytest.raises(AttributeError):
- TSC.UserItem.validate_username_or_throw(usernames[6])
-
-
-def test_evaluate_role() -> None:
- for line in role_inputs:
- actual = TSC.UserItem.CSVImport._evaluate_site_role(line[0], line[1], line[2])
- assert actual == line[3], line + [actual]
-
-
-def test_get_user_detail_empty_line() -> None:
- test_line = ""
- test_user = TSC.UserItem.CSVImport.create_user_from_line(test_line)
- assert test_user is None
-
-
-def test_get_user_detail_standard() -> None:
- test_line = "username, pword, fname, license, admin, pub, email"
- test_user = TSC.UserItem.CSVImport.create_user_from_line(test_line)
- assert test_user is not None
- assert test_user.name == "username", test_user.name
- assert test_user.fullname == "fname", test_user.fullname
- assert test_user.site_role == "Unlicensed", test_user.site_role
- assert test_user.email == "email", test_user.email
-
-
-def test_get_user_details_only_username() -> None:
- test_line = "username"
- test_user = TSC.UserItem.CSVImport.create_user_from_line(test_line)
-
-
-def test_populate_user_details_only_some() -> None:
- values = "username, , , creator, admin"
- user = TSC.UserItem.CSVImport.create_user_from_line(values)
- assert user is not None
- assert user.name == "username"
-
-
-def test_validate_user_detail_standard() -> None:
- test_line = "username, pword, fname, creator, site, 1, email"
- TSC.UserItem.CSVImport._validate_import_line_or_throw(test_line, logger)
- TSC.UserItem.CSVImport.create_user_from_line(test_line)
-
-
-# for file handling
-def _mock_file_content(content: list[str]) -> io.TextIOWrapper:
- # the empty string represents EOF
- # the tests run through the file twice, first to validate then to fetch
- mock = MagicMock(io.TextIOWrapper)
- content.append("") # EOF
- mock.readline.side_effect = content
- mock.name = "file-mock"
- return mock
-
-
-def test_validate_import_file() -> None:
- test_data = _mock_file_content(valid_import_content)
- valid, invalid = TSC.UserItem.CSVImport.validate_file_for_import(test_data, logger)
- assert valid == 2, f"Expected two lines to be parsed, got {valid}"
- assert invalid == [], f"Expected no failures, got {invalid}"
-
-
-def test_validate_usernames_file() -> None:
- test_data = _mock_file_content(usernames)
- valid, invalid = TSC.UserItem.CSVImport.validate_file_for_import(test_data, logger)
- assert valid == 5, f"Exactly 5 of the lines were valid, counted {valid + len(invalid)}"
diff --git a/test/test_view.py b/test/test_view.py
deleted file mode 100644
index f4d5b7e1f..000000000
--- a/test/test_view.py
+++ /dev/null
@@ -1,584 +0,0 @@
-from pathlib import Path
-
-import pytest
-import requests_mock
-
-import tableauserverclient as TSC
-from tableauserverclient import UserItem, GroupItem, PermissionsRule
-from tableauserverclient.datetime_helpers import format_datetime, parse_datetime
-from tableauserverclient.server.endpoint.exceptions import UnsupportedAttributeError
-
-TEST_ASSET_DIR = Path(__file__).parent / "assets"
-
-ADD_TAGS_XML = TEST_ASSET_DIR / "view_add_tags.xml"
-GET_XML = TEST_ASSET_DIR / "view_get.xml"
-GET_XML_ALL_FIELDS = TEST_ASSET_DIR / "view_get_all_fields.xml"
-GET_XML_ID = TEST_ASSET_DIR / "view_get_id.xml"
-GET_XML_USAGE = TEST_ASSET_DIR / "view_get_usage.xml"
-GET_XML_ID_USAGE = TEST_ASSET_DIR / "view_get_id_usage.xml"
-POPULATE_PREVIEW_IMAGE = TEST_ASSET_DIR / "Sample View Image.png"
-POPULATE_PDF = TEST_ASSET_DIR / "populate_pdf.pdf"
-POPULATE_CSV = TEST_ASSET_DIR / "populate_csv.csv"
-POPULATE_EXCEL = TEST_ASSET_DIR / "populate_excel.xlsx"
-POPULATE_PERMISSIONS_XML = TEST_ASSET_DIR / "view_populate_permissions.xml"
-UPDATE_PERMISSIONS = TEST_ASSET_DIR / "view_update_permissions.xml"
-UPDATE_XML = TEST_ASSET_DIR / "workbook_update.xml"
-
-
-@pytest.fixture(scope="function")
-def server():
- """Fixture to create a TSC.Server instance for testing."""
- server = TSC.Server("http://test", False)
-
- # Fake signin
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
- server.version = "3.2"
-
- return server
-
-
-def test_get(server: TSC.Server) -> None:
- response_xml = GET_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.views.baseurl, text=response_xml)
- all_views, pagination_item = server.views.get()
-
- assert 2 == pagination_item.total_available
- assert "d79634e1-6063-4ec9-95ff-50acbf609ff5" == all_views[0].id
- assert "ENDANGERED SAFARI" == all_views[0].name
- assert "SafariSample/sheets/ENDANGEREDSAFARI" == all_views[0].content_url
- assert "3cc6cd06-89ce-4fdc-b935-5294135d6d42" == all_views[0].workbook_id
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == all_views[0].owner_id
- assert "5241e88d-d384-4fd7-9c2f-648b5247efc5" == all_views[0].project_id
- assert {"tag1", "tag2"} == all_views[0].tags
- assert all_views[0].created_at is None
- assert all_views[0].updated_at is None
- assert all_views[0].sheet_type is None
-
- assert "fd252f73-593c-4c4e-8584-c032b8022adc" == all_views[1].id
- assert "Overview" == all_views[1].name
- assert "Superstore/sheets/Overview" == all_views[1].content_url
- assert "6d13b0ca-043d-4d42-8c9d-3f3313ea3a00" == all_views[1].workbook_id
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == all_views[1].owner_id
- assert "5b534f74-3226-11e8-b47a-cb2e00f738a3" == all_views[1].project_id
- assert "2002-05-30T09:00:00Z" == format_datetime(all_views[1].created_at)
- assert "2002-06-05T08:00:59Z" == format_datetime(all_views[1].updated_at)
- assert "story" == all_views[1].sheet_type
-
-
-def test_get_by_id(server: TSC.Server) -> None:
- response_xml = GET_XML_ID.read_text()
- with requests_mock.mock() as m:
- m.get(server.views.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5", text=response_xml)
- view = server.views.get_by_id("d79634e1-6063-4ec9-95ff-50acbf609ff5")
-
- assert "d79634e1-6063-4ec9-95ff-50acbf609ff5" == view.id
- assert "ENDANGERED SAFARI" == view.name
- assert "SafariSample/sheets/ENDANGEREDSAFARI" == view.content_url
- assert "3cc6cd06-89ce-4fdc-b935-5294135d6d42" == view.workbook_id
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == view.owner_id
- assert "5241e88d-d384-4fd7-9c2f-648b5247efc5" == view.project_id
- assert {"tag1", "tag2"} == view.tags
- assert "2002-05-30T09:00:00Z" == format_datetime(view.created_at)
- assert "2002-06-05T08:00:59Z" == format_datetime(view.updated_at)
- assert "story" == view.sheet_type
-
-
-def test_get_by_id_usage(server: TSC.Server) -> None:
- response_xml = GET_XML_ID_USAGE.read_text()
- with requests_mock.mock() as m:
- m.get(
- server.views.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5?includeUsageStatistics=true",
- text=response_xml,
- )
- view = server.views.get_by_id("d79634e1-6063-4ec9-95ff-50acbf609ff5", usage=True)
-
- assert "d79634e1-6063-4ec9-95ff-50acbf609ff5" == view.id
- assert "ENDANGERED SAFARI" == view.name
- assert "SafariSample/sheets/ENDANGEREDSAFARI" == view.content_url
- assert "3cc6cd06-89ce-4fdc-b935-5294135d6d42" == view.workbook_id
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == view.owner_id
- assert "5241e88d-d384-4fd7-9c2f-648b5247efc5" == view.project_id
- assert {"tag1", "tag2"} == view.tags
- assert "2002-05-30T09:00:00Z" == format_datetime(view.created_at)
- assert "2002-06-05T08:00:59Z" == format_datetime(view.updated_at)
- assert "story" == view.sheet_type
- assert 7 == view.total_views
-
-
-def test_get_by_id_missing_id(server: TSC.Server) -> None:
- with pytest.raises(TSC.MissingRequiredFieldError):
- server.views.get_by_id(None)
-
-
-def test_get_with_usage(server: TSC.Server) -> None:
- response_xml = GET_XML_USAGE.read_text()
- with requests_mock.mock() as m:
- m.get(server.views.baseurl + "?includeUsageStatistics=true", text=response_xml)
- all_views, pagination_item = server.views.get(usage=True)
-
- assert "d79634e1-6063-4ec9-95ff-50acbf609ff5" == all_views[0].id
- assert 7 == all_views[0].total_views
- assert all_views[0].created_at is None
- assert all_views[0].updated_at is None
- assert all_views[0].sheet_type is None
-
- assert "fd252f73-593c-4c4e-8584-c032b8022adc" == all_views[1].id
- assert 13 == all_views[1].total_views
- assert "2002-05-30T09:00:00Z" == format_datetime(all_views[1].created_at)
- assert "2002-06-05T08:00:59Z" == format_datetime(all_views[1].updated_at)
- assert "story" == all_views[1].sheet_type
-
-
-def test_get_with_usage_and_filter(server: TSC.Server) -> None:
- response_xml = GET_XML_USAGE.read_text()
- with requests_mock.mock() as m:
- m.get(server.views.baseurl + "?includeUsageStatistics=true&filter=name:in:[foo,bar]", text=response_xml)
- options = TSC.RequestOptions()
- options.filter.add(TSC.Filter(TSC.RequestOptions.Field.Name, TSC.RequestOptions.Operator.In, ["foo", "bar"]))
- all_views, pagination_item = server.views.get(req_options=options, usage=True)
-
- assert "ENDANGERED SAFARI" == all_views[0].name
- assert 7 == all_views[0].total_views
- assert "Overview" == all_views[1].name
- assert 13 == all_views[1].total_views
-
-
-def test_get_before_signin(server: TSC.Server) -> None:
- server._auth_token = None
- with pytest.raises(TSC.NotSignedInError):
- server.views.get()
-
-
-def test_populate_preview_image(server: TSC.Server) -> None:
- response = POPULATE_PREVIEW_IMAGE.read_bytes()
- with requests_mock.mock() as m:
- m.get(
- server.views.siteurl + "/workbooks/3cc6cd06-89ce-4fdc-b935-5294135d6d42/"
- "views/d79634e1-6063-4ec9-95ff-50acbf609ff5/previewImage",
- content=response,
- )
- single_view = TSC.ViewItem()
- single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5"
- single_view._workbook_id = "3cc6cd06-89ce-4fdc-b935-5294135d6d42"
- server.views.populate_preview_image(single_view)
- assert response == single_view.preview_image
-
-
-def test_populate_preview_image_missing_id(server: TSC.Server) -> None:
- single_view = TSC.ViewItem()
- single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5"
- with pytest.raises(TSC.MissingRequiredFieldError):
- server.views.populate_preview_image(single_view)
-
- single_view._id = None
- single_view._workbook_id = "3cc6cd06-89ce-4fdc-b935-5294135d6d42"
- with pytest.raises(TSC.MissingRequiredFieldError):
- server.views.populate_preview_image(single_view)
-
-
-def test_populate_image(server: TSC.Server) -> None:
- response = POPULATE_PREVIEW_IMAGE.read_bytes()
- with requests_mock.mock() as m:
- m.get(server.views.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/image", content=response)
- single_view = TSC.ViewItem()
- single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5"
- server.views.populate_image(single_view)
- assert response == single_view.image
-
-
-def test_populate_image_unsupported(server: TSC.Server) -> None:
- server.version = "3.8"
- response = POPULATE_PREVIEW_IMAGE.read_bytes()
- with requests_mock.mock() as m:
- m.get(
- server.views.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/image?vizWidth=1920&vizHeight=1080",
- content=response,
- )
- single_view = TSC.ViewItem()
- single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5"
-
- req_option = TSC.ImageRequestOptions(viz_width=1920, viz_height=1080)
-
- with pytest.raises(UnsupportedAttributeError):
- server.views.populate_image(single_view, req_option)
-
-
-def test_populate_image_viz_dimensions(server: TSC.Server) -> None:
- server.version = "3.23"
- response = POPULATE_PREVIEW_IMAGE.read_bytes()
- with requests_mock.mock() as m:
- m.get(
- server.views.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/image?vizWidth=1920&vizHeight=1080",
- content=response,
- )
- single_view = TSC.ViewItem()
- single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5"
-
- req_option = TSC.ImageRequestOptions(viz_width=1920, viz_height=1080)
-
- server.views.populate_image(single_view, req_option)
- assert response == single_view.image
-
- history = m.request_history
-
-
-def test_populate_image_with_options(server: TSC.Server) -> None:
- response = POPULATE_PREVIEW_IMAGE.read_bytes()
- with requests_mock.mock() as m:
- m.get(
- server.views.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/image?resolution=high&maxAge=10",
- content=response,
- )
- single_view = TSC.ViewItem()
- single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5"
- req_option = TSC.ImageRequestOptions(imageresolution=TSC.ImageRequestOptions.Resolution.High, maxage=10)
- server.views.populate_image(single_view, req_option)
- assert response == single_view.image
-
-
-def test_populate_image_svg_format(server: TSC.Server) -> None:
- server.version = "3.29"
- response = b"test "
- with requests_mock.mock() as m:
- m.get(
- server.views.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/image?format=SVG",
- content=response,
- )
- single_view = TSC.ViewItem()
- single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5"
- req_option = TSC.ImageRequestOptions(format=TSC.ImageRequestOptions.Format.SVG)
- server.views.populate_image(single_view, req_option)
- assert response == single_view.image
-
-
-def test_populate_image_png_format(server: TSC.Server) -> None:
- server.version = "3.29"
- response = POPULATE_PREVIEW_IMAGE.read_bytes()
- with requests_mock.mock() as m:
- m.get(
- server.views.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/image?format=PNG",
- content=response,
- )
- single_view = TSC.ViewItem()
- single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5"
- req_option = TSC.ImageRequestOptions(format=TSC.ImageRequestOptions.Format.PNG)
- server.views.populate_image(single_view, req_option)
- assert response == single_view.image
-
-
-def test_populate_image_format_unsupported_version(server: TSC.Server) -> None:
- server.version = "3.28"
- response = POPULATE_PREVIEW_IMAGE.read_bytes()
- with requests_mock.mock() as m:
- m.get(
- server.views.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/image?format=SVG",
- content=response,
- )
- single_view = TSC.ViewItem()
- single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5"
- req_option = TSC.ImageRequestOptions(format=TSC.ImageRequestOptions.Format.SVG)
-
- with pytest.raises(UnsupportedAttributeError):
- server.views.populate_image(single_view, req_option)
-
-
-def test_populate_pdf(server: TSC.Server) -> None:
- response = POPULATE_PDF.read_bytes()
- with requests_mock.mock() as m:
- m.get(
- server.views.baseurl
- + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/pdf?type=letter&orientation=portrait&maxAge=5",
- content=response,
- )
- single_view = TSC.ViewItem()
- single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5"
-
- size = TSC.PDFRequestOptions.PageType.Letter
- orientation = TSC.PDFRequestOptions.Orientation.Portrait
- req_option = TSC.PDFRequestOptions(size, orientation, 5)
-
- server.views.populate_pdf(single_view, req_option)
- assert response == single_view.pdf
-
-
-def test_populate_csv(server: TSC.Server) -> None:
- response = POPULATE_CSV.read_bytes()
- with requests_mock.mock() as m:
- m.get(server.views.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/data?maxAge=1", content=response)
- single_view = TSC.ViewItem()
- single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5"
- request_option = TSC.CSVRequestOptions(maxage=1)
- server.views.populate_csv(single_view, request_option)
-
- csv_file = b"".join(single_view.csv)
- assert response == csv_file
-
-
-def test_populate_csv_default_maxage(server: TSC.Server) -> None:
- response = POPULATE_CSV.read_bytes()
- with requests_mock.mock() as m:
- m.get(server.views.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/data", content=response)
- single_view = TSC.ViewItem()
- single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5"
- server.views.populate_csv(single_view)
-
- csv_file = b"".join(single_view.csv)
- assert response == csv_file
-
-
-def test_populate_image_missing_id(server: TSC.Server) -> None:
- single_view = TSC.ViewItem()
- single_view._id = None
- with pytest.raises(TSC.MissingRequiredFieldError):
- server.views.populate_image(single_view)
-
-
-def test_populate_permissions(server: TSC.Server) -> None:
- response_xml = POPULATE_PERMISSIONS_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.views.baseurl + "/e490bec4-2652-4fda-8c4e-f087db6fa328/permissions", text=response_xml)
- single_view = TSC.ViewItem()
- single_view._id = "e490bec4-2652-4fda-8c4e-f087db6fa328"
-
- server.views.populate_permissions(single_view)
- permissions = single_view.permissions
-
- assert permissions[0].grantee.tag_name == "group"
- assert permissions[0].grantee.id == "c8f2773a-c83a-11e8-8c8f-33e6d787b506"
- assert permissions[0].capabilities == {
- TSC.Permission.Capability.ViewComments: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.Read: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.AddComment: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.ExportData: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.ExportImage: TSC.Permission.Mode.Allow,
- }
-
-
-def test_add_permissions(server: TSC.Server) -> None:
- response_xml = UPDATE_PERMISSIONS.read_text()
-
- single_view = TSC.ViewItem()
- single_view._id = "21778de4-b7b9-44bc-a599-1506a2639ace"
-
- bob = UserItem.as_reference("7c37ee24-c4b1-42b6-a154-eaeab7ee330a")
- group_of_people = GroupItem.as_reference("5e5e1978-71fa-11e4-87dd-7382f5c437af")
-
- new_permissions = [PermissionsRule(bob, {"Write": "Allow"}), PermissionsRule(group_of_people, {"Read": "Deny"})]
-
- with requests_mock.mock() as m:
- m.put(server.views.baseurl + "/21778de4-b7b9-44bc-a599-1506a2639ace/permissions", text=response_xml)
- permissions = server.views.update_permissions(single_view, new_permissions)
-
- assert permissions[0].grantee.tag_name == "group"
- assert permissions[0].grantee.id == "5e5e1978-71fa-11e4-87dd-7382f5c437af"
- assert permissions[0].capabilities == {TSC.Permission.Capability.Read: TSC.Permission.Mode.Deny}
-
- assert permissions[1].grantee.tag_name == "user"
- assert permissions[1].grantee.id == "7c37ee24-c4b1-42b6-a154-eaeab7ee330a"
- assert permissions[1].capabilities == {TSC.Permission.Capability.Write: TSC.Permission.Mode.Allow}
-
-
-def test_update_tags(server: TSC.Server) -> None:
- add_tags_xml = ADD_TAGS_XML.read_text()
- update_xml = UPDATE_XML.read_text()
- with requests_mock.mock() as m:
- m.put(server.views.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/tags", text=add_tags_xml)
- m.delete(server.views.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/tags/b", status_code=204)
- m.delete(server.views.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/tags/d", status_code=204)
- m.put(server.views.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5", text=update_xml)
- single_view = TSC.ViewItem()
- single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5"
- single_view._initial_tags.update(["a", "b", "c", "d"])
- single_view.tags.update(["a", "c", "e"])
- updated_view = server.views.update(single_view)
-
- assert single_view.tags == updated_view.tags
- assert single_view._initial_tags == updated_view._initial_tags
-
-
-def test_populate_excel(server: TSC.Server) -> None:
- server.version = "3.8"
- response = POPULATE_EXCEL.read_bytes()
- with requests_mock.mock() as m:
- m.get(server.views.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/crosstab/excel?maxAge=1", content=response)
- single_view = TSC.ViewItem()
- single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5"
- request_option = TSC.ExcelRequestOptions(maxage=1)
- server.views.populate_excel(single_view, request_option)
-
- excel_file = b"".join(single_view.excel)
- assert response == excel_file
-
-
-def test_filter_excel(server: TSC.Server) -> None:
- server.version = "3.8"
- response = POPULATE_EXCEL.read_bytes()
- with requests_mock.mock() as m:
- m.get(server.views.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/crosstab/excel?maxAge=1", content=response)
- single_view = TSC.ViewItem()
- single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5"
- request_option = TSC.ExcelRequestOptions(maxage=1)
- request_option.vf("stuff", "1")
- server.views.populate_excel(single_view, request_option)
-
- excel_file = b"".join(single_view.excel)
- assert response == excel_file
-
-
-def test_pdf_viz_dimensions(server: TSC.Server) -> None:
- response = POPULATE_PDF.read_bytes()
- with requests_mock.mock() as m:
- m.get(
- server.views.baseurl + "/d79634e1-6063-4ec9-95ff-50acbf609ff5/pdf?vizHeight=1080&vizWidth=1920",
- content=response,
- )
- single_view = TSC.ViewItem()
- single_view._id = "d79634e1-6063-4ec9-95ff-50acbf609ff5"
-
- req_option = TSC.PDFRequestOptions(
- viz_height=1080,
- viz_width=1920,
- )
-
- server.views.populate_pdf(single_view, req_option)
- assert response == single_view.pdf
-
-
-def test_pdf_errors(server: TSC.Server) -> None:
- req_option = TSC.PDFRequestOptions(viz_height=1080)
- with pytest.raises(ValueError):
- req_option.get_query_params()
- req_option = TSC.PDFRequestOptions(viz_width=1920)
- with pytest.raises(ValueError):
- req_option.get_query_params()
-
-
-def test_view_get_all_fields(server: TSC.Server) -> None:
- server.version = "3.21"
- response_xml = GET_XML_ALL_FIELDS.read_text()
-
- ro = TSC.RequestOptions()
- ro.all_fields = True
-
- with requests_mock.mock() as m:
- m.get(f"{server.views.baseurl}?fields=_all_", text=response_xml)
- views, _ = server.views.get(req_options=ro)
-
- assert views[0].id == "2bdcd787-dcc6-4a5d-bc61-2846f1ef4534"
- assert views[0].name == "Overview"
- assert views[0].content_url == "Superstore/sheets/Overview"
- assert views[0].created_at == parse_datetime("2024-02-14T04:42:09Z")
- assert views[0].updated_at == parse_datetime("2024-02-14T04:42:09Z")
- assert views[0].sheet_type == "dashboard"
- assert views[0].favorites_total == 0
- assert views[0].view_url_name == "Overview"
- assert isinstance(views[0].workbook, TSC.WorkbookItem)
- assert views[0].workbook.id == "9df3e2d1-070e-497a-9578-8cc557ced9df"
- assert views[0].workbook.name == "Superstore"
- assert views[0].workbook.content_url == "Superstore"
- assert views[0].workbook.show_tabs
- assert views[0].workbook.size == 2
- assert views[0].workbook.created_at == parse_datetime("2024-02-14T04:42:09Z")
- assert views[0].workbook.updated_at == parse_datetime("2024-02-14T04:42:10Z")
- assert views[0].workbook.sheet_count == 9
- assert not views[0].workbook.has_extracts
- assert isinstance(views[0].owner, TSC.UserItem)
- assert views[0].owner.email == "bob@example.com"
- assert views[0].owner.fullname == "Bob"
- assert views[0].owner.id == "ee8bc9ca-77fe-4ae0-8093-cf77f0ee67a9"
- assert views[0].owner.last_login == parse_datetime("2025-02-04T06:39:20Z")
- assert views[0].owner.name == "bob@example.com"
- assert views[0].owner.site_role == "SiteAdministratorCreator"
- assert isinstance(views[0].project, TSC.ProjectItem)
- assert views[0].project.id == "669ca36b-492e-4ccf-bca1-3614fe6a9d7a"
- assert views[0].project.name == "Samples"
- assert views[0].project.description == "This project includes automatically uploaded samples."
- assert views[0].total_views == 0
- assert isinstance(views[0].location, TSC.LocationItem)
- assert views[0].location.id == "669ca36b-492e-4ccf-bca1-3614fe6a9d7a"
- assert views[0].location.type == "Project"
- assert views[1].id == "2a3fd19d-9129-413d-9ff7-9dfc36bf7f7e"
- assert views[1].name == "Product"
- assert views[1].content_url == "Superstore/sheets/Product"
- assert views[1].created_at == parse_datetime("2024-02-14T04:42:09Z")
- assert views[1].updated_at == parse_datetime("2024-02-14T04:42:09Z")
- assert views[1].sheet_type == "dashboard"
- assert views[1].favorites_total == 0
- assert views[1].view_url_name == "Product"
- assert isinstance(views[1].workbook, TSC.WorkbookItem)
- assert views[1].workbook.id == "9df3e2d1-070e-497a-9578-8cc557ced9df"
- assert views[1].workbook.name == "Superstore"
- assert views[1].workbook.content_url == "Superstore"
- assert views[1].workbook.show_tabs
- assert views[1].workbook.size == 2
- assert views[1].workbook.created_at == parse_datetime("2024-02-14T04:42:09Z")
- assert views[1].workbook.updated_at == parse_datetime("2024-02-14T04:42:10Z")
- assert views[1].workbook.sheet_count == 9
- assert not views[1].workbook.has_extracts
- assert isinstance(views[1].owner, TSC.UserItem)
- assert views[1].owner.email == "bob@example.com"
- assert views[1].owner.fullname == "Bob"
- assert views[1].owner.id == "ee8bc9ca-77fe-4ae0-8093-cf77f0ee67a9"
- assert views[1].owner.last_login == parse_datetime("2025-02-04T06:39:20Z")
- assert views[1].owner.name == "bob@example.com"
- assert views[1].owner.site_role == "SiteAdministratorCreator"
- assert isinstance(views[1].project, TSC.ProjectItem)
- assert views[1].project.id == "669ca36b-492e-4ccf-bca1-3614fe6a9d7a"
- assert views[1].project.name == "Samples"
- assert views[1].project.description == "This project includes automatically uploaded samples."
- assert views[1].total_views == 0
- assert isinstance(views[1].location, TSC.LocationItem)
- assert views[1].location.id == "669ca36b-492e-4ccf-bca1-3614fe6a9d7a"
- assert views[1].location.type == "Project"
- assert views[2].id == "459eda9a-85e4-46bf-a2f2-62936bd2e99a"
- assert views[2].name == "Customers"
- assert views[2].content_url == "Superstore/sheets/Customers"
- assert views[2].created_at == parse_datetime("2024-02-14T04:42:09Z")
- assert views[2].updated_at == parse_datetime("2024-02-14T04:42:09Z")
- assert views[2].sheet_type == "dashboard"
- assert views[2].favorites_total == 0
- assert views[2].view_url_name == "Customers"
- assert isinstance(views[2].workbook, TSC.WorkbookItem)
- assert views[2].workbook.id == "9df3e2d1-070e-497a-9578-8cc557ced9df"
- assert views[2].workbook.name == "Superstore"
- assert views[2].workbook.content_url == "Superstore"
- assert views[2].workbook.show_tabs
- assert views[2].workbook.size == 2
- assert views[2].workbook.created_at == parse_datetime("2024-02-14T04:42:09Z")
- assert views[2].workbook.updated_at == parse_datetime("2024-02-14T04:42:10Z")
- assert views[2].workbook.sheet_count == 9
- assert not views[2].workbook.has_extracts
- assert isinstance(views[2].owner, TSC.UserItem)
- assert views[2].owner.email == "bob@example.com"
- assert views[2].owner.fullname == "Bob"
- assert views[2].owner.id == "ee8bc9ca-77fe-4ae0-8093-cf77f0ee67a9"
- assert views[2].owner.last_login == parse_datetime("2025-02-04T06:39:20Z")
- assert views[2].owner.name == "bob@example.com"
- assert views[2].owner.site_role == "SiteAdministratorCreator"
- assert isinstance(views[2].project, TSC.ProjectItem)
- assert views[2].project.id == "669ca36b-492e-4ccf-bca1-3614fe6a9d7a"
- assert views[2].project.name == "Samples"
- assert views[2].project.description == "This project includes automatically uploaded samples."
- assert views[2].total_views == 0
- assert isinstance(views[2].location, TSC.LocationItem)
- assert views[2].location.id == "669ca36b-492e-4ccf-bca1-3614fe6a9d7a"
- assert views[2].location.type == "Project"
-
-
-def make_view() -> TSC.ViewItem:
- view = TSC.ViewItem()
- view._id = "1234"
- return view
-
-
-@pytest.mark.parametrize("view", [make_view, "1234"])
-def test_delete_view(server: TSC.Server, view: TSC.ViewItem | str) -> None:
- server.version = "3.27"
- id_ = getattr(view, "id", view)
- with requests_mock.mock() as m:
- m.delete(f"{server.views.baseurl}/{id_}")
- server.views.delete(view)
- assert m.called
- assert m.call_count == 1
diff --git a/test/test_view_acceleration.py b/test/test_view_acceleration.py
deleted file mode 100644
index cbd1dc194..000000000
--- a/test/test_view_acceleration.py
+++ /dev/null
@@ -1,120 +0,0 @@
-from pathlib import Path
-import requests_mock
-
-import pytest
-
-import tableauserverclient as TSC
-from tableauserverclient.datetime_helpers import format_datetime
-
-TEST_ASSET_DIR = Path(__file__).parent / "assets"
-
-GET_BY_ID_ACCELERATION_STATUS_XML = TEST_ASSET_DIR / "workbook_get_by_id_acceleration_status.xml"
-POPULATE_VIEWS_XML = TEST_ASSET_DIR / "workbook_populate_views.xml"
-UPDATE_VIEWS_ACCELERATION_STATUS_XML = TEST_ASSET_DIR / "workbook_update_views_acceleration_status.xml"
-UPDATE_WORKBOOK_ACCELERATION_STATUS_XML = TEST_ASSET_DIR / "workbook_update_acceleration_status.xml"
-
-
-@pytest.fixture(scope="function")
-def server():
- """Fixture to create a TSC.Server instance for testing."""
- server = TSC.Server("http://test", False)
-
- # Fake signin
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
-
- return server
-
-
-def test_get_by_id(server: TSC.Server) -> None:
- response_xml = GET_BY_ID_ACCELERATION_STATUS_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.workbooks.baseurl + "/3cc6cd06-89ce-4fdc-b935-5294135d6d42", text=response_xml)
- single_workbook = server.workbooks.get_by_id("3cc6cd06-89ce-4fdc-b935-5294135d6d42")
-
- assert "3cc6cd06-89ce-4fdc-b935-5294135d6d42" == single_workbook.id
- assert "SafariSample" == single_workbook.name
- assert "SafariSample" == single_workbook.content_url
- assert "http://tableauserver/#/workbooks/2/views" == single_workbook.webpage_url
- assert single_workbook.show_tabs is False
- assert 26 == single_workbook.size
- assert "2016-07-26T20:34:56Z" == format_datetime(single_workbook.created_at)
- assert "description for SafariSample" == single_workbook.description
- assert "2016-07-26T20:35:05Z" == format_datetime(single_workbook.updated_at)
- assert "ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" == single_workbook.project_id
- assert "default" == single_workbook.project_name
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == single_workbook.owner_id
- assert {"Safari", "Sample"} == single_workbook.tags
- assert "d79634e1-6063-4ec9-95ff-50acbf609ff5" == single_workbook.views[0].id
- assert "ENDANGERED SAFARI" == single_workbook.views[0].name
- assert "SafariSample/sheets/ENDANGEREDSAFARI" == single_workbook.views[0].content_url
- assert single_workbook.views[0].data_acceleration_config["acceleration_enabled"]
- assert "Enabled" == single_workbook.views[0].data_acceleration_config["acceleration_status"]
- assert "d79634e1-6063-4ec9-95ff-50acbf609ff9" == single_workbook.views[1].id
- assert "ENDANGERED SAFARI 2" == single_workbook.views[1].name
- assert "SafariSample/sheets/ENDANGEREDSAFARI2" == single_workbook.views[1].content_url
- assert single_workbook.views[1].data_acceleration_config["acceleration_enabled"] is False
- assert "Suspended" == single_workbook.views[1].data_acceleration_config["acceleration_status"]
-
-
-def test_update_workbook_acceleration(server: TSC.Server) -> None:
- response_xml = UPDATE_WORKBOOK_ACCELERATION_STATUS_XML.read_text()
- with requests_mock.mock() as m:
- m.put(server.workbooks.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2", text=response_xml)
- single_workbook = TSC.WorkbookItem("1d0304cd-3796-429f-b815-7258370b9b74", show_tabs=True)
- single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
- single_workbook.data_acceleration_config = {
- "acceleration_enabled": True,
- "accelerate_now": False,
- "last_updated_at": None,
- "acceleration_status": None,
- }
- # update with parameter includeViewAccelerationStatus=True
- single_workbook = server.workbooks.update(single_workbook, True)
-
- assert "1f951daf-4061-451a-9df1-69a8062664f2" == single_workbook.id
- assert "1d0304cd-3796-429f-b815-7258370b9b74" == single_workbook.project_id
- assert "SafariSample/sheets/ENDANGEREDSAFARI" == single_workbook.views[0].content_url
- assert single_workbook.views[0].data_acceleration_config["acceleration_enabled"]
- assert "Pending" == single_workbook.views[0].data_acceleration_config["acceleration_status"]
- assert "d79634e1-6063-4ec9-95ff-50acbf609ff9" == single_workbook.views[1].id
- assert "ENDANGERED SAFARI 2" == single_workbook.views[1].name
- assert "SafariSample/sheets/ENDANGEREDSAFARI2" == single_workbook.views[1].content_url
- assert single_workbook.views[1].data_acceleration_config["acceleration_enabled"]
- assert "Pending" == single_workbook.views[1].data_acceleration_config["acceleration_status"]
-
-
-def test_update_views_acceleration(server: TSC.Server) -> None:
- views_xml = POPULATE_VIEWS_XML.read_text()
- response_xml = UPDATE_VIEWS_ACCELERATION_STATUS_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.workbooks.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/views", text=views_xml)
- m.put(server.workbooks.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2", text=response_xml)
- single_workbook = TSC.WorkbookItem("1d0304cd-3796-429f-b815-7258370b9b74", show_tabs=True)
- single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
- single_workbook.data_acceleration_config = {
- "acceleration_enabled": False,
- "accelerate_now": False,
- "last_updated_at": None,
- "acceleration_status": None,
- }
- server.workbooks.populate_views(single_workbook)
- single_workbook.views = [single_workbook.views[1], single_workbook.views[2]]
- # update with parameter includeViewAccelerationStatus=True
- single_workbook = server.workbooks.update(single_workbook, True)
-
- views_list = single_workbook.views
- assert "097dbe13-de89-445f-b2c3-02f28bd010c1" == views_list[0].id
- assert "GDP per capita" == views_list[0].name
- assert views_list[0].data_acceleration_config["acceleration_enabled"] is False
- assert "Disabled" == views_list[0].data_acceleration_config["acceleration_status"]
-
- assert "2c1ab9d7-8d64-4cc6-b495-52e40c60c330" == views_list[1].id
- assert "Country ranks" == views_list[1].name
- assert views_list[1].data_acceleration_config["acceleration_enabled"]
- assert "Pending" == views_list[1].data_acceleration_config["acceleration_status"]
-
- assert "0599c28c-6d82-457e-a453-e52c1bdb00f5" == views_list[2].id
- assert "Interest rates" == views_list[2].name
- assert views_list[2].data_acceleration_config["acceleration_enabled"]
- assert "Pending" == views_list[2].data_acceleration_config["acceleration_status"]
diff --git a/test/test_virtual_connection.py b/test/test_virtual_connection.py
deleted file mode 100644
index 210f605c8..000000000
--- a/test/test_virtual_connection.py
+++ /dev/null
@@ -1,269 +0,0 @@
-import json
-from pathlib import Path
-
-import pytest
-import requests_mock
-
-import tableauserverclient as TSC
-from tableauserverclient.datetime_helpers import parse_datetime
-from tableauserverclient.models.virtual_connection_item import VirtualConnectionItem
-
-ASSET_DIR = Path(__file__).parent / "assets"
-
-VIRTUAL_CONNECTION_GET_XML = ASSET_DIR / "virtual_connections_get.xml"
-VIRTUAL_CONNECTION_POPULATE_CONNECTIONS = ASSET_DIR / "virtual_connection_populate_connections.xml"
-VIRTUAL_CONNECTION_POPULATE_CONNECTIONS2 = ASSET_DIR / "virtual_connection_populate_connections2.xml"
-VC_DB_CONN_UPDATE = ASSET_DIR / "virtual_connection_database_connection_update.xml"
-VIRTUAL_CONNECTION_DOWNLOAD = ASSET_DIR / "virtual_connections_download.xml"
-VIRTUAL_CONNECTION_UPDATE = ASSET_DIR / "virtual_connections_update.xml"
-VIRTUAL_CONNECTION_REVISIONS = ASSET_DIR / "virtual_connections_revisions.xml"
-VIRTUAL_CONNECTION_PUBLISH = ASSET_DIR / "virtual_connections_publish.xml"
-ADD_PERMISSIONS = ASSET_DIR / "virtual_connection_add_permissions.xml"
-
-
-@pytest.fixture(scope="function")
-def server():
- """Fixture to create a TSC.Server instance for testing."""
- server = TSC.Server("http://test", False)
-
- # Fake signin
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
- server.version = "3.23"
-
- return server
-
-
-def test_from_xml(server: TSC.Server) -> None:
- items = VirtualConnectionItem.from_response(VIRTUAL_CONNECTION_GET_XML.read_bytes(), server.namespace)
-
- assert len(items) == 1
- virtual_connection = items[0]
- assert virtual_connection.created_at == parse_datetime("2024-05-30T09:00:00Z")
- assert not virtual_connection.has_extracts
- assert virtual_connection.id == "8fd7cc02-bb55-4d15-b8b1-9650239efe79"
- assert virtual_connection.is_certified
- assert virtual_connection.name == "vconn"
- assert virtual_connection.updated_at == parse_datetime("2024-06-18T09:00:00Z")
- assert virtual_connection.webpage_url == "https://test/#/site/site-name/virtualconnections/3"
-
-
-def test_virtual_connection_get(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.get(server.virtual_connections.baseurl, text=VIRTUAL_CONNECTION_GET_XML.read_text())
- items, pagination_item = server.virtual_connections.get()
-
- assert len(items) == 1
- assert pagination_item.total_available == 1
- assert items[0].name == "vconn"
-
-
-@pytest.mark.parametrize(
- "populate_connections_xml", [VIRTUAL_CONNECTION_POPULATE_CONNECTIONS, VIRTUAL_CONNECTION_POPULATE_CONNECTIONS2]
-)
-def test_virtual_connection_populate_connections(server: TSC.Server, populate_connections_xml: Path) -> None:
- vconn = VirtualConnectionItem("vconn")
- vconn._id = "8fd7cc02-bb55-4d15-b8b1-9650239efe79"
- with requests_mock.mock() as m:
- m.get(f"{server.virtual_connections.baseurl}/{vconn.id}/connections", text=populate_connections_xml.read_text())
- vc_out = server.virtual_connections.populate_connections(vconn)
- connection_list = list(vconn.connections)
-
- assert vc_out is vconn
- assert vc_out._connections is not None
-
- assert len(connection_list) == 1
- connection = connection_list[0]
- assert connection.id == "37ca6ced-58d7-4dcf-99dc-f0a85223cbef"
- assert connection.connection_type == "postgres"
- assert connection.server_address == "localhost"
- assert connection.server_port == "5432"
- assert connection.username == "pgadmin"
-
-
-def test_virtual_connection_update_connection_db_connection(server: TSC.Server) -> None:
- vconn = VirtualConnectionItem("vconn")
- vconn._id = "8fd7cc02-bb55-4d15-b8b1-9650239efe79"
- connection = TSC.ConnectionItem()
- connection._id = "37ca6ced-58d7-4dcf-99dc-f0a85223cbef"
- connection.server_address = "localhost"
- connection.server_port = "5432"
- connection.username = "pgadmin"
- connection.password = "password"
- with requests_mock.mock() as m:
- m.put(
- f"{server.virtual_connections.baseurl}/{vconn.id}/connections/{connection.id}/modify",
- text=VC_DB_CONN_UPDATE.read_text(),
- )
- updated_connection = server.virtual_connections.update_connection_db_connection(vconn, connection)
-
- assert updated_connection.id == "37ca6ced-58d7-4dcf-99dc-f0a85223cbef"
- assert updated_connection.server_address == "localhost"
- assert updated_connection.server_port == "5432"
- assert updated_connection.username == "pgadmin"
- assert updated_connection.password is None
-
-
-def test_virtual_connection_get_by_id(server: TSC.Server) -> None:
- vconn = VirtualConnectionItem("vconn")
- vconn._id = "8fd7cc02-bb55-4d15-b8b1-9650239efe79"
- with requests_mock.mock() as m:
- m.get(f"{server.virtual_connections.baseurl}/{vconn.id}", text=VIRTUAL_CONNECTION_DOWNLOAD.read_text())
- vconn = server.virtual_connections.get_by_id(vconn)
-
- assert vconn.content
- assert vconn.created_at is None
- assert vconn.id is None
- assert "policyCollection" in vconn.content
- assert "revision" in vconn.content
-
-
-def test_virtual_connection_update(server: TSC.Server) -> None:
- vconn = VirtualConnectionItem("vconn")
- vconn._id = "8fd7cc02-bb55-4d15-b8b1-9650239efe79"
- vconn.is_certified = True
- vconn.certification_note = "demo certification note"
- vconn.project_id = "5286d663-8668-4ac2-8c8d-91af7d585f6b"
- vconn.owner_id = "9324cf6b-ba72-4b8e-b895-ac3f28d2f0e0"
- with requests_mock.mock() as m:
- m.put(f"{server.virtual_connections.baseurl}/{vconn.id}", text=VIRTUAL_CONNECTION_UPDATE.read_text())
- vconn = server.virtual_connections.update(vconn)
-
- assert not vconn.has_extracts
- assert vconn.id is None
- assert vconn.is_certified
- assert vconn.name == "testv1"
- assert vconn.certification_note == "demo certification note"
- assert vconn.project_id == "5286d663-8668-4ac2-8c8d-91af7d585f6b"
- assert vconn.owner_id == "9324cf6b-ba72-4b8e-b895-ac3f28d2f0e0"
-
-
-def test_virtual_connection_get_revisions(server: TSC.Server) -> None:
- vconn = VirtualConnectionItem("vconn")
- vconn._id = "8fd7cc02-bb55-4d15-b8b1-9650239efe79"
- with requests_mock.mock() as m:
- m.get(
- f"{server.virtual_connections.baseurl}/{vconn.id}/revisions", text=VIRTUAL_CONNECTION_REVISIONS.read_text()
- )
- revisions, pagination_item = server.virtual_connections.get_revisions(vconn)
-
- assert len(revisions) == 3
- assert pagination_item.total_available == 3
- assert revisions[0].resource_id == vconn.id
- assert revisions[0].resource_name == vconn.name
- assert revisions[0].created_at == parse_datetime("2016-07-26T20:34:56Z")
- assert revisions[0].revision_number == "1"
- assert not revisions[0].current
- assert not revisions[0].deleted
- assert revisions[0].user_name == "Cassie"
- assert revisions[0].user_id == "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7"
- assert revisions[1].resource_id == vconn.id
- assert revisions[1].resource_name == vconn.name
- assert revisions[1].created_at == parse_datetime("2016-07-27T20:34:56Z")
- assert revisions[1].revision_number == "2"
- assert not revisions[1].current
- assert not revisions[1].deleted
- assert revisions[2].resource_id == vconn.id
- assert revisions[2].resource_name == vconn.name
- assert revisions[2].created_at == parse_datetime("2016-07-28T20:34:56Z")
- assert revisions[2].revision_number == "3"
- assert revisions[2].current
- assert not revisions[2].deleted
- assert revisions[2].user_name == "Cassie"
- assert revisions[2].user_id == "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7"
-
-
-def test_virtual_connection_download_revision(server: TSC.Server) -> None:
- vconn = VirtualConnectionItem("vconn")
- vconn._id = "8fd7cc02-bb55-4d15-b8b1-9650239efe79"
- with requests_mock.mock() as m:
- m.get(
- f"{server.virtual_connections.baseurl}/{vconn.id}/revisions/1", text=VIRTUAL_CONNECTION_DOWNLOAD.read_text()
- )
- content = server.virtual_connections.download_revision(vconn, 1)
-
- assert content
- assert "policyCollection" in content
- data = json.loads(content)
- assert "policyCollection" in data
- assert "revision" in data
-
-
-def test_virtual_connection_delete(server: TSC.Server) -> None:
- vconn = VirtualConnectionItem("vconn")
- vconn._id = "8fd7cc02-bb55-4d15-b8b1-9650239efe79"
- with requests_mock.mock() as m:
- m.delete(f"{server.virtual_connections.baseurl}/{vconn.id}")
- server.virtual_connections.delete(vconn)
- server.virtual_connections.delete(vconn.id)
-
- assert m.call_count == 2
-
-
-def test_virtual_connection_publish(server: TSC.Server) -> None:
- vconn = VirtualConnectionItem("vconn")
- vconn._id = "8fd7cc02-bb55-4d15-b8b1-9650239efe79"
- vconn.project_id = "9836791c-9468-40f0-b7f3-d10b9562a046"
- vconn.owner_id = "ee8bc9ca-77fe-4ae0-8093-cf77f0ee67a9"
- with requests_mock.mock() as m:
- m.post(
- f"{server.virtual_connections.baseurl}?overwrite=false&publishAsDraft=false",
- text=VIRTUAL_CONNECTION_PUBLISH.read_text(),
- )
- vconn = server.virtual_connections.publish(vconn, '{"test": 0}', mode="CreateNew", publish_as_draft=False)
-
- assert vconn.name == "vconn_test"
- assert vconn.owner_id == "ee8bc9ca-77fe-4ae0-8093-cf77f0ee67a9"
- assert vconn.project_id == "9836791c-9468-40f0-b7f3-d10b9562a046"
- assert vconn.content
- assert "policyCollection" in vconn.content
- assert "revision" in vconn.content
-
-
-def test_virtual_connection_publish_draft_overwrite(server: TSC.Server) -> None:
- vconn = VirtualConnectionItem("vconn")
- vconn._id = "8fd7cc02-bb55-4d15-b8b1-9650239efe79"
- vconn.project_id = "9836791c-9468-40f0-b7f3-d10b9562a046"
- vconn.owner_id = "ee8bc9ca-77fe-4ae0-8093-cf77f0ee67a9"
- with requests_mock.mock() as m:
- m.post(
- f"{server.virtual_connections.baseurl}?overwrite=true&publishAsDraft=true",
- text=VIRTUAL_CONNECTION_PUBLISH.read_text(),
- )
- vconn = server.virtual_connections.publish(vconn, '{"test": 0}', mode="Overwrite", publish_as_draft=True)
-
- assert vconn.name == "vconn_test"
- assert vconn.owner_id == "ee8bc9ca-77fe-4ae0-8093-cf77f0ee67a9"
- assert vconn.project_id == "9836791c-9468-40f0-b7f3-d10b9562a046"
- assert vconn.content
- assert "policyCollection" in vconn.content
- assert "revision" in vconn.content
-
-
-def test_add_permissions(server: TSC.Server) -> None:
- response_xml = ADD_PERMISSIONS.read_text()
-
- single_virtual_connection = TSC.VirtualConnectionItem("test")
- single_virtual_connection._id = "21778de4-b7b9-44bc-a599-1506a2639ace"
-
- bob = TSC.UserItem.as_reference("7c37ee24-c4b1-42b6-a154-eaeab7ee330a")
- group_of_people = TSC.GroupItem.as_reference("5e5e1978-71fa-11e4-87dd-7382f5c437af")
-
- new_permissions = [
- TSC.PermissionsRule(bob, {"Write": "Allow"}),
- TSC.PermissionsRule(group_of_people, {"Read": "Deny"}),
- ]
-
- with requests_mock.mock() as m:
- m.put(
- server.virtual_connections.baseurl + "/21778de4-b7b9-44bc-a599-1506a2639ace/permissions", text=response_xml
- )
- permissions = server.virtual_connections.add_permissions(single_virtual_connection, new_permissions)
-
- assert permissions[0].grantee.tag_name == "group"
- assert permissions[0].grantee.id == "5e5e1978-71fa-11e4-87dd-7382f5c437af"
- assert permissions[0].capabilities == {TSC.Permission.Capability.Read: TSC.Permission.Mode.Deny}
-
- assert permissions[1].grantee.tag_name == "user"
- assert permissions[1].grantee.id == "7c37ee24-c4b1-42b6-a154-eaeab7ee330a"
- assert permissions[1].capabilities == {TSC.Permission.Capability.Write: TSC.Permission.Mode.Allow}
diff --git a/test/test_webhook.py b/test/test_webhook.py
deleted file mode 100644
index 4fa011da0..000000000
--- a/test/test_webhook.py
+++ /dev/null
@@ -1,169 +0,0 @@
-from pathlib import Path
-
-import pytest
-import requests_mock
-
-import tableauserverclient as TSC
-from tableauserverclient.server import RequestFactory
-from tableauserverclient.models import WebhookItem
-
-TEST_ASSET_DIR = Path(__file__).parent / "assets"
-
-GET_XML = TEST_ASSET_DIR / "webhook_get.xml"
-GET_NEW_EVENT_XML = TEST_ASSET_DIR / "webhook_get_new_event.xml"
-CREATE_XML = TEST_ASSET_DIR / "webhook_create.xml"
-CREATE_REQUEST_XML = TEST_ASSET_DIR / "webhook_create_request.xml"
-
-
-@pytest.fixture(scope="function")
-def server():
- """Fixture to create a TSC.Server instance for testing."""
- server = TSC.Server("http://test", False)
-
- # Fake signin
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
- server.version = "3.6"
-
- return server
-
-
-def test_get(server: TSC.Server) -> None:
- response_xml = GET_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.webhooks.baseurl, text=response_xml)
- webhooks, _ = server.webhooks.get()
- assert len(webhooks) == 1
- webhook = webhooks[0]
-
- assert webhook.url == "url"
- assert webhook.event == "datasource-created"
- assert webhook.owner_id == "webhook_owner_luid"
- assert webhook.name == "webhook-name"
- assert webhook.id == "webhook-id"
-
-
-def test_get_before_signin(server: TSC.Server) -> None:
- server._auth_token = None
- with pytest.raises(TSC.NotSignedInError):
- server.webhooks.get()
-
-
-def test_delete(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.delete(server.webhooks.baseurl + "/ee8c6e70-43b6-11e6-af4f-f7b0d8e20760", status_code=204)
- server.webhooks.delete("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760")
-
-
-def test_delete_missing_id(server: TSC.Server) -> None:
- with pytest.raises(ValueError):
- server.webhooks.delete("")
-
-
-def test_test(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.get(server.webhooks.baseurl + "/ee8c6e70-43b6-11e6-af4f-f7b0d8e20760/test", status_code=200)
- server.webhooks.test("ee8c6e70-43b6-11e6-af4f-f7b0d8e20760")
-
-
-def test_create(server: TSC.Server) -> None:
- response_xml = CREATE_XML.read_text()
- with requests_mock.mock() as m:
- m.post(server.webhooks.baseurl, text=response_xml)
- webhook_model = TSC.WebhookItem()
- webhook_model.name = "Test Webhook"
- webhook_model.url = "https://ifttt.com/maker-url"
- webhook_model.event = "datasource-created"
-
- new_webhook = server.webhooks.create(webhook_model)
-
- assert new_webhook.id is not None
-
-
-def test_request_factory():
- webhook_request_expected = CREATE_REQUEST_XML.read_text()
-
- webhook_item = WebhookItem()
- webhook_item._set_values("webhook-id", "webhook-name", "url", "api-event-name", None)
- webhook_request_actual = "{}\n".format(RequestFactory.Webhook.create_req(webhook_item).decode("utf-8"))
- # windows does /r/n for linebreaks, remove the extra char if it is there
- assert webhook_request_expected.replace("\r", "") == webhook_request_actual
-
-
-def test_event_setter_none():
- """Setting event to None should store None without crashing."""
- item = WebhookItem()
- item.event = "datasource-updated"
- assert item.event == "datasource-updated"
- item.event = None
- assert item._event is None
- assert item.event is None
-
-
-def test_event_setter_short_name():
- """Short event names should be stored with the webhook-source-event- prefix."""
- item = WebhookItem()
- item.event = "datasource-updated"
- assert item._event == "webhook-source-event-datasource-updated"
- assert item.event == "datasource-updated"
-
-
-def test_event_setter_full_source_name():
- """Full webhook-source-event- names should be accepted and stored as-is."""
- item = WebhookItem()
- item.event = "webhook-source-event-datasource-updated"
- assert item._event == "webhook-source-event-datasource-updated"
- assert item.event == "datasource-updated"
-
-
-def test_event_setter_new_style_event_name():
- """New-style event names (webhook-event-*) should be stored as-is and not mangled."""
- item = WebhookItem()
- item.event = "webhook-event-user-promoted-admin"
- assert item._event == "webhook-event-user-promoted-admin"
- assert item.event == "webhook-event-user-promoted-admin"
-
-
-def test_get_new_style_event(server: TSC.Server) -> None:
- """Webhooks with new-style event names (webhook-event-*) should parse correctly."""
- response_xml = GET_NEW_EVENT_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.webhooks.baseurl, text=response_xml)
- webhooks, _ = server.webhooks.get()
- assert len(webhooks) == 1
- webhook = webhooks[0]
-
- assert webhook.id == "webhook-id-2"
- assert webhook.name == "webhook-name-2"
- assert webhook.url == "https://example.com/hook"
- # New-style event name should not have the webhook-source-event- prefix stripped
- assert webhook.event == "webhook-event-user-promoted-admin"
- assert webhook.owner_id == "webhook_owner_luid"
-
-
-def test_create_with_short_event_name(server: TSC.Server) -> None:
- """Creating a webhook with a short event name (e.g. datasource-created) should work."""
- response_xml = CREATE_XML.read_text()
- with requests_mock.mock() as m:
- m.post(server.webhooks.baseurl, text=response_xml)
- webhook_model = TSC.WebhookItem()
- webhook_model.name = "Test Webhook"
- webhook_model.url = "https://ifttt.com/maker-url"
- webhook_model.event = "datasource-created"
-
- new_webhook = server.webhooks.create(webhook_model)
- assert new_webhook.id is not None
-
-
-def test_create_with_source_event_name(server: TSC.Server) -> None:
- """Creating a webhook with a full webhook-source-event-* name should work."""
- response_xml = CREATE_XML.read_text()
- with requests_mock.mock() as m:
- m.post(server.webhooks.baseurl, text=response_xml)
- webhook_model = TSC.WebhookItem()
- webhook_model.name = "Test Webhook"
- webhook_model.url = "https://ifttt.com/maker-url"
- webhook_model.event = "webhook-source-event-datasource-created"
-
- new_webhook = server.webhooks.create(webhook_model)
- assert new_webhook.id is not None
diff --git a/test/test_workbook.py b/test/test_workbook.py
deleted file mode 100644
index 7f4d80041..000000000
--- a/test/test_workbook.py
+++ /dev/null
@@ -1,1249 +0,0 @@
-import os
-import re
-import requests_mock
-import tempfile
-from defusedxml.ElementTree import fromstring
-from io import BytesIO
-from pathlib import Path
-
-import pytest
-
-import tableauserverclient as TSC
-from tableauserverclient.datetime_helpers import format_datetime, parse_datetime
-from tableauserverclient.models import UserItem, GroupItem, PermissionsRule
-from tableauserverclient.server.endpoint.exceptions import InternalServerError, UnsupportedAttributeError
-from tableauserverclient.server.request_factory import RequestFactory
-
-TEST_ASSET_DIR = Path(__file__).parent / "assets"
-
-ADD_TAGS_XML = TEST_ASSET_DIR / "workbook_add_tags.xml"
-GET_BY_ID_XML = TEST_ASSET_DIR / "workbook_get_by_id.xml"
-GET_BY_ID_XML_PERSONAL = TEST_ASSET_DIR / "workbook_get_by_id_personal.xml"
-GET_EMPTY_XML = TEST_ASSET_DIR / "workbook_get_empty.xml"
-GET_INVALID_DATE_XML = TEST_ASSET_DIR / "workbook_get_invalid_date.xml"
-GET_XML = TEST_ASSET_DIR / "workbook_get.xml"
-GET_XML_ALL_FIELDS = TEST_ASSET_DIR / "workbook_get_all_fields.xml"
-ODATA_XML = TEST_ASSET_DIR / "odata_connection.xml"
-POPULATE_CONNECTIONS_XML = TEST_ASSET_DIR / "workbook_populate_connections.xml"
-POPULATE_PDF = TEST_ASSET_DIR / "populate_pdf.pdf"
-POPULATE_POWERPOINT = TEST_ASSET_DIR / "populate_powerpoint.pptx"
-POPULATE_PERMISSIONS_XML = TEST_ASSET_DIR / "workbook_populate_permissions.xml"
-POPULATE_PREVIEW_IMAGE = TEST_ASSET_DIR / "RESTAPISample Image.png"
-POPULATE_VIEWS_XML = TEST_ASSET_DIR / "workbook_populate_views.xml"
-POPULATE_VIEWS_USAGE_XML = TEST_ASSET_DIR / "workbook_populate_views_usage.xml"
-PUBLISH_XML = TEST_ASSET_DIR / "workbook_publish.xml"
-PUBLISH_ASYNC_XML = TEST_ASSET_DIR / "workbook_publish_async.xml"
-REFRESH_XML = TEST_ASSET_DIR / "workbook_refresh.xml"
-WORKBOOK_REFRESH_DUPLICATE_XML = TEST_ASSET_DIR / "workbook_refresh_duplicate.xml"
-REVISION_XML = TEST_ASSET_DIR / "workbook_revision.xml"
-UPDATE_XML = TEST_ASSET_DIR / "workbook_update.xml"
-UPDATE_PERMISSIONS = TEST_ASSET_DIR / "workbook_update_permissions.xml"
-UPDATE_CONNECTIONS_XML = TEST_ASSET_DIR / "workbook_update_connections.xml"
-UPDATE_CONNECTIONS_NO_AUTH_XML = TEST_ASSET_DIR / "workbook_update_connections_no_auth.xml"
-
-
-@pytest.fixture(scope="function")
-def server() -> TSC.Server:
- server = TSC.Server("http://test", False)
-
- # Fake sign in
- server._site_id = "dad65087-b08b-4603-af4e-2887b8aafc67"
- server._auth_token = "j80k54ll2lfMZ0tv97mlPvvSCRyD0DOM"
-
- return server
-
-
-def test_get(server: TSC.Server) -> None:
- response_xml = GET_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.workbooks.baseurl, text=response_xml)
- all_workbooks, pagination_item = server.workbooks.get()
-
- assert 2 == pagination_item.total_available
- assert "6d13b0ca-043d-4d42-8c9d-3f3313ea3a00" == all_workbooks[0].id
- assert "Superstore" == all_workbooks[0].name
- assert "Superstore" == all_workbooks[0].content_url
- assert not all_workbooks[0].show_tabs
- assert "http://tableauserver/#/workbooks/1/views" == all_workbooks[0].webpage_url
- assert 1 == all_workbooks[0].size
- assert "2016-08-03T20:34:04Z" == format_datetime(all_workbooks[0].created_at)
- assert "description for Superstore" == all_workbooks[0].description
- assert "2016-08-04T17:56:41Z" == format_datetime(all_workbooks[0].updated_at)
- assert "ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" == all_workbooks[0].project_id
- assert "default" == all_workbooks[0].project_name
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == all_workbooks[0].owner_id
-
- assert "3cc6cd06-89ce-4fdc-b935-5294135d6d42" == all_workbooks[1].id
- assert "SafariSample" == all_workbooks[1].name
- assert "SafariSample" == all_workbooks[1].content_url
- assert "http://tableauserver/#/workbooks/2/views" == all_workbooks[1].webpage_url
- assert not all_workbooks[1].show_tabs
- assert 26 == all_workbooks[1].size
- assert "2016-07-26T20:34:56Z" == format_datetime(all_workbooks[1].created_at)
- assert "description for SafariSample" == all_workbooks[1].description
- assert "2016-07-26T20:35:05Z" == format_datetime(all_workbooks[1].updated_at)
- assert "ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" == all_workbooks[1].project_id
- assert "default" == all_workbooks[1].project_name
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == all_workbooks[1].owner_id
- assert {"Safari", "Sample"} == all_workbooks[1].tags
-
-
-def test_get_ignore_invalid_date(server: TSC.Server) -> None:
- response_xml = GET_INVALID_DATE_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.workbooks.baseurl, text=response_xml)
- all_workbooks, pagination_item = server.workbooks.get()
- assert format_datetime(all_workbooks[0].created_at) is None
- assert "2016-08-04T17:56:41Z" == format_datetime(all_workbooks[0].updated_at)
-
-
-def test_get_before_signin(server: TSC.Server) -> None:
- server._auth_token = None
- with pytest.raises(TSC.NotSignedInError):
- server.workbooks.get()
-
-
-def test_get_empty(server: TSC.Server) -> None:
- response_xml = GET_EMPTY_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.workbooks.baseurl, text=response_xml)
- all_workbooks, pagination_item = server.workbooks.get()
-
- assert 0 == pagination_item.total_available
- assert [] == all_workbooks
-
-
-def test_get_by_id(server: TSC.Server) -> None:
- response_xml = GET_BY_ID_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.workbooks.baseurl + "/3cc6cd06-89ce-4fdc-b935-5294135d6d42", text=response_xml)
- single_workbook = server.workbooks.get_by_id("3cc6cd06-89ce-4fdc-b935-5294135d6d42")
-
- assert "3cc6cd06-89ce-4fdc-b935-5294135d6d42" == single_workbook.id
- assert "SafariSample" == single_workbook.name
- assert "SafariSample" == single_workbook.content_url
- assert "http://tableauserver/#/workbooks/2/views" == single_workbook.webpage_url
- assert not single_workbook.show_tabs
- assert 26 == single_workbook.size
- assert "2016-07-26T20:34:56Z" == format_datetime(single_workbook.created_at)
- assert "description for SafariSample" == single_workbook.description
- assert "2016-07-26T20:35:05Z" == format_datetime(single_workbook.updated_at)
- assert "ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" == single_workbook.project_id
- assert "default" == single_workbook.project_name
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == single_workbook.owner_id
- assert {"Safari", "Sample"} == single_workbook.tags
- assert "d79634e1-6063-4ec9-95ff-50acbf609ff5" == single_workbook.views[0].id
- assert "ENDANGERED SAFARI" == single_workbook.views[0].name
- assert "SafariSample/sheets/ENDANGEREDSAFARI" == single_workbook.views[0].content_url
-
-
-def test_get_by_id_personal(server: TSC.Server) -> None:
- # workbooks in personal space don't have project_id or project_name
- response_xml = GET_BY_ID_XML_PERSONAL.read_text()
- with requests_mock.mock() as m:
- m.get(server.workbooks.baseurl + "/3cc6cd06-89ce-4fdc-b935-5294135d6d43", text=response_xml)
- single_workbook = server.workbooks.get_by_id("3cc6cd06-89ce-4fdc-b935-5294135d6d43")
-
- assert "3cc6cd06-89ce-4fdc-b935-5294135d6d43" == single_workbook.id
- assert "SafariSample" == single_workbook.name
- assert "SafariSample" == single_workbook.content_url
- assert "http://tableauserver/#/workbooks/2/views" == single_workbook.webpage_url
- assert not single_workbook.show_tabs
- assert 26 == single_workbook.size
- assert "2016-07-26T20:34:56Z" == format_datetime(single_workbook.created_at)
- assert "description for SafariSample" == single_workbook.description
- assert "2016-07-26T20:35:05Z" == format_datetime(single_workbook.updated_at)
- assert single_workbook.project_id
- assert single_workbook.project_name is None
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == single_workbook.owner_id
- assert {"Safari", "Sample"} == single_workbook.tags
- assert "d79634e1-6063-4ec9-95ff-50acbf609ff5" == single_workbook.views[0].id
- assert "ENDANGERED SAFARI" == single_workbook.views[0].name
- assert "SafariSample/sheets/ENDANGEREDSAFARI" == single_workbook.views[0].content_url
-
-
-def test_get_by_id_missing_id(server: TSC.Server) -> None:
- with pytest.raises(ValueError):
- server.workbooks.get_by_id("")
-
-
-def test_refresh_id(server: TSC.Server) -> None:
- server.version = "2.8"
- response_xml = REFRESH_XML.read_text()
- with requests_mock.mock() as m:
- m.post(
- server.workbooks.baseurl + "/3cc6cd06-89ce-4fdc-b935-5294135d6d42/refresh",
- status_code=202,
- text=response_xml,
- )
- server.workbooks.refresh("3cc6cd06-89ce-4fdc-b935-5294135d6d42")
-
-
-def test_refresh_already_running(server: TSC.Server) -> None:
- server.version = "2.8"
- response_xml = WORKBOOK_REFRESH_DUPLICATE_XML.read_text()
- with requests_mock.mock() as m:
- m.post(
- server.workbooks.baseurl + "/3cc6cd06-89ce-4fdc-b935-5294135d6d42/refresh",
- status_code=409,
- text=response_xml,
- )
- refresh_job = server.workbooks.refresh("3cc6cd06-89ce-4fdc-b935-5294135d6d42")
- assert refresh_job is None
-
-
-def test_refresh_object(server: TSC.Server) -> None:
- server.version = "2.8"
- workbook = TSC.WorkbookItem("")
- workbook._id = "3cc6cd06-89ce-4fdc-b935-5294135d6d42"
- response_xml = REFRESH_XML.read_text()
- with requests_mock.mock() as m:
- m.post(
- server.workbooks.baseurl + "/3cc6cd06-89ce-4fdc-b935-5294135d6d42/refresh",
- status_code=202,
- text=response_xml,
- )
- server.workbooks.refresh(workbook)
-
-
-def test_delete(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.delete(server.workbooks.baseurl + "/3cc6cd06-89ce-4fdc-b935-5294135d6d42", status_code=204)
- server.workbooks.delete("3cc6cd06-89ce-4fdc-b935-5294135d6d42")
-
-
-def test_delete_missing_id(server: TSC.Server) -> None:
- with pytest.raises(ValueError):
- server.workbooks.delete("")
-
-
-def test_update(server: TSC.Server) -> None:
- response_xml = UPDATE_XML.read_text()
- with requests_mock.mock() as m:
- m.put(server.workbooks.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2", text=response_xml)
- single_workbook = TSC.WorkbookItem("1d0304cd-3796-429f-b815-7258370b9b74", show_tabs=True)
- single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
- single_workbook.owner_id = "dd2239f6-ddf1-4107-981a-4cf94e415794"
- single_workbook.name = "renamedWorkbook"
- single_workbook.data_acceleration_config = {
- "acceleration_enabled": True,
- "accelerate_now": False,
- "last_updated_at": None,
- "acceleration_status": None,
- }
- single_workbook = server.workbooks.update(single_workbook)
-
- assert "1f951daf-4061-451a-9df1-69a8062664f2" == single_workbook.id
- assert single_workbook.show_tabs
- assert "1d0304cd-3796-429f-b815-7258370b9b74" == single_workbook.project_id
- assert "dd2239f6-ddf1-4107-981a-4cf94e415794" == single_workbook.owner_id
- assert "renamedWorkbook" == single_workbook.name
- assert single_workbook.data_acceleration_config["acceleration_enabled"]
- assert not single_workbook.data_acceleration_config["accelerate_now"]
-
-
-def test_update_description_in_request_xml(server: TSC.Server) -> None:
- """description should be included in the update request XML when server >= 3.21."""
- server.version = "3.21"
- response_xml = UPDATE_XML.read_text()
- with requests_mock.mock() as m:
- m.put(server.workbooks.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2", text=response_xml)
- single_workbook = TSC.WorkbookItem("1d0304cd-3796-429f-b815-7258370b9b74", show_tabs=True)
- single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
- single_workbook.description = "A great workbook"
- server.workbooks.update(single_workbook)
- request_body = m.request_history[0].body
- xml_root = fromstring(request_body)
- workbook_el = xml_root.find(".//workbook")
- assert workbook_el is not None
- assert workbook_el.get("description") == "A great workbook"
-
-
-def test_update_description_excluded_below_v3_21(server: TSC.Server) -> None:
- """description should be excluded from the update request XML when server < 3.21."""
- server.version = "3.20"
- response_xml = UPDATE_XML.read_text()
- with requests_mock.mock() as m:
- m.put(server.workbooks.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2", text=response_xml)
- single_workbook = TSC.WorkbookItem("1d0304cd-3796-429f-b815-7258370b9b74", show_tabs=True)
- single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
- single_workbook.description = "A great workbook"
- server.workbooks.update(single_workbook)
- request_body = m.request_history[0].body
- xml_root = fromstring(request_body)
- workbook_el = xml_root.find(".//workbook")
- assert workbook_el is not None
- assert workbook_el.get("description") is None
-
-
-def test_update_missing_id(server: TSC.Server) -> None:
- single_workbook = TSC.WorkbookItem("test")
- with pytest.raises(TSC.MissingRequiredFieldError):
- server.workbooks.update(single_workbook)
-
-
-def test_update_copy_fields(server: TSC.Server) -> None:
- connection_xml = POPULATE_CONNECTIONS_XML.read_text()
- update_xml = UPDATE_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.workbooks.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/connections", text=connection_xml)
- m.put(server.workbooks.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2", text=update_xml)
- single_workbook = TSC.WorkbookItem("1d0304cd-3796-429f-b815-7258370b9b74")
- single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
- server.workbooks.populate_connections(single_workbook)
- updated_workbook = server.workbooks.update(single_workbook)
-
- assert single_workbook._connections == updated_workbook._connections
- assert single_workbook._views == updated_workbook._views
- assert single_workbook.tags == updated_workbook.tags
- assert single_workbook._initial_tags == updated_workbook._initial_tags
- assert single_workbook._preview_image == updated_workbook._preview_image
-
-
-def test_update_tags(server: TSC.Server) -> None:
- add_tags_xml = ADD_TAGS_XML.read_text()
- update_xml = UPDATE_XML.read_text()
- with requests_mock.mock() as m:
- m.put(server.workbooks.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/tags", text=add_tags_xml)
- m.delete(server.workbooks.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/tags/b", status_code=204)
- m.delete(server.workbooks.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/tags/d", status_code=204)
- m.put(server.workbooks.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2", text=update_xml)
- single_workbook = TSC.WorkbookItem("1d0304cd-3796-429f-b815-7258370b9b74")
- single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
- single_workbook._initial_tags.update(["a", "b", "c", "d"])
- single_workbook.tags.update(["a", "c", "e"])
- updated_workbook = server.workbooks.update(single_workbook)
-
- assert single_workbook.tags == updated_workbook.tags
- assert single_workbook._initial_tags == updated_workbook._initial_tags
-
-
-def test_download(server: TSC.Server, tmp_path: Path) -> None:
- with requests_mock.mock() as m:
- m.get(
- server.workbooks.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/content",
- headers={"Content-Disposition": 'name="tableau_workbook"; filename="RESTAPISample.twbx"'},
- )
- file_path = server.workbooks.download("1f951daf-4061-451a-9df1-69a8062664f2", filepath=tmp_path)
- assert os.path.exists(file_path)
-
-
-def test_download_object(server: TSC.Server) -> None:
- with BytesIO() as file_object:
- with requests_mock.mock() as m:
- m.get(
- server.workbooks.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/content",
- headers={"Content-Disposition": 'name="tableau_workbook"; filename="RESTAPISample.twbx"'},
- )
- file_path = server.workbooks.download("1f951daf-4061-451a-9df1-69a8062664f2", filepath=file_object)
- assert isinstance(file_path, BytesIO)
-
-
-def test_download_sanitizes_name(server: TSC.Server, tmp_path: Path) -> None:
- filename = "Name,With,Commas.twbx"
- disposition = f'name="tableau_workbook"; filename="{filename}"'
- with requests_mock.mock() as m:
- m.get(
- server.workbooks.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/content",
- headers={"Content-Disposition": disposition},
- )
- file_path = server.workbooks.download("1f951daf-4061-451a-9df1-69a8062664f2", filepath=tmp_path)
- assert os.path.basename(file_path) == "NameWithCommas.twbx"
- assert os.path.exists(file_path)
-
-
-def test_download_extract_only(server: TSC.Server, tmp_path: Path) -> None:
- # Pretend we're 2.5 for 'extract_only'
- server.version = "2.5"
-
- with requests_mock.mock() as m:
- m.get(
- server.workbooks.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/content?includeExtract=False",
- headers={"Content-Disposition": 'name="tableau_workbook"; filename="RESTAPISample.twbx"'},
- complete_qs=True,
- )
- # Technically this shouldn't download a twbx, but we are interested in the qs, not the file
- file_path = server.workbooks.download(
- "1f951daf-4061-451a-9df1-69a8062664f2", include_extract=False, filepath=tmp_path
- )
- assert os.path.exists(file_path)
-
-
-def test_download_no_extract_emits_deprecation_warning(server: TSC.Server, tmp_path: Path) -> None:
- """no_extract=True should emit a DeprecationWarning and map to includeExtract=False."""
- server.version = "2.5"
-
- with requests_mock.mock() as m:
- m.get(
- server.workbooks.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/content?includeExtract=False",
- headers={"Content-Disposition": 'name="tableau_workbook"; filename="RESTAPISample.twbx"'},
- complete_qs=True,
- )
- with pytest.warns(DeprecationWarning, match="deprecated and will be removed"):
- file_path = server.workbooks.download(
- "1f951daf-4061-451a-9df1-69a8062664f2", no_extract=True, filepath=tmp_path
- )
- assert os.path.exists(file_path)
-
-
-def test_download_missing_id(server: TSC.Server) -> None:
- with pytest.raises(ValueError):
- server.workbooks.download("")
-
-
-def test_populate_views(server: TSC.Server) -> None:
- response_xml = POPULATE_VIEWS_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.workbooks.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/views", text=response_xml)
- single_workbook = TSC.WorkbookItem("test")
- single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
- server.workbooks.populate_views(single_workbook)
-
- views_list = single_workbook.views
- assert "097dbe13-de89-445f-b2c3-02f28bd010c1" == views_list[0].id
- assert "GDP per capita" == views_list[0].name
- assert "RESTAPISample/sheets/GDPpercapita" == views_list[0].content_url
-
- assert "2c1ab9d7-8d64-4cc6-b495-52e40c60c330" == views_list[1].id
- assert "Country ranks" == views_list[1].name
- assert "RESTAPISample/sheets/Countryranks" == views_list[1].content_url
-
- assert "0599c28c-6d82-457e-a453-e52c1bdb00f5" == views_list[2].id
- assert "Interest rates" == views_list[2].name
- assert "RESTAPISample/sheets/Interestrates" == views_list[2].content_url
-
-
-def test_populate_views_with_usage(server: TSC.Server) -> None:
- response_xml = POPULATE_VIEWS_USAGE_XML.read_text()
- with requests_mock.mock() as m:
- m.get(
- server.workbooks.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/views?includeUsageStatistics=true",
- text=response_xml,
- )
- single_workbook = TSC.WorkbookItem("test")
- single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
- server.workbooks.populate_views(single_workbook, usage=True)
-
- views_list = single_workbook.views
- assert "097dbe13-de89-445f-b2c3-02f28bd010c1" == views_list[0].id
- assert 2 == views_list[0].total_views
- assert "2c1ab9d7-8d64-4cc6-b495-52e40c60c330" == views_list[1].id
- assert 37 == views_list[1].total_views
- assert "0599c28c-6d82-457e-a453-e52c1bdb00f5" == views_list[2].id
- assert 0 == views_list[2].total_views
-
-
-def test_populate_views_missing_id(server: TSC.Server) -> None:
- single_workbook = TSC.WorkbookItem("test")
- with pytest.raises(TSC.MissingRequiredFieldError):
- server.workbooks.populate_views(single_workbook)
-
-
-def test_populate_connections(server: TSC.Server) -> None:
- response_xml = POPULATE_CONNECTIONS_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.workbooks.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/connections", text=response_xml)
- single_workbook = TSC.WorkbookItem("test")
- single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
- server.workbooks.populate_connections(single_workbook)
-
- assert "37ca6ced-58d7-4dcf-99dc-f0a85223cbef" == single_workbook.connections[0].id
- assert "dataengine" == single_workbook.connections[0].connection_type
- assert "4506225a-0d32-4ab1-82d3-c24e85f7afba" == single_workbook.connections[0].datasource_id
- assert "World Indicators" == single_workbook.connections[0].datasource_name
-
-
-def test_populate_permissions(server: TSC.Server) -> None:
- response_xml = POPULATE_PERMISSIONS_XML.read_text()
- with requests_mock.mock() as m:
- m.get(server.workbooks.baseurl + "/21778de4-b7b9-44bc-a599-1506a2639ace/permissions", text=response_xml)
- single_workbook = TSC.WorkbookItem("test")
- single_workbook._id = "21778de4-b7b9-44bc-a599-1506a2639ace"
-
- server.workbooks.populate_permissions(single_workbook)
- permissions = single_workbook.permissions
-
- assert permissions[0].grantee.tag_name == "group"
- assert permissions[0].grantee.id == "5e5e1978-71fa-11e4-87dd-7382f5c437af"
- assert permissions[0].capabilities == {
- TSC.Permission.Capability.WebAuthoring: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.Read: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.Filter: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.AddComment: TSC.Permission.Mode.Allow,
- }
-
- assert permissions[1].grantee.tag_name == "user"
- assert permissions[1].grantee.id == "7c37ee24-c4b1-42b6-a154-eaeab7ee330a"
- assert permissions[1].capabilities == {
- TSC.Permission.Capability.ExportImage: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.ShareView: TSC.Permission.Mode.Allow,
- TSC.Permission.Capability.ExportData: TSC.Permission.Mode.Deny,
- TSC.Permission.Capability.ViewComments: TSC.Permission.Mode.Deny,
- }
-
-
-def test_add_permissions(server: TSC.Server) -> None:
- response_xml = UPDATE_PERMISSIONS.read_text()
-
- single_workbook = TSC.WorkbookItem("test")
- single_workbook._id = "21778de4-b7b9-44bc-a599-1506a2639ace"
-
- bob = UserItem.as_reference("7c37ee24-c4b1-42b6-a154-eaeab7ee330a")
- group_of_people = GroupItem.as_reference("5e5e1978-71fa-11e4-87dd-7382f5c437af")
-
- new_permissions = [PermissionsRule(bob, {"Write": "Allow"}), PermissionsRule(group_of_people, {"Read": "Deny"})]
-
- with requests_mock.mock() as m:
- m.put(server.workbooks.baseurl + "/21778de4-b7b9-44bc-a599-1506a2639ace/permissions", text=response_xml)
- permissions = server.workbooks.update_permissions(single_workbook, new_permissions)
-
- assert permissions[0].grantee.tag_name == "group"
- assert permissions[0].grantee.id == "5e5e1978-71fa-11e4-87dd-7382f5c437af"
- assert permissions[0].capabilities == {TSC.Permission.Capability.Read: TSC.Permission.Mode.Deny}
- assert permissions[1].grantee.tag_name == "user"
- assert permissions[1].grantee.id == "7c37ee24-c4b1-42b6-a154-eaeab7ee330a"
- assert permissions[1].capabilities == {TSC.Permission.Capability.Write: TSC.Permission.Mode.Allow}
-
-
-def test_populate_connections_missing_id(server: TSC.Server) -> None:
- single_workbook = TSC.WorkbookItem("test")
- with pytest.raises(TSC.MissingRequiredFieldError):
- server.workbooks.populate_connections(single_workbook)
-
-
-def test_populate_pdf(server: TSC.Server) -> None:
- server.version = "3.4"
- response = POPULATE_PDF.read_bytes()
- with requests_mock.mock() as m:
- m.get(
- server.workbooks.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/pdf?type=a5&orientation=landscape",
- content=response,
- )
- single_workbook = TSC.WorkbookItem("test")
- single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
-
- type = TSC.PDFRequestOptions.PageType.A5
- orientation = TSC.PDFRequestOptions.Orientation.Landscape
- req_option = TSC.PDFRequestOptions(type, orientation)
-
- server.workbooks.populate_pdf(single_workbook, req_option)
- assert response == single_workbook.pdf
-
-
-def test_populate_pdf_unsupported(server: TSC.Server) -> None:
- server.version = "3.4"
- with requests_mock.mock() as m:
- m.get(
- server.workbooks.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/pdf?type=a5&orientation=landscape",
- content=b"",
- )
- single_workbook = TSC.WorkbookItem("test")
- single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
-
- type = TSC.PDFRequestOptions.PageType.A5
- orientation = TSC.PDFRequestOptions.Orientation.Landscape
- req_option = TSC.PDFRequestOptions(type, orientation)
- req_option.vf("Region", "West")
-
- with pytest.raises(UnsupportedAttributeError):
- server.workbooks.populate_pdf(single_workbook, req_option)
-
-
-def test_populate_pdf_vf_dims(server: TSC.Server) -> None:
- server.version = "3.23"
- response = POPULATE_PDF.read_bytes()
- with requests_mock.mock() as m:
- m.get(
- server.workbooks.baseurl
- + "/1f951daf-4061-451a-9df1-69a8062664f2/pdf?type=a5&orientation=landscape&vf_Region=West&vizWidth=1920&vizHeight=1080",
- content=response,
- )
- single_workbook = TSC.WorkbookItem("test")
- single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
-
- type = TSC.PDFRequestOptions.PageType.A5
- orientation = TSC.PDFRequestOptions.Orientation.Landscape
- req_option = TSC.PDFRequestOptions(type, orientation)
- req_option.vf("Region", "West")
- req_option.viz_width = 1920
- req_option.viz_height = 1080
-
- server.workbooks.populate_pdf(single_workbook, req_option)
- assert response == single_workbook.pdf
-
-
-def test_populate_powerpoint(server: TSC.Server) -> None:
- server.version = "3.8"
- response = POPULATE_POWERPOINT.read_bytes()
- with requests_mock.mock() as m:
- m.get(server.workbooks.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/powerpoint?maxAge=1", content=response)
- single_workbook = TSC.WorkbookItem("test")
- single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
-
- ro = TSC.PPTXRequestOptions(maxage=1)
-
- server.workbooks.populate_powerpoint(single_workbook, ro)
- assert response == single_workbook.powerpoint
-
-
-def test_populate_preview_image(server: TSC.Server) -> None:
- response = POPULATE_PREVIEW_IMAGE.read_bytes()
- with requests_mock.mock() as m:
- m.get(server.workbooks.baseurl + "/1f951daf-4061-451a-9df1-69a8062664f2/previewImage", content=response)
- single_workbook = TSC.WorkbookItem("test")
- single_workbook._id = "1f951daf-4061-451a-9df1-69a8062664f2"
- server.workbooks.populate_preview_image(single_workbook)
-
- assert response == single_workbook.preview_image
-
-
-def test_populate_preview_image_missing_id(server: TSC.Server) -> None:
- single_workbook = TSC.WorkbookItem("test")
- with pytest.raises(TSC.MissingRequiredFieldError):
- server.workbooks.populate_preview_image(single_workbook)
-
-
-def test_publish(server: TSC.Server) -> None:
- response_xml = PUBLISH_XML.read_text()
- with requests_mock.mock() as m:
- m.post(server.workbooks.baseurl, text=response_xml)
-
- new_workbook = TSC.WorkbookItem(
- name="Sample", show_tabs=False, project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760"
- )
-
- new_workbook.description = "REST API Testing"
-
- sample_workbook = os.path.join(TEST_ASSET_DIR, "SampleWB.twbx")
- publish_mode = server.PublishMode.CreateNew
-
- new_workbook = server.workbooks.publish(new_workbook, sample_workbook, publish_mode)
- assert "a8076ca1-e9d8-495e-bae6-c684dbb55836" == new_workbook.id
- assert "RESTAPISample" == new_workbook.name
- assert "RESTAPISample_0" == new_workbook.content_url
- assert not new_workbook.show_tabs
- assert 1 == new_workbook.size
- assert "2016-08-18T18:33:24Z" == format_datetime(new_workbook.created_at)
- assert "2016-08-18T20:31:34Z" == format_datetime(new_workbook.updated_at)
- assert "ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" == new_workbook.project_id
- assert "default" == new_workbook.project_name
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == new_workbook.owner_id
- assert "fe0b4e89-73f4-435e-952d-3a263fbfa56c" == new_workbook.views[0].id
- assert "GDP per capita" == new_workbook.views[0].name
- assert "RESTAPISample_0/sheets/GDPpercapita" == new_workbook.views[0].content_url
- assert "REST API Testing" == new_workbook.description
-
-
-def test_publish_description_in_request_xml(server: TSC.Server) -> None:
- """description should be included in the publish request XML."""
- response_xml = PUBLISH_XML.read_text()
- with requests_mock.mock() as m:
- m.post(server.workbooks.baseurl, text=response_xml)
- new_workbook = TSC.WorkbookItem(
- name="Sample", show_tabs=False, project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760"
- )
- new_workbook.description = "A great workbook"
- sample_workbook = os.path.join(TEST_ASSET_DIR, "SampleWB.twbx")
- server.workbooks.publish(new_workbook, sample_workbook, server.PublishMode.CreateNew)
- request_body = m._adapter.request_history[0]._request.body
- assert re.search(b'description=\\"A great workbook\\"', request_body)
-
-
-def test_publish_a_packaged_file_object(server: TSC.Server) -> None:
- response_xml = PUBLISH_XML.read_text()
- with requests_mock.mock() as m:
- m.post(server.workbooks.baseurl, text=response_xml)
-
- new_workbook = TSC.WorkbookItem(
- name="Sample", show_tabs=False, project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760"
- )
-
- sample_workbook = os.path.join(TEST_ASSET_DIR, "SampleWB.twbx")
-
- with open(sample_workbook, "rb") as fp:
- publish_mode = server.PublishMode.CreateNew
-
- new_workbook = server.workbooks.publish(new_workbook, fp, publish_mode)
-
- assert "a8076ca1-e9d8-495e-bae6-c684dbb55836" == new_workbook.id
- assert "RESTAPISample" == new_workbook.name
- assert "RESTAPISample_0" == new_workbook.content_url
- assert not new_workbook.show_tabs
- assert 1 == new_workbook.size
- assert "2016-08-18T18:33:24Z" == format_datetime(new_workbook.created_at)
- assert "2016-08-18T20:31:34Z" == format_datetime(new_workbook.updated_at)
- assert "ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" == new_workbook.project_id
- assert "default" == new_workbook.project_name
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == new_workbook.owner_id
- assert "fe0b4e89-73f4-435e-952d-3a263fbfa56c" == new_workbook.views[0].id
- assert "GDP per capita" == new_workbook.views[0].name
- assert "RESTAPISample_0/sheets/GDPpercapita" == new_workbook.views[0].content_url
-
-
-def test_publish_non_packeged_file_object(server: TSC.Server) -> None:
- response_xml = PUBLISH_XML.read_text()
- with requests_mock.mock() as m:
- m.post(server.workbooks.baseurl, text=response_xml)
-
- new_workbook = TSC.WorkbookItem(
- name="Sample", show_tabs=False, project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760"
- )
-
- sample_workbook = os.path.join(TEST_ASSET_DIR, "RESTAPISample.twb")
-
- with open(sample_workbook, "rb") as fp:
- publish_mode = server.PublishMode.CreateNew
-
- new_workbook = server.workbooks.publish(new_workbook, fp, publish_mode)
-
- assert "a8076ca1-e9d8-495e-bae6-c684dbb55836" == new_workbook.id
- assert "RESTAPISample" == new_workbook.name
- assert "RESTAPISample_0" == new_workbook.content_url
- assert not new_workbook.show_tabs
- assert 1 == new_workbook.size
- assert "2016-08-18T18:33:24Z" == format_datetime(new_workbook.created_at)
- assert "2016-08-18T20:31:34Z" == format_datetime(new_workbook.updated_at)
- assert "ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" == new_workbook.project_id
- assert "default" == new_workbook.project_name
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == new_workbook.owner_id
- assert "fe0b4e89-73f4-435e-952d-3a263fbfa56c" == new_workbook.views[0].id
- assert "GDP per capita" == new_workbook.views[0].name
- assert "RESTAPISample_0/sheets/GDPpercapita" == new_workbook.views[0].content_url
-
-
-def test_publish_path_object(server: TSC.Server) -> None:
- response_xml = PUBLISH_XML.read_text()
- with requests_mock.mock() as m:
- m.post(server.workbooks.baseurl, text=response_xml)
-
- new_workbook = TSC.WorkbookItem(
- name="Sample", show_tabs=False, project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760"
- )
-
- sample_workbook = Path(TEST_ASSET_DIR) / "SampleWB.twbx"
- publish_mode = server.PublishMode.CreateNew
-
- new_workbook = server.workbooks.publish(new_workbook, sample_workbook, publish_mode)
-
- assert "a8076ca1-e9d8-495e-bae6-c684dbb55836" == new_workbook.id
- assert "RESTAPISample" == new_workbook.name
- assert "RESTAPISample_0" == new_workbook.content_url
- assert not new_workbook.show_tabs
- assert 1 == new_workbook.size
- assert "2016-08-18T18:33:24Z" == format_datetime(new_workbook.created_at)
- assert "2016-08-18T20:31:34Z" == format_datetime(new_workbook.updated_at)
- assert "ee8c6e70-43b6-11e6-af4f-f7b0d8e20760" == new_workbook.project_id
- assert "default" == new_workbook.project_name
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == new_workbook.owner_id
- assert "fe0b4e89-73f4-435e-952d-3a263fbfa56c" == new_workbook.views[0].id
- assert "GDP per capita" == new_workbook.views[0].name
- assert "RESTAPISample_0/sheets/GDPpercapita" == new_workbook.views[0].content_url
-
-
-def test_publish_with_hidden_views_on_workbook(server: TSC.Server) -> None:
- response_xml = PUBLISH_XML.read_text()
- with requests_mock.mock() as m:
- m.post(server.workbooks.baseurl, text=response_xml)
-
- new_workbook = TSC.WorkbookItem(
- name="Sample", show_tabs=False, project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760"
- )
-
- sample_workbook = os.path.join(TEST_ASSET_DIR, "SampleWB.twbx")
- publish_mode = server.PublishMode.CreateNew
-
- new_workbook.hidden_views = ["GDP per capita"]
- new_workbook = server.workbooks.publish(new_workbook, sample_workbook, publish_mode)
- request_body = m._adapter.request_history[0]._request.body
- # order of attributes in xml is unspecified
- assert re.search(b' <\\/views>', request_body)
- assert re.search(b' <\\/views>', request_body)
-
-
-def test_publish_with_thumbnails_user_id(server: TSC.Server) -> None:
- response_xml = PUBLISH_XML.read_text()
- with requests_mock.mock() as m:
- m.post(server.workbooks.baseurl, text=response_xml)
-
- new_workbook = TSC.WorkbookItem(
- name="Sample",
- show_tabs=False,
- project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760",
- thumbnails_user_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20761",
- )
-
- sample_workbook = os.path.join(TEST_ASSET_DIR, "SampleWB.twbx")
- publish_mode = server.PublishMode.CreateNew
- new_workbook = server.workbooks.publish(new_workbook, sample_workbook, publish_mode)
- request_body = m._adapter.request_history[0]._request.body
- # order of attributes in xml is unspecified
- assert re.search(b'thumbnailsUserId=\\"ee8c6e70-43b6-11e6-af4f-f7b0d8e20761\\"', request_body)
-
-
-def test_publish_with_thumbnails_group_id(server: TSC.Server) -> None:
- response_xml = PUBLISH_XML.read_text()
- with requests_mock.mock() as m:
- m.post(server.workbooks.baseurl, text=response_xml)
-
- new_workbook = TSC.WorkbookItem(
- name="Sample",
- show_tabs=False,
- project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760",
- thumbnails_group_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20762",
- )
-
- sample_workbook = os.path.join(TEST_ASSET_DIR, "SampleWB.twbx")
- publish_mode = server.PublishMode.CreateNew
- new_workbook = server.workbooks.publish(new_workbook, sample_workbook, publish_mode)
- request_body = m._adapter.request_history[0]._request.body
- assert re.search(b'thumbnailsGroupId=\\"ee8c6e70-43b6-11e6-af4f-f7b0d8e20762\\"', request_body)
-
-
-@pytest.mark.filterwarnings("ignore:'as_job' not available")
-def test_publish_with_query_params(server: TSC.Server) -> None:
- response_xml = PUBLISH_ASYNC_XML.read_text()
- with requests_mock.mock() as m:
- m.post(server.workbooks.baseurl, text=response_xml)
-
- new_workbook = TSC.WorkbookItem(
- name="Sample", show_tabs=False, project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760"
- )
-
- sample_workbook = os.path.join(TEST_ASSET_DIR, "SampleWB.twbx")
- publish_mode = server.PublishMode.CreateNew
-
- server.workbooks.publish(new_workbook, sample_workbook, publish_mode, as_job=True, skip_connection_check=True)
-
- request_query_params = m._adapter.request_history[0].qs
- assert "asjob" in request_query_params
- assert request_query_params["asjob"]
- assert "skipconnectioncheck" in request_query_params
- assert request_query_params["skipconnectioncheck"]
-
-
-def test_publish_async(server: TSC.Server) -> None:
- server.version = "3.0"
- baseurl = server.workbooks.baseurl
- response_xml = PUBLISH_ASYNC_XML.read_text()
- with requests_mock.mock() as m:
- m.post(baseurl, text=response_xml)
-
- new_workbook = TSC.WorkbookItem(
- name="Sample", show_tabs=False, project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760"
- )
-
- sample_workbook = os.path.join(TEST_ASSET_DIR, "SampleWB.twbx")
- publish_mode = server.PublishMode.CreateNew
-
- new_job = server.workbooks.publish(new_workbook, sample_workbook, publish_mode, as_job=True)
-
- assert "7c3d599e-949f-44c3-94a1-f30ba85757e4" == new_job.id
- assert "PublishWorkbook" == new_job.type
- assert "0" == new_job.progress
- assert "2018-06-29T23:22:32Z" == format_datetime(new_job.created_at)
- assert 1 == new_job.finish_code
-
-
-def test_publish_invalid_file(server: TSC.Server) -> None:
- new_workbook = TSC.WorkbookItem("test", "ee8c6e70-43b6-11e6-af4f-f7b0d8e20760")
- with pytest.raises(IOError):
- server.workbooks.publish(new_workbook, ".", server.PublishMode.CreateNew)
-
-
-def test_publish_invalid_file_type(server: TSC.Server) -> None:
- new_workbook = TSC.WorkbookItem("test", "ee8c6e70-43b6-11e6-af4f-f7b0d8e20760")
- with pytest.raises(ValueError):
- server.workbooks.publish(
- new_workbook, os.path.join(TEST_ASSET_DIR, "SampleDS.tds"), server.PublishMode.CreateNew
- )
-
-
-def test_publish_unnamed_file_object(server: TSC.Server) -> None:
- new_workbook = TSC.WorkbookItem("test")
-
- with open(os.path.join(TEST_ASSET_DIR, "SampleWB.twbx"), "rb") as f:
- with pytest.raises(ValueError):
- server.workbooks.publish(new_workbook, f, server.PublishMode.CreateNew)
-
-
-def test_publish_non_bytes_file_object(server: TSC.Server) -> None:
- new_workbook = TSC.WorkbookItem("test")
-
- with open(os.path.join(TEST_ASSET_DIR, "SampleWB.twbx")) as f:
- with pytest.raises(TypeError):
- server.workbooks.publish(new_workbook, f, server.PublishMode.CreateNew)
-
-
-def test_publish_file_object_of_unknown_type_raises_exception(server: TSC.Server) -> None:
- new_workbook = TSC.WorkbookItem("test")
- with BytesIO() as file_object:
- file_object.write(bytes.fromhex("89504E470D0A1A0A"))
- file_object.seek(0)
- with pytest.raises(ValueError):
- server.workbooks.publish(new_workbook, file_object, server.PublishMode.CreateNew)
-
-
-def test_publish_multi_connection(server: TSC.Server) -> None:
- new_workbook = TSC.WorkbookItem(name="Sample", show_tabs=False, project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760")
- connection1 = TSC.ConnectionItem()
- connection1.server_address = "mysql.test.com"
- connection1.connection_credentials = TSC.ConnectionCredentials("test", "secret", True)
- connection2 = TSC.ConnectionItem()
- connection2.server_address = "pgsql.test.com"
- connection2.connection_credentials = TSC.ConnectionCredentials("test", "secret", True)
-
- response = RequestFactory.Workbook._generate_xml(new_workbook, connections=[connection1, connection2])
- # Can't use ConnectionItem parser due to xml namespace problems
- connection_results = fromstring(response).findall(".//connection")
-
- assert connection_results[0].get("serverAddress", None) == "mysql.test.com"
- assert connection_results[0].find("connectionCredentials").get("name", None) == "test"
- assert connection_results[1].get("serverAddress", None) == "pgsql.test.com"
- assert connection_results[1].find("connectionCredentials").get("password", None) == "secret"
-
-
-def test_publish_multi_connection_flat(server: TSC.Server) -> None:
- new_workbook = TSC.WorkbookItem(name="Sample", show_tabs=False, project_id="ee8c6e70-43b6-11e6-af4f-f7b0d8e20760")
- connection1 = TSC.ConnectionItem()
- connection1.server_address = "mysql.test.com"
- connection1.username = "test"
- connection1.password = "secret"
- connection1.embed_password = True
- connection2 = TSC.ConnectionItem()
- connection2.server_address = "pgsql.test.com"
- connection2.username = "test"
- connection2.password = "secret"
- connection2.embed_password = True
-
- response = RequestFactory.Workbook._generate_xml(new_workbook, connections=[connection1, connection2])
- # Can't use ConnectionItem parser due to xml namespace problems
- connection_results = fromstring(response).findall(".//connection")
-
- assert connection_results[0].get("serverAddress", None) == "mysql.test.com"
- assert connection_results[0].find("connectionCredentials").get("name", None) == "test"
- assert connection_results[1].get("serverAddress", None) == "pgsql.test.com"
- assert connection_results[1].find("connectionCredentials").get("password", None) == "secret"
-
-
-def test_synchronous_publish_timeout_error(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.register_uri("POST", server.workbooks.baseurl, status_code=504)
-
- new_workbook = TSC.WorkbookItem(project_id="")
- publish_mode = server.PublishMode.CreateNew
-
- with pytest.raises(InternalServerError, match="Please use asynchronous publishing to avoid timeouts"):
- server.workbooks.publish(new_workbook, TEST_ASSET_DIR / "SampleWB.twbx", publish_mode)
-
-
-def test_async_publish_timeout_error(server: TSC.Server) -> None:
- with requests_mock.mock() as m:
- m.register_uri("POST", server.workbooks.baseurl, status_code=504)
-
- new_workbook = TSC.WorkbookItem(project_id="")
- publish_mode = server.PublishMode.CreateNew
-
- with pytest.raises(InternalServerError, match="TSC_CHUNK_SIZE_MB"):
- server.workbooks.publish(new_workbook, TEST_ASSET_DIR / "SampleWB.twbx", publish_mode, as_job=True)
-
-
-def test_delete_extracts_all(server: TSC.Server) -> None:
- server.version = "3.10"
-
- response_xml = PUBLISH_ASYNC_XML.read_text()
- with requests_mock.mock() as m:
- m.post(
- server.workbooks.baseurl + "/3cc6cd06-89ce-4fdc-b935-5294135d6d42/deleteExtract",
- status_code=200,
- text=response_xml,
- )
- server.workbooks.delete_extract("3cc6cd06-89ce-4fdc-b935-5294135d6d42")
-
-
-def test_create_extracts_all(server: TSC.Server) -> None:
- server.version = "3.10"
-
- response_xml = PUBLISH_ASYNC_XML.read_text()
- with requests_mock.mock() as m:
- m.post(
- server.workbooks.baseurl + "/3cc6cd06-89ce-4fdc-b935-5294135d6d42/createExtract",
- status_code=200,
- text=response_xml,
- )
- server.workbooks.create_extract("3cc6cd06-89ce-4fdc-b935-5294135d6d42")
-
-
-def test_create_extracts_one(server: TSC.Server) -> None:
- server.version = "3.10"
-
- datasource = TSC.DatasourceItem("test")
- datasource._id = "1f951daf-4061-451a-9df1-69a8062664f2"
-
- response_xml = PUBLISH_ASYNC_XML.read_text()
- with requests_mock.mock() as m:
- m.post(
- server.workbooks.baseurl + "/3cc6cd06-89ce-4fdc-b935-5294135d6d42/createExtract",
- status_code=200,
- text=response_xml,
- )
- server.workbooks.create_extract("3cc6cd06-89ce-4fdc-b935-5294135d6d42", False, datasource)
-
-
-def test_revisions(server: TSC.Server) -> None:
- workbook = TSC.WorkbookItem("project", "test")
- workbook._id = "06b944d2-959d-4604-9305-12323c95e70e"
-
- response_xml = REVISION_XML.read_text()
- with requests_mock.mock() as m:
- m.get(f"{server.workbooks.baseurl}/{workbook.id}/revisions", text=response_xml)
- server.workbooks.populate_revisions(workbook)
- revisions = workbook.revisions
-
- assert len(revisions) == 3
- assert "2016-07-26T20:34:56Z" == format_datetime(revisions[0].created_at)
- assert "2016-07-27T20:34:56Z" == format_datetime(revisions[1].created_at)
- assert "2016-07-28T20:34:56Z" == format_datetime(revisions[2].created_at)
-
- assert not revisions[0].deleted
- assert not revisions[0].current
- assert not revisions[1].deleted
- assert not revisions[1].current
- assert not revisions[2].deleted
- assert revisions[2].current
-
- assert "Cassie" == revisions[0].user_name
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == revisions[0].user_id
- assert revisions[1].user_name is None
- assert revisions[1].user_id is None
- assert "Cassie" == revisions[2].user_name
- assert "5de011f8-5aa9-4d5b-b991-f462c8dd6bb7" == revisions[2].user_id
-
-
-def test_delete_revision(server: TSC.Server) -> None:
- workbook = TSC.WorkbookItem("project", "test")
- workbook._id = "06b944d2-959d-4604-9305-12323c95e70e"
-
- with requests_mock.mock() as m:
- m.delete(f"{server.workbooks.baseurl}/{workbook.id}/revisions/3")
- server.workbooks.delete_revision(workbook.id, "3")
-
-
-def test_download_revision(server: TSC.Server) -> None:
- with requests_mock.mock() as m, tempfile.TemporaryDirectory() as td:
- m.get(
- server.workbooks.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/revisions/3/content",
- headers={"Content-Disposition": 'name="tableau_datasource"; filename="Sample datasource.tds"'},
- )
- file_path = server.workbooks.download_revision("9dbd2263-16b5-46e1-9c43-a76bb8ab65fb", "3", td)
- assert os.path.exists(file_path)
-
-
-def test_bad_download_response(server: TSC.Server) -> None:
- with requests_mock.mock() as m, tempfile.TemporaryDirectory() as td:
- m.get(
- server.workbooks.baseurl + "/9dbd2263-16b5-46e1-9c43-a76bb8ab65fb/content",
- headers={"Content-Disposition": '''name="tableau_workbook"; filename*=UTF-8''"Sample workbook.twb"'''},
- )
- file_path = server.workbooks.download("9dbd2263-16b5-46e1-9c43-a76bb8ab65fb", td)
- assert os.path.exists(file_path)
-
-
-def test_odata_connection(server: TSC.Server) -> None:
- workbook = TSC.WorkbookItem("project", "test")
- workbook._id = "06b944d2-959d-4604-9305-12323c95e70e"
- connection = TSC.ConnectionItem()
- url = "https://odata.website.com/TestODataEndpoint"
- connection.server_address = url
- connection._connection_type = "odata"
- connection._id = "17376070-64d1-4d17-acb4-a56e4b5b1768"
-
- creds = TSC.ConnectionCredentials("", "", True)
- connection.connection_credentials = creds
- response_xml = ODATA_XML.read_text()
-
- with requests_mock.mock() as m:
- m.put(f"{server.workbooks.baseurl}/{workbook.id}/connections/{connection.id}", text=response_xml)
- server.workbooks.update_connection(workbook, connection)
-
- history = m.request_history
-
- request = history[0]
- xml = fromstring(request.body)
- xml_connection = xml.find(".//connection")
-
- assert xml_connection is not None
- assert xml_connection.get("serverAddress") == url
-
-
-def test_update_workbook_connections(server: TSC.Server) -> None:
- populate_xml = POPULATE_CONNECTIONS_XML.read_text()
- response_xml = UPDATE_CONNECTIONS_XML.read_text()
-
- with requests_mock.Mocker() as m:
- workbook_id = "1a2b3c4d-5e6f-7a8b-9c0d-112233445566"
- connection_luids = ["abc12345-def6-7890-gh12-ijklmnopqrst", "1234abcd-5678-efgh-ijkl-0987654321mn"]
-
- workbook = TSC.WorkbookItem(workbook_id)
- workbook._id = workbook_id
- server.version = "3.26"
- url = f"{server.baseurl}/{workbook_id}/connections"
- m.get(
- "http://test/api/3.26/sites/dad65087-b08b-4603-af4e-2887b8aafc67/workbooks/1a2b3c4d-5e6f-7a8b-9c0d-112233445566/connections",
- text=populate_xml,
- )
- m.put(
- "http://test/api/3.26/sites/dad65087-b08b-4603-af4e-2887b8aafc67/workbooks/1a2b3c4d-5e6f-7a8b-9c0d-112233445566/connections",
- text=response_xml,
- )
-
- connection_items = server.workbooks.update_connections(
- workbook_item=workbook,
- connection_luids=connection_luids,
- authentication_type="AD Service Principal",
- username="svc-client",
- password="secret-token",
- embed_password=True,
- )
- updated_ids = [conn.id for conn in connection_items]
-
- assert updated_ids == connection_luids
- assert "AD Service Principal" == connection_items[0].auth_type
-
-
-def test_update_workbook_connections_without_auth_type(server: TSC.Server) -> None:
- """Test that update_connections works when authentication_type is not provided."""
- populate_xml = POPULATE_CONNECTIONS_XML.read_text()
- response_xml = UPDATE_CONNECTIONS_NO_AUTH_XML.read_text()
-
- with requests_mock.Mocker() as m:
- workbook_id = "1a2b3c4d-5e6f-7a8b-9c0d-112233445566"
- connection_luids = ["abc12345-def6-7890-gh12-ijklmnopqrst", "1234abcd-5678-efgh-ijkl-0987654321mn"]
-
- workbook = TSC.WorkbookItem(workbook_id)
- workbook._id = workbook_id
- server.version = "3.26"
- url = f"{server.baseurl}/{workbook_id}/connections"
- m.get(
- "http://test/api/3.26/sites/dad65087-b08b-4603-af4e-2887b8aafc67/workbooks/1a2b3c4d-5e6f-7a8b-9c0d-112233445566/connections",
- text=populate_xml,
- )
- m.put(
- "http://test/api/3.26/sites/dad65087-b08b-4603-af4e-2887b8aafc67/workbooks/1a2b3c4d-5e6f-7a8b-9c0d-112233445566/connections",
- text=response_xml,
- )
-
- # Update connections without specifying authentication_type
- connection_items = server.workbooks.update_connections(
- workbook_item=workbook,
- connection_luids=connection_luids,
- username="user1",
- embed_password=True,
- )
- updated_ids = [conn.id for conn in connection_items]
-
- assert updated_ids == connection_luids
- # Verify that the auth type from the response is preserved (UsernamePassword)
- assert connection_items[0].auth_type == "UsernamePassword"
-
-
-def test_get_workbook_all_fields(server: TSC.Server) -> None:
- server.version = "3.21"
- baseurl = server.workbooks.baseurl
-
- response = GET_XML_ALL_FIELDS.read_text()
-
- ro = TSC.RequestOptions()
- ro.all_fields = True
-
- with requests_mock.mock() as m:
- m.get(f"{baseurl}?fields=_all_", text=response)
- workbooks, _ = server.workbooks.get(req_options=ro)
-
- assert workbooks[0].id == "9df3e2d1-070e-497a-9578-8cc557ced9df"
- assert workbooks[0].name == "Superstore"
- assert workbooks[0].content_url == "Superstore"
- assert workbooks[0].webpage_url == "https://10ax.online.tableau.com/#/site/exampledev/workbooks/265605"
- assert workbooks[0].show_tabs
- assert workbooks[0].size == 2
- assert workbooks[0].created_at == parse_datetime("2024-02-14T04:42:09Z")
- assert workbooks[0].updated_at == parse_datetime("2024-02-14T04:42:10Z")
- assert workbooks[0].sheet_count == 9
- assert not workbooks[0].has_extracts
- assert not workbooks[0].encrypt_extracts
- assert workbooks[0].default_view_id == "2bdcd787-dcc6-4a5d-bc61-2846f1ef4534"
- assert workbooks[0].share_description == "Superstore"
- assert workbooks[0].last_published_at == parse_datetime("2024-02-14T04:42:09Z")
- assert isinstance(workbooks[0].project, TSC.ProjectItem)
- assert workbooks[0].project.id == "669ca36b-492e-4ccf-bca1-3614fe6a9d7a"
- assert workbooks[0].project.name == "Samples"
- assert workbooks[0].project.description == "This project includes automatically uploaded samples."
- assert isinstance(workbooks[0].location, TSC.LocationItem)
- assert workbooks[0].location.id == "669ca36b-492e-4ccf-bca1-3614fe6a9d7a"
- assert workbooks[0].location.type == "Project"
- assert workbooks[0].location.name == "Samples"
- assert isinstance(workbooks[0].owner, TSC.UserItem)
- assert workbooks[0].owner.email == "bob@example.com"
- assert workbooks[0].owner.fullname == "Bob Smith"
- assert workbooks[0].owner.id == "ee8bc9ca-77fe-4ae0-8093-cf77f0ee67a9"
- assert workbooks[0].owner.last_login == parse_datetime("2025-02-04T06:39:20Z")
- assert workbooks[0].owner.name == "bob@example.com"
- assert workbooks[0].owner.site_role == "SiteAdministratorCreator"
- assert workbooks[1].id == "6693cb26-9507-4174-ad3e-9de81a18c971"
- assert workbooks[1].name == "World Indicators"
- assert workbooks[1].content_url == "WorldIndicators"
- assert workbooks[1].webpage_url == "https://10ax.online.tableau.com/#/site/exampledev/workbooks/265606"
- assert workbooks[1].show_tabs
- assert workbooks[1].size == 1
- assert workbooks[1].created_at == parse_datetime("2024-02-14T04:42:11Z")
- assert workbooks[1].updated_at == parse_datetime("2024-02-14T04:42:12Z")
- assert workbooks[1].sheet_count == 8
- assert not workbooks[1].has_extracts
- assert not workbooks[1].encrypt_extracts
- assert workbooks[1].default_view_id == "3d10dbcf-a206-47c7-91ba-ebab3ab33d7c"
- assert workbooks[1].share_description == "World Indicators"
- assert workbooks[1].last_published_at == parse_datetime("2024-02-14T04:42:11Z")
- assert isinstance(workbooks[1].project, TSC.ProjectItem)
- assert workbooks[1].project.id == "669ca36b-492e-4ccf-bca1-3614fe6a9d7a"
- assert workbooks[1].project.name == "Samples"
- assert workbooks[1].project.description == "This project includes automatically uploaded samples."
- assert isinstance(workbooks[1].location, TSC.LocationItem)
- assert workbooks[1].location.id == "669ca36b-492e-4ccf-bca1-3614fe6a9d7a"
- assert workbooks[1].location.type == "Project"
- assert workbooks[1].location.name == "Samples"
- assert isinstance(workbooks[1].owner, TSC.UserItem)
- assert workbooks[1].owner.email == "bob@example.com"
- assert workbooks[1].owner.fullname == "Bob Smith"
- assert workbooks[1].owner.id == "ee8bc9ca-77fe-4ae0-8093-cf77f0ee67a9"
- assert workbooks[1].owner.last_login == parse_datetime("2025-02-04T06:39:20Z")
- assert workbooks[1].owner.name == "bob@example.com"
- assert workbooks[1].owner.site_role == "SiteAdministratorCreator"
- assert workbooks[2].id == "dbc0f162-909f-4edf-8392-0d12a80af955"
- assert workbooks[2].name == "Superstore"
- assert workbooks[2].description == "This is a superstore workbook"
- assert workbooks[2].content_url == "Superstore_17078880698360"
- assert workbooks[2].webpage_url == "https://10ax.online.tableau.com/#/site/exampledev/workbooks/265621"
- assert not workbooks[2].show_tabs
- assert workbooks[2].size == 1
- assert workbooks[2].created_at == parse_datetime("2024-02-14T05:21:09Z")
- assert workbooks[2].updated_at == parse_datetime("2024-07-02T02:19:59Z")
- assert workbooks[2].sheet_count == 7
- assert workbooks[2].has_extracts
- assert not workbooks[2].encrypt_extracts
- assert workbooks[2].default_view_id == "8c4b1d3e-3f31-4d2a-8b9f-492b92f27987"
- assert workbooks[2].share_description == "Superstore"
- assert workbooks[2].last_published_at == parse_datetime("2024-07-02T02:19:58Z")
- assert isinstance(workbooks[2].project, TSC.ProjectItem)
- assert workbooks[2].project.id == "9836791c-9468-40f0-b7f3-d10b9562a046"
- assert workbooks[2].project.name == "default"
- assert workbooks[2].project.description == "The default project that was automatically created by Tableau."
- assert isinstance(workbooks[2].location, TSC.LocationItem)
- assert workbooks[2].location.id == "9836791c-9468-40f0-b7f3-d10b9562a046"
- assert workbooks[2].location.type == "Project"
- assert workbooks[2].location.name == "default"
- assert isinstance(workbooks[2].owner, TSC.UserItem)
- assert workbooks[2].owner.email == "bob@example.com"
- assert workbooks[2].owner.fullname == "Bob Smith"
- assert workbooks[2].owner.id == "ee8bc9ca-77fe-4ae0-8093-cf77f0ee67a9"
- assert workbooks[2].owner.last_login == parse_datetime("2025-02-04T06:39:20Z")
- assert workbooks[2].owner.name == "bob@example.com"
- assert workbooks[2].owner.site_role == "SiteAdministratorCreator"
diff --git a/test/test_workbook_model.py b/test/test_workbook_model.py
deleted file mode 100644
index 3d6f31a7a..000000000
--- a/test/test_workbook_model.py
+++ /dev/null
@@ -1,12 +0,0 @@
-import pytest
-
-import tableauserverclient as TSC
-
-
-def test_invalid_show_tabs():
- workbook = TSC.WorkbookItem("10")
- with pytest.raises(ValueError):
- workbook.show_tabs = "Hello"
-
- with pytest.raises(ValueError):
- workbook.show_tabs = None
diff --git a/test_e2e/__init__.py b/test_e2e/__init__.py
deleted file mode 100644
index e69de29bb..000000000
diff --git a/test_e2e/assets/WorkbookWithoutExtract.twbx b/test_e2e/assets/WorkbookWithoutExtract.twbx
deleted file mode 100644
index 49fc684af..000000000
Binary files a/test_e2e/assets/WorkbookWithoutExtract.twbx and /dev/null differ
diff --git a/test_e2e/conftest.py b/test_e2e/conftest.py
deleted file mode 100644
index 376d41a09..000000000
--- a/test_e2e/conftest.py
+++ /dev/null
@@ -1,32 +0,0 @@
-import os
-import pytest
-import tableauserverclient as TSC
-
-
-def pytest_configure(config):
- config.addinivalue_line("markers", "e2e: mark test as end-to-end (requires a real Tableau server)")
-
-
-@pytest.fixture(scope="session")
-def server():
- """
- Authenticated TSC server session for e2e tests.
-
- Required environment variables:
- TABLEAU_SERVER β server URL, e.g. https://10ax.online.tableau.com
- TABLEAU_SITE β site content URL
- TABLEAU_TOKEN β personal access token value
- TABLEAU_TOKEN_NAME β personal access token name
- """
- url = os.environ.get("TABLEAU_SERVER")
- site = os.environ.get("TABLEAU_SITE", "")
- token = os.environ.get("TABLEAU_TOKEN")
- token_name = os.environ.get("TABLEAU_TOKEN_NAME")
-
- if not all([url, token, token_name]):
- pytest.skip("E2E tests require TABLEAU_SERVER, TABLEAU_TOKEN, and TABLEAU_TOKEN_NAME env vars")
-
- server = TSC.Server(url, use_server_version=True)
- auth = TSC.PersonalAccessTokenAuth(token_name, token, site)
- with server.auth.sign_in(auth):
- yield server
diff --git a/test_e2e/test_tagging.py b/test_e2e/test_tagging.py
deleted file mode 100644
index 4af741255..000000000
--- a/test_e2e/test_tagging.py
+++ /dev/null
@@ -1,78 +0,0 @@
-"""
-E2E tests for tag operations against a real Tableau server.
-
-Run with:
- TABLEAU_SERVER=https://... TABLEAU_SITE=mysite TABLEAU_TOKEN=... TABLEAU_TOKEN_NAME=... \
- pytest test_e2e/test_tagging.py -v
-"""
-import os
-from pathlib import Path
-
-import pytest
-import tableauserverclient as TSC
-
-ASSETS_DIR = Path(__file__).parent / "assets"
-SAMPLE_WORKBOOK = ASSETS_DIR / "WorkbookWithoutExtract.twbx"
-
-pytestmark = pytest.mark.e2e
-
-
-@pytest.fixture(scope="module")
-def workbook(server):
- """Publish a workbook for tagging tests, clean up after.
-
- Uses TABLEAU_PROJECT env var if set, otherwise falls back to the first
- project named 'Default' or 'Personal Work', then the first available project.
- """
- project_name = os.environ.get("TABLEAU_PROJECT", "Default")
- opts = TSC.RequestOptions()
- opts.filter.add(TSC.Filter(TSC.RequestOptions.Field.Name, TSC.RequestOptions.Operator.Equals, project_name))
- projects, _ = server.projects.get(opts)
- if not projects:
- pytest.skip(f"Project {project_name!r} not found β set TABLEAU_PROJECT env var")
- project = projects[0]
-
- wb = TSC.WorkbookItem(name="tsc-e2e-tagging-test", project_id=project.id)
- wb = server.workbooks.publish(wb, SAMPLE_WORKBOOK, TSC.Server.PublishMode.Overwrite)
- yield wb
- server.workbooks.delete(wb.id)
-
-
-def test_tag_with_spaces_stored_as_single_tag(server, workbook):
- """A tag containing a space must be stored as one tag, not split on the space."""
- spaced_tag = "Yearly Sales"
- server.workbooks.add_tags(workbook, spaced_tag)
- updated = server.workbooks.get_by_id(workbook.id)
- try:
- assert spaced_tag in updated.tags, (
- f"Tag '{spaced_tag}' not found in {updated.tags!r} β was it split on the space?"
- )
- assert "Yearly" not in updated.tags, "Tag was incorrectly split β 'Yearly' should not be a separate tag"
- assert "Sales" not in updated.tags, "Tag was incorrectly split β 'Sales' should not be a separate tag"
- finally:
- server.workbooks.delete_tags(workbook, spaced_tag)
-
-
-def test_tag_with_comma_stored_as_single_tag(server, workbook):
- """A tag containing a comma must be stored as one tag, not split on the comma."""
- comma_tag = "Sales,Marketing"
- server.workbooks.add_tags(workbook, comma_tag)
- updated = server.workbooks.get_by_id(workbook.id)
- try:
- assert comma_tag in updated.tags, (
- f"Tag '{comma_tag}' not found in {updated.tags!r} β was it split on the comma?"
- )
- finally:
- server.workbooks.delete_tags(workbook, comma_tag)
-
-
-def test_multiple_tags_including_spaced(server, workbook):
- """Adding multiple tags where one has a space should all round-trip correctly."""
- tags = ["simple", "Yearly Sales", "another tag"]
- server.workbooks.add_tags(workbook, tags)
- updated = server.workbooks.get_by_id(workbook.id)
- try:
- for tag in tags:
- assert tag in updated.tags, f"Tag '{tag}' not found in {updated.tags!r}"
- finally:
- server.workbooks.delete_tags(workbook, tags)