mirror of
https://github.com/sstent/go-garth.git
synced 2026-03-13 11:35:21 +00:00
working auth and activity list
This commit is contained in:
1057
GarminEndpoints.md
Normal file
1057
GarminEndpoints.md
Normal file
File diff suppressed because it is too large
Load Diff
21
garth/.coderabbit.yaml
Normal file
21
garth/.coderabbit.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json # Schema for CodeRabbit configurations
|
||||
language: "en-US"
|
||||
early_access: true
|
||||
reviews:
|
||||
request_changes_workflow: false
|
||||
high_level_summary: true
|
||||
poem: false
|
||||
review_status: true
|
||||
collapse_walkthrough: false
|
||||
auto_review:
|
||||
enabled: true
|
||||
drafts: false
|
||||
path_filters:
|
||||
- "!tests/**/cassettes/**"
|
||||
path_instructions:
|
||||
- path: "tests/**"
|
||||
instructions: |
|
||||
- test functions shouldn't have a return type hint
|
||||
- it's ok to use `assert` instead of `pytest.assume()`
|
||||
chat:
|
||||
auto_reply: true
|
||||
7
garth/.devcontainer/Dockerfile
Normal file
7
garth/.devcontainer/Dockerfile
Normal file
@@ -0,0 +1,7 @@
|
||||
FROM mcr.microsoft.com/devcontainers/anaconda:0-3
|
||||
|
||||
# Copy environment.yml (if found) to a temp location so we update the environment. Also
|
||||
# copy "noop.txt" so the COPY instruction does not fail if no environment.yml exists.
|
||||
COPY environment.yml* .devcontainer/noop.txt /tmp/conda-tmp/
|
||||
RUN if [ -f "/tmp/conda-tmp/environment.yml" ]; then umask 0002 && /opt/conda/bin/conda env update -n base -f /tmp/conda-tmp/environment.yml; fi \
|
||||
&& rm -rf /tmp/conda-tmp
|
||||
10
garth/.devcontainer/devcontainer.json
Normal file
10
garth/.devcontainer/devcontainer.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "Anaconda (Python 3)",
|
||||
"build": {
|
||||
"context": "..",
|
||||
"dockerfile": "Dockerfile"
|
||||
},
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/node:1": {}
|
||||
}
|
||||
}
|
||||
3
garth/.devcontainer/noop.txt
Normal file
3
garth/.devcontainer/noop.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
This file copied into the container along with environment.yml* from the parent
|
||||
folder. This file is included to prevents the Dockerfile COPY instruction from
|
||||
failing if no environment.yml is found.
|
||||
1
garth/.gitattributes
vendored
Normal file
1
garth/.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.ipynb linguist-documentation=true
|
||||
17
garth/.github/dependabot.yml
vendored
Normal file
17
garth/.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
time: "20:00"
|
||||
timezone: "America/Mexico_City"
|
||||
open-pull-requests-limit: 5
|
||||
|
||||
- package-ecosystem: pip
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
time: "20:00"
|
||||
timezone: "America/Mexico_City"
|
||||
open-pull-requests-limit: 5
|
||||
87
garth/.github/workflows/ci.yml
vendored
Normal file
87
garth/.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- "**"
|
||||
pull_request: {}
|
||||
|
||||
env:
|
||||
COLUMNS: 150
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
checks: write
|
||||
statuses: write
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
name: lint ${{ matrix.python-version }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- uses: astral-sh/setup-uv@v6
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
uv pip install --system -e .
|
||||
uv pip install --system --group linting
|
||||
|
||||
- uses: pre-commit/action@v3.0.1
|
||||
with:
|
||||
extra_args: --all-files --verbose
|
||||
env:
|
||||
SKIP: no-commit-to-branch
|
||||
|
||||
test:
|
||||
name: test ${{ matrix.python-version }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu, macos, windows]
|
||||
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
||||
|
||||
env:
|
||||
PYTHON: ${{ matrix.python-version }}
|
||||
OS: ${{ matrix.os }}
|
||||
|
||||
runs-on: ${{ matrix.os }}-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- uses: astral-sh/setup-uv@v6
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
uv pip install --system -e .
|
||||
uv pip install --system --group testing
|
||||
|
||||
- name: test
|
||||
run: make testcov
|
||||
env:
|
||||
CONTEXT: ${{ runner.os }}-py${{ matrix.python-version }}-with-deps
|
||||
|
||||
- name: upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
files: ./coverage/coverage.xml
|
||||
flags: unittests
|
||||
name: codecov-umbrella
|
||||
fail_ci_if_error: true
|
||||
30
garth/.github/workflows/publish.yml
vendored
Normal file
30
garth/.github/workflows/publish.yml
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
name: Publish to PyPI
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: pypi
|
||||
url: https://pypi.org/p/garth
|
||||
permissions:
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.13"
|
||||
|
||||
- uses: astral-sh/setup-uv@v6
|
||||
|
||||
- name: Build package
|
||||
run: |
|
||||
uv build
|
||||
|
||||
- name: Publish to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
53
garth/.gitignore
vendored
Normal file
53
garth/.gitignore
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
# Virtual environments
|
||||
env/
|
||||
env3*/
|
||||
venv/
|
||||
.venv/
|
||||
.envrc
|
||||
.env
|
||||
__pypackages__/
|
||||
|
||||
# IDEs and editors
|
||||
.idea/
|
||||
|
||||
# Package distribution and build files
|
||||
*.egg-info/
|
||||
dist/
|
||||
/build/
|
||||
_build/
|
||||
|
||||
# Python bytecode and cache files
|
||||
*.py[cod]
|
||||
.cache/
|
||||
/.ghtopdep_cache/
|
||||
.hypothesis
|
||||
.mypy_cache/
|
||||
.pytest_cache/
|
||||
/.ruff_cache/
|
||||
|
||||
# Benchmark and test files
|
||||
/benchmarks/*.json
|
||||
/htmlcov/
|
||||
/codecov.sh
|
||||
/coverage.lcov
|
||||
.coverage
|
||||
test.py
|
||||
/coverage/
|
||||
|
||||
# Documentation files
|
||||
/docs/changelog.md
|
||||
/site/
|
||||
/site.zip
|
||||
|
||||
# Other files and folders
|
||||
.python-version
|
||||
.DS_Store
|
||||
.auto-format
|
||||
/sandbox/
|
||||
/worktrees/
|
||||
.pdm-python
|
||||
tmp/
|
||||
.pdm.toml
|
||||
|
||||
# exclude saved oauth tokens
|
||||
oauth*_token.json
|
||||
6
garth/.markdownlint.json
Normal file
6
garth/.markdownlint.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"MD033": {
|
||||
"allowed_elements": ["img", "a", "source", "picture"]
|
||||
},
|
||||
"MD046": false
|
||||
}
|
||||
33
garth/.pre-commit-config.yaml
Normal file
33
garth/.pre-commit-config.yaml
Normal file
@@ -0,0 +1,33 @@
|
||||
exclude: '.*\.ipynb$'
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.6.0
|
||||
hooks:
|
||||
- id: check-yaml
|
||||
args: ['--unsafe']
|
||||
- id: check-toml
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: v2.2.6
|
||||
hooks:
|
||||
- id: codespell
|
||||
additional_dependencies:
|
||||
- tomli
|
||||
exclude: 'cassettes/'
|
||||
|
||||
- repo: https://github.com/DavidAnson/markdownlint-cli2
|
||||
rev: v0.12.1
|
||||
hooks:
|
||||
- id: markdownlint-cli2
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: lint
|
||||
name: lint
|
||||
entry: make lint
|
||||
types: [python]
|
||||
language: system
|
||||
pass_filenames: false
|
||||
21
garth/LICENSE
Normal file
21
garth/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
# MIT License
|
||||
|
||||
Copyright (c) 2023 Matin Tamizi
|
||||
|
||||
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.
|
||||
81
garth/Makefile
Normal file
81
garth/Makefile
Normal file
@@ -0,0 +1,81 @@
|
||||
# Based on Makefile for pydantic (github.com/pydantic/pydantic/blob/main/Makefile)
|
||||
|
||||
.DEFAULT_GOAL := all
|
||||
sources = src tests
|
||||
|
||||
.PHONY: .uv ## Check that uv is installed
|
||||
.uv:
|
||||
@uv --version || echo 'Please install uv: https://docs.astral.sh/uv/getting-started/installation/'
|
||||
|
||||
.PHONY: .pre-commit ## Check that pre-commit is installed
|
||||
.pre-commit:
|
||||
@pre-commit -V || echo 'Please install pre-commit: https://pre-commit.com/'
|
||||
|
||||
.PHONY: install ## Install the package, dependencies, and pre-commit for local development
|
||||
install: .uv .pre-commit
|
||||
uv pip install -e .
|
||||
uv pip install --group dev --group linting --group testing
|
||||
pre-commit install --install-hooks
|
||||
|
||||
.PHONY: sync ## Sync dependencies and lockfiles
|
||||
sync: .uv clean
|
||||
uv pip install -e . --force-reinstall
|
||||
uv sync
|
||||
|
||||
.PHONY: format ## Auto-format python source files
|
||||
format: .uv
|
||||
uv run ruff format $(sources)
|
||||
uv run ruff check --fix $(sources)
|
||||
|
||||
.PHONY: lint ## Lint python source files
|
||||
lint: .uv
|
||||
uv run ruff format --check $(sources)
|
||||
uv run ruff check $(sources)
|
||||
uv run mypy $(sources)
|
||||
|
||||
.PHONY: codespell ## Use Codespell to do spellchecking
|
||||
codespell: .pre-commit
|
||||
pre-commit run codespell --all-files
|
||||
|
||||
.PHONY: test ## Run all tests, skipping the type-checker integration tests
|
||||
test: .uv
|
||||
uv run coverage run -m pytest -v --durations=10
|
||||
|
||||
.PHONY: testcov ## Run tests and generate a coverage report, skipping the type-checker integration tests
|
||||
testcov: test
|
||||
@echo "building coverage html"
|
||||
@uv run coverage html
|
||||
@echo "building coverage xml"
|
||||
@uv run coverage xml -o coverage/coverage.xml
|
||||
|
||||
.PHONY: all ## Run the standard set of checks performed in CI
|
||||
all: lint codespell testcov
|
||||
|
||||
.PHONY: clean ## Clear local caches and build artifacts
|
||||
clean:
|
||||
find . -type d -name __pycache__ -exec rm -r {} +
|
||||
find . -type f -name '*.py[co]' -exec rm -f {} +
|
||||
find . -type f -name '*~' -exec rm -f {} +
|
||||
find . -type f -name '.*~' -exec rm -f {} +
|
||||
rm -rf .cache
|
||||
rm -rf .pytest_cache
|
||||
rm -rf .ruff_cache
|
||||
rm -rf htmlcov
|
||||
rm -rf *.egg-info
|
||||
rm -f .coverage
|
||||
rm -f .coverage.*
|
||||
rm -rf build
|
||||
rm -rf dist
|
||||
rm -rf site
|
||||
rm -rf docs/_build
|
||||
rm -rf docs/.changelog.md docs/.version.md docs/.tmp_schema_mappings.html
|
||||
rm -rf fastapi/test.db
|
||||
rm -rf coverage.xml
|
||||
rm -rf __pypackages__ uv.lock
|
||||
|
||||
.PHONY: help ## Display this message
|
||||
help:
|
||||
@grep -E \
|
||||
'^.PHONY: .*?## .*$$' $(MAKEFILE_LIST) | \
|
||||
sort | \
|
||||
awk 'BEGIN {FS = ".PHONY: |## "}; {printf "\033[36m%-19s\033[0m %s\n", $$2, $$3}'
|
||||
1108
garth/README.md
Normal file
1108
garth/README.md
Normal file
File diff suppressed because it is too large
Load Diff
1084
garth/colabs/chatgpt_analysis_of_stats.ipynb
Normal file
1084
garth/colabs/chatgpt_analysis_of_stats.ipynb
Normal file
File diff suppressed because one or more lines are too long
478
garth/colabs/sleep.ipynb
Normal file
478
garth/colabs/sleep.ipynb
Normal file
File diff suppressed because one or more lines are too long
502
garth/colabs/stress.ipynb
Normal file
502
garth/colabs/stress.ipynb
Normal file
File diff suppressed because one or more lines are too long
89
garth/pyproject.toml
Normal file
89
garth/pyproject.toml
Normal file
@@ -0,0 +1,89 @@
|
||||
[project]
|
||||
name = "garth"
|
||||
dynamic = ["version"]
|
||||
description = "Garmin SSO auth + Connect client"
|
||||
authors = [
|
||||
{name = "Matin Tamizi", email = "mtamizi@duck.com"},
|
||||
]
|
||||
dependencies = [
|
||||
"requests>=2.0.0,<3.0.0",
|
||||
"pydantic>=1.10.12,<3.0.0",
|
||||
"requests-oauthlib>=1.3.1,<3.0.0",
|
||||
]
|
||||
requires-python = ">=3.10"
|
||||
readme = "README.md"
|
||||
license = {text = "MIT"}
|
||||
classifiers = [
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Programming Language :: Python :: 3.13",
|
||||
"Operating System :: MacOS :: MacOS X",
|
||||
"Operating System :: Microsoft :: Windows",
|
||||
"Operating System :: POSIX :: Linux",
|
||||
"Operating System :: OS Independent",
|
||||
]
|
||||
keywords = ["garmin", "garmin api", "garmin connect", "garmin sso"]
|
||||
|
||||
[project.urls]
|
||||
"Homepage" = "https://github.com/matin/garth"
|
||||
"Repository" = "https://github.com/matin/garth"
|
||||
"Issues" = "https://github.com/matin/garth/issues"
|
||||
"Changelog" = "https://github.com/matin/garth/releases"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.version]
|
||||
path = "src/garth/version.py"
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
addopts = "--ignore=__pypackages__ --ignore-glob=*.yaml"
|
||||
|
||||
[tool.mypy]
|
||||
ignore_missing_imports = true
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 79
|
||||
indent-width = 4
|
||||
target-version = "py310"
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = ["E", "F", "I"]
|
||||
ignore = []
|
||||
|
||||
[tool.ruff.format]
|
||||
quote-style = "double"
|
||||
indent-style = "space"
|
||||
skip-magic-trailing-comma = false
|
||||
line-ending = "auto"
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"ipython",
|
||||
"ipdb",
|
||||
"ipykernel",
|
||||
"pandas",
|
||||
"matplotlib",
|
||||
]
|
||||
linting = [
|
||||
"ruff",
|
||||
"mypy",
|
||||
"types-requests",
|
||||
]
|
||||
testing = [
|
||||
"coverage",
|
||||
"pytest",
|
||||
"pytest-vcr",
|
||||
]
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
known-first-party = ["garth"]
|
||||
combine-as-imports = true
|
||||
lines-after-imports = 2
|
||||
|
||||
[project.scripts]
|
||||
garth = "garth.cli:main"
|
||||
59
garth/src/garth/__init__.py
Normal file
59
garth/src/garth/__init__.py
Normal file
@@ -0,0 +1,59 @@
|
||||
from .data import (
|
||||
BodyBatteryData,
|
||||
DailyBodyBatteryStress,
|
||||
HRVData,
|
||||
SleepData,
|
||||
WeightData,
|
||||
)
|
||||
from .http import Client, client
|
||||
from .stats import (
|
||||
DailyHRV,
|
||||
DailyHydration,
|
||||
DailyIntensityMinutes,
|
||||
DailySleep,
|
||||
DailySteps,
|
||||
DailyStress,
|
||||
WeeklyIntensityMinutes,
|
||||
WeeklySteps,
|
||||
WeeklyStress,
|
||||
)
|
||||
from .users import UserProfile, UserSettings
|
||||
from .version import __version__
|
||||
|
||||
|
||||
__all__ = [
|
||||
"BodyBatteryData",
|
||||
"Client",
|
||||
"DailyBodyBatteryStress",
|
||||
"DailyHRV",
|
||||
"DailyHydration",
|
||||
"DailyIntensityMinutes",
|
||||
"DailySleep",
|
||||
"DailySteps",
|
||||
"DailyStress",
|
||||
"HRVData",
|
||||
"SleepData",
|
||||
"WeightData",
|
||||
"UserProfile",
|
||||
"UserSettings",
|
||||
"WeeklyIntensityMinutes",
|
||||
"WeeklySteps",
|
||||
"WeeklyStress",
|
||||
"__version__",
|
||||
"client",
|
||||
"configure",
|
||||
"connectapi",
|
||||
"download",
|
||||
"login",
|
||||
"resume",
|
||||
"save",
|
||||
"upload",
|
||||
]
|
||||
|
||||
configure = client.configure
|
||||
connectapi = client.connectapi
|
||||
download = client.download
|
||||
login = client.login
|
||||
resume = client.load
|
||||
save = client.dump
|
||||
upload = client.upload
|
||||
37
garth/src/garth/auth_tokens.py
Normal file
37
garth/src/garth/auth_tokens.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic.dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class OAuth1Token:
|
||||
oauth_token: str
|
||||
oauth_token_secret: str
|
||||
mfa_token: str | None = None
|
||||
mfa_expiration_timestamp: datetime | None = None
|
||||
domain: str | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class OAuth2Token:
|
||||
scope: str
|
||||
jti: str
|
||||
token_type: str
|
||||
access_token: str
|
||||
refresh_token: str
|
||||
expires_in: int
|
||||
expires_at: int
|
||||
refresh_token_expires_in: int
|
||||
refresh_token_expires_at: int
|
||||
|
||||
@property
|
||||
def expired(self):
|
||||
return self.expires_at < time.time()
|
||||
|
||||
@property
|
||||
def refresh_expired(self):
|
||||
return self.refresh_token_expires_at < time.time()
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.token_type.title()} {self.access_token}"
|
||||
34
garth/src/garth/cli.py
Normal file
34
garth/src/garth/cli.py
Normal file
@@ -0,0 +1,34 @@
|
||||
import argparse
|
||||
import getpass
|
||||
|
||||
import garth
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(prog="garth")
|
||||
parser.add_argument(
|
||||
"--domain",
|
||||
"-d",
|
||||
default="garmin.com",
|
||||
help=(
|
||||
"Domain for Garmin Connect (default: garmin.com). "
|
||||
"Use garmin.cn for China."
|
||||
),
|
||||
)
|
||||
subparsers = parser.add_subparsers(dest="command")
|
||||
subparsers.add_parser(
|
||||
"login", help="Authenticate with Garmin Connect and print token"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
garth.configure(domain=args.domain)
|
||||
|
||||
match args.command:
|
||||
case "login":
|
||||
email = input("Email: ")
|
||||
password = getpass.getpass("Password: ")
|
||||
garth.login(email, password)
|
||||
token = garth.client.dumps()
|
||||
print(token)
|
||||
case _:
|
||||
parser.print_help()
|
||||
21
garth/src/garth/data/__init__.py
Normal file
21
garth/src/garth/data/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
__all__ = [
|
||||
"BodyBatteryData",
|
||||
"BodyBatteryEvent",
|
||||
"BodyBatteryReading",
|
||||
"DailyBodyBatteryStress",
|
||||
"HRVData",
|
||||
"SleepData",
|
||||
"StressReading",
|
||||
"WeightData",
|
||||
]
|
||||
|
||||
from .body_battery import (
|
||||
BodyBatteryData,
|
||||
BodyBatteryEvent,
|
||||
BodyBatteryReading,
|
||||
DailyBodyBatteryStress,
|
||||
StressReading,
|
||||
)
|
||||
from .hrv import HRVData
|
||||
from .sleep import SleepData
|
||||
from .weight import WeightData
|
||||
47
garth/src/garth/data/_base.py
Normal file
47
garth/src/garth/data/_base.py
Normal file
@@ -0,0 +1,47 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from datetime import date
|
||||
from itertools import chain
|
||||
|
||||
from typing_extensions import Self
|
||||
|
||||
from .. import http
|
||||
from ..utils import date_range, format_end_date
|
||||
|
||||
|
||||
MAX_WORKERS = 10
|
||||
|
||||
|
||||
class Data(ABC):
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def get(
|
||||
cls, day: date | str, *, client: http.Client | None = None
|
||||
) -> Self | list[Self] | None: ...
|
||||
|
||||
@classmethod
|
||||
def list(
|
||||
cls,
|
||||
end: date | str | None = None,
|
||||
days: int = 1,
|
||||
*,
|
||||
client: http.Client | None = None,
|
||||
max_workers: int = MAX_WORKERS,
|
||||
) -> list[Self]:
|
||||
client = client or http.client
|
||||
end = format_end_date(end)
|
||||
|
||||
def fetch_date(date_):
|
||||
if day := cls.get(date_, client=client):
|
||||
return day
|
||||
|
||||
dates = date_range(end, days)
|
||||
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
||||
data = list(executor.map(fetch_date, dates))
|
||||
data = [day for day in data if day is not None]
|
||||
|
||||
return list(
|
||||
chain.from_iterable(
|
||||
day if isinstance(day, list) else [day] for day in data
|
||||
)
|
||||
)
|
||||
11
garth/src/garth/data/body_battery/__init__.py
Normal file
11
garth/src/garth/data/body_battery/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
||||
__all__ = [
|
||||
"BodyBatteryData",
|
||||
"BodyBatteryEvent",
|
||||
"BodyBatteryReading",
|
||||
"DailyBodyBatteryStress",
|
||||
"StressReading",
|
||||
]
|
||||
|
||||
from .daily_stress import DailyBodyBatteryStress
|
||||
from .events import BodyBatteryData, BodyBatteryEvent
|
||||
from .readings import BodyBatteryReading, StressReading
|
||||
90
garth/src/garth/data/body_battery/daily_stress.py
Normal file
90
garth/src/garth/data/body_battery/daily_stress.py
Normal file
@@ -0,0 +1,90 @@
|
||||
from datetime import date, datetime
|
||||
from functools import cached_property
|
||||
from typing import Any
|
||||
|
||||
from pydantic.dataclasses import dataclass
|
||||
from typing_extensions import Self
|
||||
|
||||
from ... import http
|
||||
from ...utils import camel_to_snake_dict, format_end_date
|
||||
from .._base import Data
|
||||
from .readings import (
|
||||
BodyBatteryReading,
|
||||
StressReading,
|
||||
parse_body_battery_readings,
|
||||
parse_stress_readings,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class DailyBodyBatteryStress(Data):
|
||||
"""Complete daily Body Battery and stress data."""
|
||||
|
||||
user_profile_pk: int
|
||||
calendar_date: date
|
||||
start_timestamp_gmt: datetime
|
||||
end_timestamp_gmt: datetime
|
||||
start_timestamp_local: datetime
|
||||
end_timestamp_local: datetime
|
||||
max_stress_level: int
|
||||
avg_stress_level: int
|
||||
stress_chart_value_offset: int
|
||||
stress_chart_y_axis_origin: int
|
||||
stress_values_array: list[list[int]]
|
||||
body_battery_values_array: list[list[Any]]
|
||||
|
||||
@cached_property
|
||||
def body_battery_readings(self) -> list[BodyBatteryReading]:
|
||||
"""Convert body battery values array to structured readings."""
|
||||
return parse_body_battery_readings(self.body_battery_values_array)
|
||||
|
||||
@property
|
||||
def stress_readings(self) -> list[StressReading]:
|
||||
"""Convert stress values array to structured readings."""
|
||||
return parse_stress_readings(self.stress_values_array)
|
||||
|
||||
@property
|
||||
def current_body_battery(self) -> int | None:
|
||||
"""Get the latest Body Battery level."""
|
||||
readings = self.body_battery_readings
|
||||
return readings[-1].level if readings else None
|
||||
|
||||
@property
|
||||
def max_body_battery(self) -> int | None:
|
||||
"""Get the maximum Body Battery level for the day."""
|
||||
readings = self.body_battery_readings
|
||||
return max(reading.level for reading in readings) if readings else None
|
||||
|
||||
@property
|
||||
def min_body_battery(self) -> int | None:
|
||||
"""Get the minimum Body Battery level for the day."""
|
||||
readings = self.body_battery_readings
|
||||
return min(reading.level for reading in readings) if readings else None
|
||||
|
||||
@property
|
||||
def body_battery_change(self) -> int | None:
|
||||
"""Calculate the Body Battery change for the day."""
|
||||
readings = self.body_battery_readings
|
||||
if not readings or len(readings) < 2:
|
||||
return None
|
||||
return readings[-1].level - readings[0].level
|
||||
|
||||
@classmethod
|
||||
def get(
|
||||
cls,
|
||||
day: date | str | None = None,
|
||||
*,
|
||||
client: http.Client | None = None,
|
||||
) -> Self | None:
|
||||
"""Get complete Body Battery and stress data for a specific date."""
|
||||
client = client or http.client
|
||||
date_str = format_end_date(day)
|
||||
|
||||
path = f"/wellness-service/wellness/dailyStress/{date_str}"
|
||||
response = client.connectapi(path)
|
||||
|
||||
if not isinstance(response, dict):
|
||||
return None
|
||||
|
||||
snake_response = camel_to_snake_dict(response)
|
||||
return cls(**snake_response)
|
||||
227
garth/src/garth/data/body_battery/events.py
Normal file
227
garth/src/garth/data/body_battery/events.py
Normal file
@@ -0,0 +1,227 @@
|
||||
import logging
|
||||
from datetime import date, datetime
|
||||
from typing import Any
|
||||
|
||||
from pydantic.dataclasses import dataclass
|
||||
from typing_extensions import Self
|
||||
|
||||
from ... import http
|
||||
from ...utils import format_end_date
|
||||
from .._base import Data
|
||||
from .readings import BodyBatteryReading, parse_body_battery_readings
|
||||
|
||||
|
||||
MAX_WORKERS = 10
|
||||
|
||||
|
||||
@dataclass
|
||||
class BodyBatteryEvent:
|
||||
"""Body Battery event data."""
|
||||
|
||||
event_type: str
|
||||
event_start_time_gmt: datetime
|
||||
timezone_offset: int
|
||||
duration_in_milliseconds: int
|
||||
body_battery_impact: int
|
||||
feedback_type: str
|
||||
short_feedback: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class BodyBatteryData(Data):
|
||||
"""Legacy Body Battery events data (sleep events only)."""
|
||||
|
||||
event: BodyBatteryEvent | None = None
|
||||
activity_name: str | None = None
|
||||
activity_type: str | None = None
|
||||
activity_id: str | None = None
|
||||
average_stress: float | None = None
|
||||
stress_values_array: list[list[int]] | None = None
|
||||
body_battery_values_array: list[list[Any]] | None = None
|
||||
|
||||
@property
|
||||
def body_battery_readings(self) -> list[BodyBatteryReading]:
|
||||
"""Convert body battery values array to structured readings."""
|
||||
return parse_body_battery_readings(self.body_battery_values_array)
|
||||
|
||||
@property
|
||||
def current_level(self) -> int | None:
|
||||
"""Get the latest Body Battery level."""
|
||||
readings = self.body_battery_readings
|
||||
return readings[-1].level if readings else None
|
||||
|
||||
@property
|
||||
def max_level(self) -> int | None:
|
||||
"""Get the maximum Body Battery level for the day."""
|
||||
readings = self.body_battery_readings
|
||||
return max(reading.level for reading in readings) if readings else None
|
||||
|
||||
@property
|
||||
def min_level(self) -> int | None:
|
||||
"""Get the minimum Body Battery level for the day."""
|
||||
readings = self.body_battery_readings
|
||||
return min(reading.level for reading in readings) if readings else None
|
||||
|
||||
@classmethod
|
||||
def get(
|
||||
cls,
|
||||
date_str: str | date | None = None,
|
||||
*,
|
||||
client: http.Client | None = None,
|
||||
) -> list[Self]:
|
||||
"""Get Body Battery events for a specific date."""
|
||||
client = client or http.client
|
||||
date_str = format_end_date(date_str)
|
||||
|
||||
path = f"/wellness-service/wellness/bodyBattery/events/{date_str}"
|
||||
try:
|
||||
response = client.connectapi(path)
|
||||
except Exception as e:
|
||||
logging.warning(f"Failed to fetch Body Battery events: {e}")
|
||||
return []
|
||||
|
||||
if not isinstance(response, list):
|
||||
return []
|
||||
|
||||
events = []
|
||||
for item in response:
|
||||
try:
|
||||
# Parse event data with validation
|
||||
event_data = item.get("event")
|
||||
|
||||
# Validate event_data exists before accessing properties
|
||||
if event_data is None:
|
||||
logging.warning(f"Missing event data in item: {item}")
|
||||
event = None
|
||||
else:
|
||||
# Validate and parse datetime with explicit error handling
|
||||
event_start_time_str = event_data.get("eventStartTimeGmt")
|
||||
if not event_start_time_str:
|
||||
logging.error(
|
||||
f"Missing eventStartTimeGmt in event data: "
|
||||
f"{event_data}"
|
||||
)
|
||||
raise ValueError(
|
||||
"eventStartTimeGmt is required but missing"
|
||||
)
|
||||
|
||||
try:
|
||||
event_start_time_gmt = datetime.fromisoformat(
|
||||
event_start_time_str.replace("Z", "+00:00")
|
||||
)
|
||||
except (ValueError, AttributeError) as e:
|
||||
logging.error(
|
||||
f"Invalid datetime format "
|
||||
f"'{event_start_time_str}': {e}"
|
||||
)
|
||||
raise ValueError(
|
||||
f"Invalid eventStartTimeGmt format: "
|
||||
f"{event_start_time_str}"
|
||||
) from e
|
||||
|
||||
# Validate numeric fields
|
||||
timezone_offset = event_data.get("timezoneOffset", 0)
|
||||
if not isinstance(timezone_offset, (int, float)):
|
||||
logging.warning(
|
||||
f"Invalid timezone_offset type: "
|
||||
f"{type(timezone_offset)}, using 0"
|
||||
)
|
||||
timezone_offset = 0
|
||||
|
||||
duration_ms = event_data.get("durationInMilliseconds", 0)
|
||||
if not isinstance(duration_ms, (int, float)):
|
||||
logging.warning(
|
||||
f"Invalid durationInMilliseconds type: "
|
||||
f"{type(duration_ms)}, using 0"
|
||||
)
|
||||
duration_ms = 0
|
||||
|
||||
battery_impact = event_data.get("bodyBatteryImpact", 0)
|
||||
if not isinstance(battery_impact, (int, float)):
|
||||
logging.warning(
|
||||
f"Invalid bodyBatteryImpact type: "
|
||||
f"{type(battery_impact)}, using 0"
|
||||
)
|
||||
battery_impact = 0
|
||||
|
||||
event = BodyBatteryEvent(
|
||||
event_type=event_data.get("eventType", ""),
|
||||
event_start_time_gmt=event_start_time_gmt,
|
||||
timezone_offset=int(timezone_offset),
|
||||
duration_in_milliseconds=int(duration_ms),
|
||||
body_battery_impact=int(battery_impact),
|
||||
feedback_type=event_data.get("feedbackType", ""),
|
||||
short_feedback=event_data.get("shortFeedback", ""),
|
||||
)
|
||||
|
||||
# Validate data arrays
|
||||
stress_values = item.get("stressValuesArray")
|
||||
if stress_values is not None and not isinstance(
|
||||
stress_values, list
|
||||
):
|
||||
logging.warning(
|
||||
f"Invalid stressValuesArray type: "
|
||||
f"{type(stress_values)}, using None"
|
||||
)
|
||||
stress_values = None
|
||||
|
||||
battery_values = item.get("bodyBatteryValuesArray")
|
||||
if battery_values is not None and not isinstance(
|
||||
battery_values, list
|
||||
):
|
||||
logging.warning(
|
||||
f"Invalid bodyBatteryValuesArray type: "
|
||||
f"{type(battery_values)}, using None"
|
||||
)
|
||||
battery_values = None
|
||||
|
||||
# Validate average_stress
|
||||
avg_stress = item.get("averageStress")
|
||||
if avg_stress is not None and not isinstance(
|
||||
avg_stress, (int, float)
|
||||
):
|
||||
logging.warning(
|
||||
f"Invalid averageStress type: "
|
||||
f"{type(avg_stress)}, using None"
|
||||
)
|
||||
avg_stress = None
|
||||
|
||||
events.append(
|
||||
cls(
|
||||
event=event,
|
||||
activity_name=item.get("activityName"),
|
||||
activity_type=item.get("activityType"),
|
||||
activity_id=item.get("activityId"),
|
||||
average_stress=avg_stress,
|
||||
stress_values_array=stress_values,
|
||||
body_battery_values_array=battery_values,
|
||||
)
|
||||
)
|
||||
|
||||
except ValueError as e:
|
||||
# Re-raise validation errors with context
|
||||
logging.error(
|
||||
f"Data validation error for Body Battery event item "
|
||||
f"{item}: {e}"
|
||||
)
|
||||
continue
|
||||
except Exception as e:
|
||||
# Log unexpected errors with full context
|
||||
logging.error(
|
||||
f"Unexpected error parsing Body Battery event item "
|
||||
f"{item}: {e}",
|
||||
exc_info=True,
|
||||
)
|
||||
continue
|
||||
|
||||
# Log summary of data quality issues
|
||||
total_items = len(response)
|
||||
parsed_events = len(events)
|
||||
if parsed_events < total_items:
|
||||
skipped = total_items - parsed_events
|
||||
logging.info(
|
||||
f"Body Battery events parsing: {parsed_events}/{total_items} "
|
||||
f"successful, {skipped} skipped due to data issues"
|
||||
)
|
||||
|
||||
return events
|
||||
56
garth/src/garth/data/body_battery/readings.py
Normal file
56
garth/src/garth/data/body_battery/readings.py
Normal file
@@ -0,0 +1,56 @@
|
||||
from typing import Any
|
||||
|
||||
from pydantic.dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class BodyBatteryReading:
|
||||
"""Individual Body Battery reading."""
|
||||
|
||||
timestamp: int
|
||||
status: str
|
||||
level: int
|
||||
version: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class StressReading:
|
||||
"""Individual stress reading."""
|
||||
|
||||
timestamp: int
|
||||
stress_level: int
|
||||
|
||||
|
||||
def parse_body_battery_readings(
|
||||
body_battery_values_array: list[list[Any]] | None,
|
||||
) -> list[BodyBatteryReading]:
|
||||
"""Convert body battery values array to structured readings."""
|
||||
readings = []
|
||||
for values in body_battery_values_array or []:
|
||||
# Each reading requires 4 values: timestamp, status, level, version
|
||||
if len(values) >= 4:
|
||||
readings.append(
|
||||
BodyBatteryReading(
|
||||
timestamp=values[0],
|
||||
status=values[1],
|
||||
level=values[2],
|
||||
version=values[3],
|
||||
)
|
||||
)
|
||||
# Sort readings by timestamp to ensure chronological order
|
||||
return sorted(readings, key=lambda reading: reading.timestamp)
|
||||
|
||||
|
||||
def parse_stress_readings(
|
||||
stress_values_array: list[list[int]] | None,
|
||||
) -> list[StressReading]:
|
||||
"""Convert stress values array to structured readings."""
|
||||
readings = []
|
||||
for values in stress_values_array or []:
|
||||
# Each reading requires 2 values: timestamp, stress_level
|
||||
if len(values) >= 2:
|
||||
readings.append(
|
||||
StressReading(timestamp=values[0], stress_level=values[1])
|
||||
)
|
||||
# Sort readings by timestamp to ensure chronological order
|
||||
return sorted(readings, key=lambda reading: reading.timestamp)
|
||||
68
garth/src/garth/data/hrv.py
Normal file
68
garth/src/garth/data/hrv.py
Normal file
@@ -0,0 +1,68 @@
|
||||
from datetime import date, datetime
|
||||
|
||||
from pydantic.dataclasses import dataclass
|
||||
from typing_extensions import Self
|
||||
|
||||
from .. import http
|
||||
from ..utils import camel_to_snake_dict
|
||||
from ._base import Data
|
||||
|
||||
|
||||
@dataclass
|
||||
class Baseline:
|
||||
low_upper: int
|
||||
balanced_low: int
|
||||
balanced_upper: int
|
||||
marker_value: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class HRVSummary:
|
||||
calendar_date: date
|
||||
weekly_avg: int
|
||||
last_night_avg: int | None
|
||||
last_night_5_min_high: int
|
||||
baseline: Baseline
|
||||
status: str
|
||||
feedback_phrase: str
|
||||
create_time_stamp: datetime
|
||||
|
||||
|
||||
@dataclass
|
||||
class HRVReading:
|
||||
hrv_value: int
|
||||
reading_time_gmt: datetime
|
||||
reading_time_local: datetime
|
||||
|
||||
|
||||
@dataclass
|
||||
class HRVData(Data):
|
||||
user_profile_pk: int
|
||||
hrv_summary: HRVSummary
|
||||
hrv_readings: list[HRVReading]
|
||||
start_timestamp_gmt: datetime
|
||||
end_timestamp_gmt: datetime
|
||||
start_timestamp_local: datetime
|
||||
end_timestamp_local: datetime
|
||||
sleep_start_timestamp_gmt: datetime
|
||||
sleep_end_timestamp_gmt: datetime
|
||||
sleep_start_timestamp_local: datetime
|
||||
sleep_end_timestamp_local: datetime
|
||||
|
||||
@classmethod
|
||||
def get(
|
||||
cls, day: date | str, *, client: http.Client | None = None
|
||||
) -> Self | None:
|
||||
client = client or http.client
|
||||
path = f"/hrv-service/hrv/{day}"
|
||||
hrv_data = client.connectapi(path)
|
||||
if not hrv_data:
|
||||
return None
|
||||
hrv_data = camel_to_snake_dict(hrv_data)
|
||||
assert isinstance(hrv_data, dict)
|
||||
return cls(**hrv_data)
|
||||
|
||||
@classmethod
|
||||
def list(cls, *args, **kwargs) -> list[Self]:
|
||||
data = super().list(*args, **kwargs)
|
||||
return sorted(data, key=lambda d: d.hrv_summary.calendar_date)
|
||||
123
garth/src/garth/data/sleep.py
Normal file
123
garth/src/garth/data/sleep.py
Normal file
@@ -0,0 +1,123 @@
|
||||
from datetime import date, datetime
|
||||
from typing import Optional, Union
|
||||
|
||||
from pydantic.dataclasses import dataclass
|
||||
from typing_extensions import Self
|
||||
|
||||
from .. import http
|
||||
from ..utils import camel_to_snake_dict, get_localized_datetime
|
||||
from ._base import Data
|
||||
|
||||
|
||||
@dataclass
|
||||
class Score:
|
||||
qualifier_key: str
|
||||
optimal_start: Optional[float] = None
|
||||
optimal_end: Optional[float] = None
|
||||
value: Optional[int] = None
|
||||
ideal_start_in_seconds: Optional[float] = None
|
||||
ideal_end_in_seconds: Optional[float] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class SleepScores:
|
||||
total_duration: Score
|
||||
stress: Score
|
||||
awake_count: Score
|
||||
overall: Score
|
||||
rem_percentage: Score
|
||||
restlessness: Score
|
||||
light_percentage: Score
|
||||
deep_percentage: Score
|
||||
|
||||
|
||||
@dataclass
|
||||
class DailySleepDTO:
|
||||
id: int
|
||||
user_profile_pk: int
|
||||
calendar_date: date
|
||||
sleep_time_seconds: int
|
||||
nap_time_seconds: int
|
||||
sleep_window_confirmed: bool
|
||||
sleep_window_confirmation_type: str
|
||||
sleep_start_timestamp_gmt: int
|
||||
sleep_end_timestamp_gmt: int
|
||||
sleep_start_timestamp_local: int
|
||||
sleep_end_timestamp_local: int
|
||||
device_rem_capable: bool
|
||||
retro: bool
|
||||
unmeasurable_sleep_seconds: Optional[int] = None
|
||||
deep_sleep_seconds: Optional[int] = None
|
||||
light_sleep_seconds: Optional[int] = None
|
||||
rem_sleep_seconds: Optional[int] = None
|
||||
awake_sleep_seconds: Optional[int] = None
|
||||
sleep_from_device: Optional[bool] = None
|
||||
sleep_version: Optional[int] = None
|
||||
awake_count: Optional[int] = None
|
||||
sleep_scores: Optional[SleepScores] = None
|
||||
auto_sleep_start_timestamp_gmt: Optional[int] = None
|
||||
auto_sleep_end_timestamp_gmt: Optional[int] = None
|
||||
sleep_quality_type_pk: Optional[int] = None
|
||||
sleep_result_type_pk: Optional[int] = None
|
||||
average_sp_o2_value: Optional[float] = None
|
||||
lowest_sp_o2_value: Optional[int] = None
|
||||
highest_sp_o2_value: Optional[int] = None
|
||||
average_sp_o2_hr_sleep: Optional[float] = None
|
||||
average_respiration_value: Optional[float] = None
|
||||
lowest_respiration_value: Optional[float] = None
|
||||
highest_respiration_value: Optional[float] = None
|
||||
avg_sleep_stress: Optional[float] = None
|
||||
age_group: Optional[str] = None
|
||||
sleep_score_feedback: Optional[str] = None
|
||||
sleep_score_insight: Optional[str] = None
|
||||
|
||||
@property
|
||||
def sleep_start(self) -> datetime:
|
||||
return get_localized_datetime(
|
||||
self.sleep_start_timestamp_gmt, self.sleep_start_timestamp_local
|
||||
)
|
||||
|
||||
@property
|
||||
def sleep_end(self) -> datetime:
|
||||
return get_localized_datetime(
|
||||
self.sleep_end_timestamp_gmt, self.sleep_end_timestamp_local
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class SleepMovement:
|
||||
start_gmt: datetime
|
||||
end_gmt: datetime
|
||||
activity_level: float
|
||||
|
||||
|
||||
@dataclass
|
||||
class SleepData(Data):
|
||||
daily_sleep_dto: DailySleepDTO
|
||||
sleep_movement: Optional[list[SleepMovement]] = None
|
||||
|
||||
@classmethod
|
||||
def get(
|
||||
cls,
|
||||
day: Union[date, str],
|
||||
*,
|
||||
buffer_minutes: int = 60,
|
||||
client: Optional[http.Client] = None,
|
||||
) -> Optional[Self]:
|
||||
client = client or http.client
|
||||
path = (
|
||||
f"/wellness-service/wellness/dailySleepData/{client.username}?"
|
||||
f"nonSleepBufferMinutes={buffer_minutes}&date={day}"
|
||||
)
|
||||
sleep_data = client.connectapi(path)
|
||||
assert sleep_data
|
||||
sleep_data = camel_to_snake_dict(sleep_data)
|
||||
assert isinstance(sleep_data, dict)
|
||||
return (
|
||||
cls(**sleep_data) if sleep_data["daily_sleep_dto"]["id"] else None
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def list(cls, *args, **kwargs) -> list[Self]:
|
||||
data = super().list(*args, **kwargs)
|
||||
return sorted(data, key=lambda x: x.daily_sleep_dto.calendar_date)
|
||||
81
garth/src/garth/data/weight.py
Normal file
81
garth/src/garth/data/weight.py
Normal file
@@ -0,0 +1,81 @@
|
||||
from datetime import date, datetime, timedelta
|
||||
from itertools import chain
|
||||
|
||||
from pydantic import Field, ValidationInfo, field_validator
|
||||
from pydantic.dataclasses import dataclass
|
||||
from typing_extensions import Self
|
||||
|
||||
from .. import http
|
||||
from ..utils import (
|
||||
camel_to_snake_dict,
|
||||
format_end_date,
|
||||
get_localized_datetime,
|
||||
)
|
||||
from ._base import MAX_WORKERS, Data
|
||||
|
||||
|
||||
@dataclass
|
||||
class WeightData(Data):
|
||||
sample_pk: int
|
||||
calendar_date: date
|
||||
weight: int
|
||||
source_type: str
|
||||
weight_delta: float
|
||||
timestamp_gmt: int
|
||||
datetime_utc: datetime = Field(..., alias="timestamp_gmt")
|
||||
datetime_local: datetime = Field(..., alias="date")
|
||||
bmi: float | None = None
|
||||
body_fat: float | None = None
|
||||
body_water: float | None = None
|
||||
bone_mass: int | None = None
|
||||
muscle_mass: int | None = None
|
||||
physique_rating: float | None = None
|
||||
visceral_fat: float | None = None
|
||||
metabolic_age: int | None = None
|
||||
|
||||
@field_validator("datetime_local", mode="before")
|
||||
@classmethod
|
||||
def to_localized_datetime(cls, v: int, info: ValidationInfo) -> datetime:
|
||||
return get_localized_datetime(info.data["timestamp_gmt"], v)
|
||||
|
||||
@classmethod
|
||||
def get(
|
||||
cls, day: date | str, *, client: http.Client | None = None
|
||||
) -> Self | None:
|
||||
client = client or http.client
|
||||
path = f"/weight-service/weight/dayview/{day}"
|
||||
data = client.connectapi(path)
|
||||
day_weight_list = data["dateWeightList"] if data else []
|
||||
|
||||
if not day_weight_list:
|
||||
return None
|
||||
|
||||
# Get first (most recent) weight entry for the day
|
||||
weight_data = camel_to_snake_dict(day_weight_list[0])
|
||||
return cls(**weight_data)
|
||||
|
||||
@classmethod
|
||||
def list(
|
||||
cls,
|
||||
end: date | str | None = None,
|
||||
days: int = 1,
|
||||
*,
|
||||
client: http.Client | None = None,
|
||||
max_workers: int = MAX_WORKERS,
|
||||
) -> list[Self]:
|
||||
client = client or http.client
|
||||
end = format_end_date(end)
|
||||
start = end - timedelta(days=days - 1)
|
||||
|
||||
data = client.connectapi(
|
||||
f"/weight-service/weight/range/{start}/{end}?includeAll=true"
|
||||
)
|
||||
weight_summaries = data["dailyWeightSummaries"] if data else []
|
||||
weight_metrics = chain.from_iterable(
|
||||
summary["allWeightMetrics"] for summary in weight_summaries
|
||||
)
|
||||
weight_data_list = (
|
||||
cls(**camel_to_snake_dict(weight_data))
|
||||
for weight_data in weight_metrics
|
||||
)
|
||||
return sorted(weight_data_list, key=lambda d: d.datetime_utc)
|
||||
18
garth/src/garth/exc.py
Normal file
18
garth/src/garth/exc.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from requests import HTTPError
|
||||
|
||||
|
||||
@dataclass
|
||||
class GarthException(Exception):
|
||||
"""Base exception for all garth exceptions."""
|
||||
|
||||
msg: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class GarthHTTPError(GarthException):
|
||||
error: HTTPError
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"{self.msg}: {self.error}"
|
||||
247
garth/src/garth/http.py
Normal file
247
garth/src/garth/http.py
Normal file
@@ -0,0 +1,247 @@
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
from typing import IO, Any, Dict, Literal, Tuple
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from requests import HTTPError, Response, Session
|
||||
from requests.adapters import HTTPAdapter, Retry
|
||||
|
||||
from . import sso
|
||||
from .auth_tokens import OAuth1Token, OAuth2Token
|
||||
from .exc import GarthHTTPError
|
||||
from .utils import asdict
|
||||
|
||||
|
||||
USER_AGENT = {"User-Agent": "GCM-iOS-5.7.2.1"}
|
||||
|
||||
|
||||
class Client:
|
||||
sess: Session
|
||||
last_resp: Response
|
||||
domain: str = "garmin.com"
|
||||
oauth1_token: OAuth1Token | Literal["needs_mfa"] | None = None
|
||||
oauth2_token: OAuth2Token | dict[str, Any] | None = None
|
||||
timeout: int = 10
|
||||
retries: int = 3
|
||||
status_forcelist: Tuple[int, ...] = (408, 429, 500, 502, 503, 504)
|
||||
backoff_factor: float = 0.5
|
||||
pool_connections: int = 10
|
||||
pool_maxsize: int = 10
|
||||
_user_profile: Dict[str, Any] | None = None
|
||||
|
||||
def __init__(self, session: Session | None = None, **kwargs):
|
||||
self.sess = session if session else Session()
|
||||
self.sess.headers.update(USER_AGENT)
|
||||
self.configure(
|
||||
timeout=self.timeout,
|
||||
retries=self.retries,
|
||||
status_forcelist=self.status_forcelist,
|
||||
backoff_factor=self.backoff_factor,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
def configure(
|
||||
self,
|
||||
/,
|
||||
oauth1_token: OAuth1Token | None = None,
|
||||
oauth2_token: OAuth2Token | None = None,
|
||||
domain: str | None = None,
|
||||
proxies: Dict[str, str] | None = None,
|
||||
ssl_verify: bool | None = None,
|
||||
timeout: int | None = None,
|
||||
retries: int | None = None,
|
||||
status_forcelist: Tuple[int, ...] | None = None,
|
||||
backoff_factor: float | None = None,
|
||||
pool_connections: int | None = None,
|
||||
pool_maxsize: int | None = None,
|
||||
):
|
||||
if oauth1_token is not None:
|
||||
self.oauth1_token = oauth1_token
|
||||
if oauth2_token is not None:
|
||||
self.oauth2_token = oauth2_token
|
||||
if domain:
|
||||
self.domain = domain
|
||||
if proxies is not None:
|
||||
self.sess.proxies.update(proxies)
|
||||
if ssl_verify is not None:
|
||||
self.sess.verify = ssl_verify
|
||||
if timeout is not None:
|
||||
self.timeout = timeout
|
||||
if retries is not None:
|
||||
self.retries = retries
|
||||
if status_forcelist is not None:
|
||||
self.status_forcelist = status_forcelist
|
||||
if backoff_factor is not None:
|
||||
self.backoff_factor = backoff_factor
|
||||
if pool_connections is not None:
|
||||
self.pool_connections = pool_connections
|
||||
if pool_maxsize is not None:
|
||||
self.pool_maxsize = pool_maxsize
|
||||
|
||||
retry = Retry(
|
||||
total=self.retries,
|
||||
status_forcelist=self.status_forcelist,
|
||||
backoff_factor=self.backoff_factor,
|
||||
)
|
||||
adapter = HTTPAdapter(
|
||||
max_retries=retry,
|
||||
pool_connections=self.pool_connections,
|
||||
pool_maxsize=self.pool_maxsize,
|
||||
)
|
||||
self.sess.mount("https://", adapter)
|
||||
|
||||
@property
|
||||
def user_profile(self):
|
||||
if not self._user_profile:
|
||||
self._user_profile = self.connectapi(
|
||||
"/userprofile-service/socialProfile"
|
||||
)
|
||||
assert isinstance(self._user_profile, dict), (
|
||||
"No profile from connectapi"
|
||||
)
|
||||
return self._user_profile
|
||||
|
||||
@property
|
||||
def profile(self):
|
||||
return self.user_profile
|
||||
|
||||
@property
|
||||
def username(self):
|
||||
return self.user_profile["userName"]
|
||||
|
||||
def request(
|
||||
self,
|
||||
method: str,
|
||||
subdomain: str,
|
||||
path: str,
|
||||
/,
|
||||
api: bool = False,
|
||||
referrer: str | bool = False,
|
||||
headers: dict = {},
|
||||
**kwargs,
|
||||
) -> Response:
|
||||
url = f"https://{subdomain}.{self.domain}"
|
||||
url = urljoin(url, path)
|
||||
if referrer is True and self.last_resp:
|
||||
headers["referer"] = self.last_resp.url
|
||||
if api:
|
||||
assert self.oauth1_token, (
|
||||
"OAuth1 token is required for API requests"
|
||||
)
|
||||
if (
|
||||
not isinstance(self.oauth2_token, OAuth2Token)
|
||||
or self.oauth2_token.expired
|
||||
):
|
||||
self.refresh_oauth2()
|
||||
headers["Authorization"] = str(self.oauth2_token)
|
||||
self.last_resp = self.sess.request(
|
||||
method,
|
||||
url,
|
||||
headers=headers,
|
||||
timeout=self.timeout,
|
||||
**kwargs,
|
||||
)
|
||||
try:
|
||||
self.last_resp.raise_for_status()
|
||||
except HTTPError as e:
|
||||
raise GarthHTTPError(
|
||||
msg="Error in request",
|
||||
error=e,
|
||||
)
|
||||
return self.last_resp
|
||||
|
||||
def get(self, *args, **kwargs) -> Response:
|
||||
return self.request("GET", *args, **kwargs)
|
||||
|
||||
def post(self, *args, **kwargs) -> Response:
|
||||
return self.request("POST", *args, **kwargs)
|
||||
|
||||
def delete(self, *args, **kwargs) -> Response:
|
||||
return self.request("DELETE", *args, **kwargs)
|
||||
|
||||
def put(self, *args, **kwargs) -> Response:
|
||||
return self.request("PUT", *args, **kwargs)
|
||||
|
||||
def login(self, *args, **kwargs):
|
||||
self.oauth1_token, self.oauth2_token = sso.login(
|
||||
*args, **kwargs, client=self
|
||||
)
|
||||
return self.oauth1_token, self.oauth2_token
|
||||
|
||||
def resume_login(self, *args, **kwargs):
|
||||
self.oauth1_token, self.oauth2_token = sso.resume_login(
|
||||
*args, **kwargs
|
||||
)
|
||||
return self.oauth1_token, self.oauth2_token
|
||||
|
||||
def refresh_oauth2(self):
|
||||
assert self.oauth1_token and isinstance(
|
||||
self.oauth1_token, OAuth1Token
|
||||
), "OAuth1 token is required for OAuth2 refresh"
|
||||
# There is a way to perform a refresh of an OAuth2 token, but it
|
||||
# appears even Garmin uses this approach when the OAuth2 is expired
|
||||
self.oauth2_token = sso.exchange(self.oauth1_token, self)
|
||||
|
||||
def connectapi(
|
||||
self, path: str, method="GET", **kwargs
|
||||
) -> Dict[str, Any] | None:
|
||||
resp = self.request(method, "connectapi", path, api=True, **kwargs)
|
||||
if resp.status_code == 204:
|
||||
return None
|
||||
return resp.json()
|
||||
|
||||
def download(self, path: str, **kwargs) -> bytes:
|
||||
resp = self.get("connectapi", path, api=True, **kwargs)
|
||||
return resp.content
|
||||
|
||||
def upload(
|
||||
self, fp: IO[bytes], /, path: str = "/upload-service/upload"
|
||||
) -> Dict[str, Any]:
|
||||
fname = os.path.basename(fp.name)
|
||||
files = {"file": (fname, fp)}
|
||||
result = self.connectapi(
|
||||
path,
|
||||
method="POST",
|
||||
files=files,
|
||||
)
|
||||
assert result is not None, "No result from upload"
|
||||
return result
|
||||
|
||||
def dump(self, dir_path: str):
|
||||
dir_path = os.path.expanduser(dir_path)
|
||||
os.makedirs(dir_path, exist_ok=True)
|
||||
with open(os.path.join(dir_path, "oauth1_token.json"), "w") as f:
|
||||
if self.oauth1_token:
|
||||
json.dump(asdict(self.oauth1_token), f, indent=4)
|
||||
with open(os.path.join(dir_path, "oauth2_token.json"), "w") as f:
|
||||
if self.oauth2_token:
|
||||
json.dump(asdict(self.oauth2_token), f, indent=4)
|
||||
|
||||
def dumps(self) -> str:
|
||||
r = []
|
||||
r.append(asdict(self.oauth1_token))
|
||||
r.append(asdict(self.oauth2_token))
|
||||
s = json.dumps(r)
|
||||
return base64.b64encode(s.encode()).decode()
|
||||
|
||||
def load(self, dir_path: str):
|
||||
dir_path = os.path.expanduser(dir_path)
|
||||
with open(os.path.join(dir_path, "oauth1_token.json")) as f:
|
||||
oauth1 = OAuth1Token(**json.load(f))
|
||||
with open(os.path.join(dir_path, "oauth2_token.json")) as f:
|
||||
oauth2 = OAuth2Token(**json.load(f))
|
||||
self.configure(
|
||||
oauth1_token=oauth1, oauth2_token=oauth2, domain=oauth1.domain
|
||||
)
|
||||
|
||||
def loads(self, s: str):
|
||||
oauth1, oauth2 = json.loads(base64.b64decode(s))
|
||||
self.configure(
|
||||
oauth1_token=OAuth1Token(**oauth1),
|
||||
oauth2_token=OAuth2Token(**oauth2),
|
||||
domain=oauth1.get("domain"),
|
||||
)
|
||||
|
||||
|
||||
client = Client()
|
||||
0
garth/src/garth/py.typed
Normal file
0
garth/src/garth/py.typed
Normal file
259
garth/src/garth/sso.py
Normal file
259
garth/src/garth/sso.py
Normal file
@@ -0,0 +1,259 @@
|
||||
import asyncio
|
||||
import re
|
||||
import time
|
||||
from typing import Any, Callable, Dict, Literal, Tuple
|
||||
from urllib.parse import parse_qs
|
||||
|
||||
import requests
|
||||
from requests import Session
|
||||
from requests_oauthlib import OAuth1Session
|
||||
|
||||
from . import http
|
||||
from .auth_tokens import OAuth1Token, OAuth2Token
|
||||
from .exc import GarthException
|
||||
|
||||
|
||||
CSRF_RE = re.compile(r'name="_csrf"\s+value="(.+?)"')
|
||||
TITLE_RE = re.compile(r"<title>(.+?)</title>")
|
||||
OAUTH_CONSUMER_URL = "https://thegarth.s3.amazonaws.com/oauth_consumer.json"
|
||||
OAUTH_CONSUMER: Dict[str, str] = {}
|
||||
USER_AGENT = {"User-Agent": "com.garmin.android.apps.connectmobile"}
|
||||
|
||||
|
||||
class GarminOAuth1Session(OAuth1Session):
|
||||
def __init__(
|
||||
self,
|
||||
/,
|
||||
parent: Session | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
global OAUTH_CONSUMER
|
||||
if not OAUTH_CONSUMER:
|
||||
OAUTH_CONSUMER = requests.get(OAUTH_CONSUMER_URL).json()
|
||||
super().__init__(
|
||||
OAUTH_CONSUMER["consumer_key"],
|
||||
OAUTH_CONSUMER["consumer_secret"],
|
||||
**kwargs,
|
||||
)
|
||||
if parent is not None:
|
||||
self.mount("https://", parent.adapters["https://"])
|
||||
self.proxies = parent.proxies
|
||||
self.verify = parent.verify
|
||||
|
||||
|
||||
def login(
|
||||
email: str,
|
||||
password: str,
|
||||
/,
|
||||
client: "http.Client | None" = None,
|
||||
prompt_mfa: Callable | None = lambda: input("MFA code: "),
|
||||
return_on_mfa: bool = False,
|
||||
) -> (
|
||||
Tuple[OAuth1Token, OAuth2Token]
|
||||
| Tuple[Literal["needs_mfa"], dict[str, Any]]
|
||||
):
|
||||
"""Login to Garmin Connect.
|
||||
|
||||
Args:
|
||||
email: Garmin account email
|
||||
password: Garmin account password
|
||||
client: Optional HTTP client to use
|
||||
prompt_mfa: Callable that prompts for MFA code. Returns on MFA if None.
|
||||
return_on_mfa: If True, returns dict with MFA info instead of prompting
|
||||
|
||||
Returns:
|
||||
If return_on_mfa=False (default):
|
||||
Tuple[OAuth1Token, OAuth2Token]: OAuth tokens after login
|
||||
If return_on_mfa=True and MFA required:
|
||||
dict: Contains needs_mfa and client_state for resume_login()
|
||||
"""
|
||||
client = client or http.client
|
||||
|
||||
# Define params based on domain
|
||||
SSO = f"https://sso.{client.domain}/sso"
|
||||
SSO_EMBED = f"{SSO}/embed"
|
||||
SSO_EMBED_PARAMS = dict(
|
||||
id="gauth-widget",
|
||||
embedWidget="true",
|
||||
gauthHost=SSO,
|
||||
)
|
||||
SIGNIN_PARAMS = {
|
||||
**SSO_EMBED_PARAMS,
|
||||
**dict(
|
||||
gauthHost=SSO_EMBED,
|
||||
service=SSO_EMBED,
|
||||
source=SSO_EMBED,
|
||||
redirectAfterAccountLoginUrl=SSO_EMBED,
|
||||
redirectAfterAccountCreationUrl=SSO_EMBED,
|
||||
),
|
||||
}
|
||||
|
||||
# Set cookies
|
||||
client.get("sso", "/sso/embed", params=SSO_EMBED_PARAMS)
|
||||
|
||||
# Get CSRF token
|
||||
client.get(
|
||||
"sso",
|
||||
"/sso/signin",
|
||||
params=SIGNIN_PARAMS,
|
||||
referrer=True,
|
||||
)
|
||||
csrf_token = get_csrf_token(client.last_resp.text)
|
||||
|
||||
# Submit login form with email and password
|
||||
client.post(
|
||||
"sso",
|
||||
"/sso/signin",
|
||||
params=SIGNIN_PARAMS,
|
||||
referrer=True,
|
||||
data=dict(
|
||||
username=email,
|
||||
password=password,
|
||||
embed="true",
|
||||
_csrf=csrf_token,
|
||||
),
|
||||
)
|
||||
title = get_title(client.last_resp.text)
|
||||
|
||||
# Handle MFA
|
||||
if "MFA" in title:
|
||||
if return_on_mfa or prompt_mfa is None:
|
||||
return "needs_mfa", {
|
||||
"signin_params": SIGNIN_PARAMS,
|
||||
"client": client,
|
||||
}
|
||||
|
||||
handle_mfa(client, SIGNIN_PARAMS, prompt_mfa)
|
||||
title = get_title(client.last_resp.text)
|
||||
|
||||
if title != "Success":
|
||||
raise GarthException(f"Unexpected title: {title}")
|
||||
return _complete_login(client)
|
||||
|
||||
|
||||
def get_oauth1_token(ticket: str, client: "http.Client") -> OAuth1Token:
|
||||
sess = GarminOAuth1Session(parent=client.sess)
|
||||
base_url = f"https://connectapi.{client.domain}/oauth-service/oauth/"
|
||||
login_url = f"https://sso.{client.domain}/sso/embed"
|
||||
url = (
|
||||
f"{base_url}preauthorized?ticket={ticket}&login-url={login_url}"
|
||||
"&accepts-mfa-tokens=true"
|
||||
)
|
||||
resp = sess.get(
|
||||
url,
|
||||
headers=USER_AGENT,
|
||||
timeout=client.timeout,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
parsed = parse_qs(resp.text)
|
||||
token = {k: v[0] for k, v in parsed.items()}
|
||||
return OAuth1Token(domain=client.domain, **token) # type: ignore
|
||||
|
||||
|
||||
def exchange(oauth1: OAuth1Token, client: "http.Client") -> OAuth2Token:
|
||||
sess = GarminOAuth1Session(
|
||||
resource_owner_key=oauth1.oauth_token,
|
||||
resource_owner_secret=oauth1.oauth_token_secret,
|
||||
parent=client.sess,
|
||||
)
|
||||
data = dict(mfa_token=oauth1.mfa_token) if oauth1.mfa_token else {}
|
||||
base_url = f"https://connectapi.{client.domain}/oauth-service/oauth/"
|
||||
url = f"{base_url}exchange/user/2.0"
|
||||
headers = {
|
||||
**USER_AGENT,
|
||||
**{"Content-Type": "application/x-www-form-urlencoded"},
|
||||
}
|
||||
resp = sess.post(
|
||||
url,
|
||||
headers=headers,
|
||||
data=data,
|
||||
timeout=client.timeout,
|
||||
)
|
||||
resp.raise_for_status()
|
||||
token = resp.json()
|
||||
return OAuth2Token(**set_expirations(token))
|
||||
|
||||
|
||||
def handle_mfa(
|
||||
client: "http.Client", signin_params: dict, prompt_mfa: Callable
|
||||
) -> None:
|
||||
csrf_token = get_csrf_token(client.last_resp.text)
|
||||
if asyncio.iscoroutinefunction(prompt_mfa):
|
||||
mfa_code = asyncio.run(prompt_mfa())
|
||||
else:
|
||||
mfa_code = prompt_mfa()
|
||||
client.post(
|
||||
"sso",
|
||||
"/sso/verifyMFA/loginEnterMfaCode",
|
||||
params=signin_params,
|
||||
referrer=True,
|
||||
data={
|
||||
"mfa-code": mfa_code,
|
||||
"embed": "true",
|
||||
"_csrf": csrf_token,
|
||||
"fromPage": "setupEnterMfaCode",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def set_expirations(token: dict) -> dict:
|
||||
token["expires_at"] = int(time.time() + token["expires_in"])
|
||||
token["refresh_token_expires_at"] = int(
|
||||
time.time() + token["refresh_token_expires_in"]
|
||||
)
|
||||
return token
|
||||
|
||||
|
||||
def get_csrf_token(html: str) -> str:
|
||||
m = CSRF_RE.search(html)
|
||||
if not m:
|
||||
raise GarthException("Couldn't find CSRF token")
|
||||
return m.group(1)
|
||||
|
||||
|
||||
def get_title(html: str) -> str:
|
||||
m = TITLE_RE.search(html)
|
||||
if not m:
|
||||
raise GarthException("Couldn't find title")
|
||||
return m.group(1)
|
||||
|
||||
|
||||
def resume_login(
|
||||
client_state: dict, mfa_code: str
|
||||
) -> Tuple[OAuth1Token, OAuth2Token]:
|
||||
"""Complete login after MFA code is provided.
|
||||
|
||||
Args:
|
||||
client_state: The client state from login() when MFA was needed
|
||||
mfa_code: The MFA code provided by the user
|
||||
|
||||
Returns:
|
||||
Tuple[OAuth1Token, OAuth2Token]: The OAuth tokens after login
|
||||
"""
|
||||
client = client_state["client"]
|
||||
signin_params = client_state["signin_params"]
|
||||
handle_mfa(client, signin_params, lambda: mfa_code)
|
||||
return _complete_login(client)
|
||||
|
||||
|
||||
def _complete_login(client: "http.Client") -> Tuple[OAuth1Token, OAuth2Token]:
|
||||
"""Complete the login process after successful authentication.
|
||||
|
||||
Args:
|
||||
client: The HTTP client
|
||||
|
||||
Returns:
|
||||
Tuple[OAuth1Token, OAuth2Token]: The OAuth tokens
|
||||
"""
|
||||
# Parse ticket
|
||||
m = re.search(r'embed\?ticket=([^"]+)"', client.last_resp.text)
|
||||
if not m:
|
||||
raise GarthException(
|
||||
"Couldn't find ticket in response"
|
||||
) # pragma: no cover
|
||||
ticket = m.group(1)
|
||||
|
||||
oauth1 = get_oauth1_token(ticket, client)
|
||||
oauth2 = exchange(oauth1, client)
|
||||
|
||||
return oauth1, oauth2
|
||||
18
garth/src/garth/stats/__init__.py
Normal file
18
garth/src/garth/stats/__init__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
__all__ = [
|
||||
"DailyHRV",
|
||||
"DailyHydration",
|
||||
"DailyIntensityMinutes",
|
||||
"DailySleep",
|
||||
"DailySteps",
|
||||
"DailyStress",
|
||||
"WeeklyIntensityMinutes",
|
||||
"WeeklyStress",
|
||||
"WeeklySteps",
|
||||
]
|
||||
|
||||
from .hrv import DailyHRV
|
||||
from .hydration import DailyHydration
|
||||
from .intensity_minutes import DailyIntensityMinutes, WeeklyIntensityMinutes
|
||||
from .sleep import DailySleep
|
||||
from .steps import DailySteps, WeeklySteps
|
||||
from .stress import DailyStress, WeeklyStress
|
||||
53
garth/src/garth/stats/_base.py
Normal file
53
garth/src/garth/stats/_base.py
Normal file
@@ -0,0 +1,53 @@
|
||||
from datetime import date, timedelta
|
||||
from typing import ClassVar
|
||||
|
||||
from pydantic.dataclasses import dataclass
|
||||
from typing_extensions import Self
|
||||
|
||||
from .. import http
|
||||
from ..utils import camel_to_snake_dict, format_end_date
|
||||
|
||||
|
||||
@dataclass
|
||||
class Stats:
|
||||
calendar_date: date
|
||||
|
||||
_path: ClassVar[str]
|
||||
_page_size: ClassVar[int]
|
||||
|
||||
@classmethod
|
||||
def list(
|
||||
cls,
|
||||
end: date | str | None = None,
|
||||
period: int = 1,
|
||||
*,
|
||||
client: http.Client | None = None,
|
||||
) -> list[Self]:
|
||||
client = client or http.client
|
||||
end = format_end_date(end)
|
||||
period_type = "days" if "daily" in cls._path else "weeks"
|
||||
|
||||
if period > cls._page_size:
|
||||
page = cls.list(end, cls._page_size, client=client)
|
||||
if not page:
|
||||
return []
|
||||
page = (
|
||||
cls.list(
|
||||
end - timedelta(**{period_type: cls._page_size}),
|
||||
period - cls._page_size,
|
||||
client=client,
|
||||
)
|
||||
+ page
|
||||
)
|
||||
return page
|
||||
|
||||
start = end - timedelta(**{period_type: period - 1})
|
||||
path = cls._path.format(start=start, end=end, period=period)
|
||||
page_dirs = client.connectapi(path)
|
||||
if not isinstance(page_dirs, list) or not page_dirs:
|
||||
return []
|
||||
page_dirs = [d for d in page_dirs if isinstance(d, dict)]
|
||||
if page_dirs and "values" in page_dirs[0]:
|
||||
page_dirs = [{**stat, **stat.pop("values")} for stat in page_dirs]
|
||||
page_dirs = [camel_to_snake_dict(stat) for stat in page_dirs]
|
||||
return [cls(**stat) for stat in page_dirs]
|
||||
66
garth/src/garth/stats/hrv.py
Normal file
66
garth/src/garth/stats/hrv.py
Normal file
@@ -0,0 +1,66 @@
|
||||
from datetime import date, datetime, timedelta
|
||||
from typing import Any, ClassVar, cast
|
||||
|
||||
from pydantic.dataclasses import dataclass
|
||||
from typing_extensions import Self
|
||||
|
||||
from .. import http
|
||||
from ..utils import camel_to_snake_dict, format_end_date
|
||||
|
||||
|
||||
@dataclass
|
||||
class HRVBaseline:
|
||||
low_upper: int
|
||||
balanced_low: int
|
||||
balanced_upper: int
|
||||
marker_value: float | None
|
||||
|
||||
|
||||
@dataclass
|
||||
class DailyHRV:
|
||||
calendar_date: date
|
||||
weekly_avg: int | None
|
||||
last_night_avg: int | None
|
||||
last_night_5_min_high: int | None
|
||||
baseline: HRVBaseline | None
|
||||
status: str
|
||||
feedback_phrase: str
|
||||
create_time_stamp: datetime
|
||||
|
||||
_path: ClassVar[str] = "/hrv-service/hrv/daily/{start}/{end}"
|
||||
_page_size: ClassVar[int] = 28
|
||||
|
||||
@classmethod
|
||||
def list(
|
||||
cls,
|
||||
end: date | str | None = None,
|
||||
period: int = 28,
|
||||
*,
|
||||
client: http.Client | None = None,
|
||||
) -> list[Self]:
|
||||
client = client or http.client
|
||||
end = format_end_date(end)
|
||||
|
||||
# Paginate if period is greater than page size
|
||||
if period > cls._page_size:
|
||||
page = cls.list(end, cls._page_size, client=client)
|
||||
if not page:
|
||||
return []
|
||||
page = (
|
||||
cls.list(
|
||||
end - timedelta(days=cls._page_size),
|
||||
period - cls._page_size,
|
||||
client=client,
|
||||
)
|
||||
+ page
|
||||
)
|
||||
return page
|
||||
|
||||
start = end - timedelta(days=period - 1)
|
||||
path = cls._path.format(start=start, end=end)
|
||||
response = client.connectapi(path)
|
||||
if response is None:
|
||||
return []
|
||||
daily_hrv = camel_to_snake_dict(response)["hrv_summaries"]
|
||||
daily_hrv = cast(list[dict[str, Any]], daily_hrv)
|
||||
return [cls(**hrv) for hrv in daily_hrv]
|
||||
17
garth/src/garth/stats/hydration.py
Normal file
17
garth/src/garth/stats/hydration.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from typing import ClassVar
|
||||
|
||||
from pydantic.dataclasses import dataclass
|
||||
|
||||
from ._base import Stats
|
||||
|
||||
|
||||
BASE_PATH = "/usersummary-service/stats/hydration"
|
||||
|
||||
|
||||
@dataclass
|
||||
class DailyHydration(Stats):
|
||||
value_in_ml: float
|
||||
goal_in_ml: float
|
||||
|
||||
_path: ClassVar[str] = f"{BASE_PATH}/daily/{{start}}/{{end}}"
|
||||
_page_size: ClassVar[int] = 28
|
||||
28
garth/src/garth/stats/intensity_minutes.py
Normal file
28
garth/src/garth/stats/intensity_minutes.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from typing import ClassVar
|
||||
|
||||
from pydantic.dataclasses import dataclass
|
||||
|
||||
from ._base import Stats
|
||||
|
||||
|
||||
BASE_PATH = "/usersummary-service/stats/im"
|
||||
|
||||
|
||||
@dataclass
|
||||
class DailyIntensityMinutes(Stats):
|
||||
weekly_goal: int
|
||||
moderate_value: int | None = None
|
||||
vigorous_value: int | None = None
|
||||
|
||||
_path: ClassVar[str] = f"{BASE_PATH}/daily/{{start}}/{{end}}"
|
||||
_page_size: ClassVar[int] = 28
|
||||
|
||||
|
||||
@dataclass
|
||||
class WeeklyIntensityMinutes(Stats):
|
||||
weekly_goal: int
|
||||
moderate_value: int | None = None
|
||||
vigorous_value: int | None = None
|
||||
|
||||
_path: ClassVar[str] = f"{BASE_PATH}/weekly/{{start}}/{{end}}"
|
||||
_page_size: ClassVar[int] = 52
|
||||
15
garth/src/garth/stats/sleep.py
Normal file
15
garth/src/garth/stats/sleep.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from typing import ClassVar
|
||||
|
||||
from pydantic.dataclasses import dataclass
|
||||
|
||||
from ._base import Stats
|
||||
|
||||
|
||||
@dataclass
|
||||
class DailySleep(Stats):
|
||||
value: int | None
|
||||
|
||||
_path: ClassVar[str] = (
|
||||
"/wellness-service/stats/daily/sleep/score/{start}/{end}"
|
||||
)
|
||||
_page_size: ClassVar[int] = 28
|
||||
30
garth/src/garth/stats/steps.py
Normal file
30
garth/src/garth/stats/steps.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from typing import ClassVar
|
||||
|
||||
from pydantic.dataclasses import dataclass
|
||||
|
||||
from ._base import Stats
|
||||
|
||||
|
||||
BASE_PATH = "/usersummary-service/stats/steps"
|
||||
|
||||
|
||||
@dataclass
|
||||
class DailySteps(Stats):
|
||||
total_steps: int | None
|
||||
total_distance: int | None
|
||||
step_goal: int
|
||||
|
||||
_path: ClassVar[str] = f"{BASE_PATH}/daily/{{start}}/{{end}}"
|
||||
_page_size: ClassVar[int] = 28
|
||||
|
||||
|
||||
@dataclass
|
||||
class WeeklySteps(Stats):
|
||||
total_steps: int
|
||||
average_steps: float
|
||||
average_distance: float
|
||||
total_distance: float
|
||||
wellness_data_days_count: int
|
||||
|
||||
_path: ClassVar[str] = f"{BASE_PATH}/weekly/{{end}}/{{period}}"
|
||||
_page_size: ClassVar[int] = 52
|
||||
28
garth/src/garth/stats/stress.py
Normal file
28
garth/src/garth/stats/stress.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from typing import ClassVar
|
||||
|
||||
from pydantic.dataclasses import dataclass
|
||||
|
||||
from ._base import Stats
|
||||
|
||||
|
||||
BASE_PATH = "/usersummary-service/stats/stress"
|
||||
|
||||
|
||||
@dataclass
|
||||
class DailyStress(Stats):
|
||||
overall_stress_level: int
|
||||
rest_stress_duration: int | None = None
|
||||
low_stress_duration: int | None = None
|
||||
medium_stress_duration: int | None = None
|
||||
high_stress_duration: int | None = None
|
||||
|
||||
_path: ClassVar[str] = f"{BASE_PATH}/daily/{{start}}/{{end}}"
|
||||
_page_size: ClassVar[int] = 28
|
||||
|
||||
|
||||
@dataclass
|
||||
class WeeklyStress(Stats):
|
||||
value: int
|
||||
|
||||
_path: ClassVar[str] = f"{BASE_PATH}/weekly/{{end}}/{{period}}"
|
||||
_page_size: ClassVar[int] = 52
|
||||
5
garth/src/garth/users/__init__.py
Normal file
5
garth/src/garth/users/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from .profile import UserProfile
|
||||
from .settings import UserSettings
|
||||
|
||||
|
||||
__all__ = ["UserProfile", "UserSettings"]
|
||||
79
garth/src/garth/users/profile.py
Normal file
79
garth/src/garth/users/profile.py
Normal file
@@ -0,0 +1,79 @@
|
||||
from pydantic.dataclasses import dataclass
|
||||
from typing_extensions import Self
|
||||
|
||||
from .. import http
|
||||
from ..utils import camel_to_snake_dict
|
||||
|
||||
|
||||
@dataclass
|
||||
class UserProfile:
|
||||
id: int
|
||||
profile_id: int
|
||||
garmin_guid: str
|
||||
display_name: str
|
||||
full_name: str
|
||||
user_name: str
|
||||
profile_image_type: str | None
|
||||
profile_image_url_large: str | None
|
||||
profile_image_url_medium: str | None
|
||||
profile_image_url_small: str | None
|
||||
location: str | None
|
||||
facebook_url: str | None
|
||||
twitter_url: str | None
|
||||
personal_website: str | None
|
||||
motivation: str | None
|
||||
bio: str | None
|
||||
primary_activity: str | None
|
||||
favorite_activity_types: list[str]
|
||||
running_training_speed: float
|
||||
cycling_training_speed: float
|
||||
favorite_cycling_activity_types: list[str]
|
||||
cycling_classification: str | None
|
||||
cycling_max_avg_power: float
|
||||
swimming_training_speed: float
|
||||
profile_visibility: str
|
||||
activity_start_visibility: str
|
||||
activity_map_visibility: str
|
||||
course_visibility: str
|
||||
activity_heart_rate_visibility: str
|
||||
activity_power_visibility: str
|
||||
badge_visibility: str
|
||||
show_age: bool
|
||||
show_weight: bool
|
||||
show_height: bool
|
||||
show_weight_class: bool
|
||||
show_age_range: bool
|
||||
show_gender: bool
|
||||
show_activity_class: bool
|
||||
show_vo_2_max: bool
|
||||
show_personal_records: bool
|
||||
show_last_12_months: bool
|
||||
show_lifetime_totals: bool
|
||||
show_upcoming_events: bool
|
||||
show_recent_favorites: bool
|
||||
show_recent_device: bool
|
||||
show_recent_gear: bool
|
||||
show_badges: bool
|
||||
other_activity: str | None
|
||||
other_primary_activity: str | None
|
||||
other_motivation: str | None
|
||||
user_roles: list[str]
|
||||
name_approved: bool
|
||||
user_profile_full_name: str
|
||||
make_golf_scorecards_private: bool
|
||||
allow_golf_live_scoring: bool
|
||||
allow_golf_scoring_by_connections: bool
|
||||
user_level: int
|
||||
user_point: int
|
||||
level_update_date: str
|
||||
level_is_viewed: bool
|
||||
level_point_threshold: int
|
||||
user_point_offset: int
|
||||
user_pro: bool
|
||||
|
||||
@classmethod
|
||||
def get(cls, /, client: http.Client | None = None) -> Self:
|
||||
client = client or http.client
|
||||
profile = client.connectapi("/userprofile-service/socialProfile")
|
||||
assert isinstance(profile, dict)
|
||||
return cls(**camel_to_snake_dict(profile))
|
||||
108
garth/src/garth/users/settings.py
Normal file
108
garth/src/garth/users/settings.py
Normal file
@@ -0,0 +1,108 @@
|
||||
from datetime import date
|
||||
from typing import Dict
|
||||
|
||||
from pydantic.dataclasses import dataclass
|
||||
from typing_extensions import Self
|
||||
|
||||
from .. import http
|
||||
from ..utils import camel_to_snake_dict
|
||||
|
||||
|
||||
@dataclass
|
||||
class PowerFormat:
|
||||
format_id: int
|
||||
format_key: str
|
||||
min_fraction: int
|
||||
max_fraction: int
|
||||
grouping_used: bool
|
||||
display_format: str | None
|
||||
|
||||
|
||||
@dataclass
|
||||
class FirstDayOfWeek:
|
||||
day_id: int
|
||||
day_name: str
|
||||
sort_order: int
|
||||
is_possible_first_day: bool
|
||||
|
||||
|
||||
@dataclass
|
||||
class WeatherLocation:
|
||||
use_fixed_location: bool | None
|
||||
latitude: float | None
|
||||
longitude: float | None
|
||||
location_name: str | None
|
||||
iso_country_code: str | None
|
||||
postal_code: str | None
|
||||
|
||||
|
||||
@dataclass
|
||||
class UserData:
|
||||
gender: str
|
||||
weight: float
|
||||
height: float
|
||||
time_format: str
|
||||
birth_date: date
|
||||
measurement_system: str
|
||||
activity_level: str | None
|
||||
handedness: str
|
||||
power_format: PowerFormat
|
||||
heart_rate_format: PowerFormat
|
||||
first_day_of_week: FirstDayOfWeek
|
||||
vo_2_max_running: float | None
|
||||
vo_2_max_cycling: float | None
|
||||
lactate_threshold_speed: float | None
|
||||
lactate_threshold_heart_rate: float | None
|
||||
dive_number: int | None
|
||||
intensity_minutes_calc_method: str
|
||||
moderate_intensity_minutes_hr_zone: int
|
||||
vigorous_intensity_minutes_hr_zone: int
|
||||
hydration_measurement_unit: str
|
||||
hydration_containers: list[Dict[str, float | str | None]]
|
||||
hydration_auto_goal_enabled: bool
|
||||
firstbeat_max_stress_score: float | None
|
||||
firstbeat_cycling_lt_timestamp: int | None
|
||||
firstbeat_running_lt_timestamp: int | None
|
||||
threshold_heart_rate_auto_detected: bool
|
||||
ftp_auto_detected: bool | None
|
||||
training_status_paused_date: str | None
|
||||
weather_location: WeatherLocation | None
|
||||
golf_distance_unit: str | None
|
||||
golf_elevation_unit: str | None
|
||||
golf_speed_unit: str | None
|
||||
external_bottom_time: float | None
|
||||
|
||||
|
||||
@dataclass
|
||||
class UserSleep:
|
||||
sleep_time: int
|
||||
default_sleep_time: bool
|
||||
wake_time: int
|
||||
default_wake_time: bool
|
||||
|
||||
|
||||
@dataclass
|
||||
class UserSleepWindow:
|
||||
sleep_window_frequency: str
|
||||
start_sleep_time_seconds_from_midnight: int
|
||||
end_sleep_time_seconds_from_midnight: int
|
||||
|
||||
|
||||
@dataclass
|
||||
class UserSettings:
|
||||
id: int
|
||||
user_data: UserData
|
||||
user_sleep: UserSleep
|
||||
connect_date: str | None
|
||||
source_type: str | None
|
||||
user_sleep_windows: list[UserSleepWindow] | None = None
|
||||
|
||||
@classmethod
|
||||
def get(cls, /, client: http.Client | None = None) -> Self:
|
||||
client = client or http.client
|
||||
settings = client.connectapi(
|
||||
"/userprofile-service/userprofile/user-settings"
|
||||
)
|
||||
assert isinstance(settings, dict)
|
||||
data = camel_to_snake_dict(settings)
|
||||
return cls(**data)
|
||||
73
garth/src/garth/utils.py
Normal file
73
garth/src/garth/utils.py
Normal file
@@ -0,0 +1,73 @@
|
||||
import dataclasses
|
||||
import re
|
||||
from datetime import date, datetime, timedelta, timezone
|
||||
from typing import Any, Dict, List, Union
|
||||
|
||||
|
||||
CAMEL_TO_SNAKE = re.compile(
|
||||
r"((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z])|(?<=[a-zA-Z])[0-9])"
|
||||
)
|
||||
|
||||
|
||||
def camel_to_snake(camel_str: str) -> str:
|
||||
snake_str = CAMEL_TO_SNAKE.sub(r"_\1", camel_str)
|
||||
return snake_str.lower()
|
||||
|
||||
|
||||
def camel_to_snake_dict(camel_dict: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Converts a dictionary's keys from camel case to snake case. This version
|
||||
handles nested dictionaries and lists.
|
||||
"""
|
||||
snake_dict: Dict[str, Any] = {}
|
||||
for k, v in camel_dict.items():
|
||||
new_key = camel_to_snake(k)
|
||||
if isinstance(v, dict):
|
||||
snake_dict[new_key] = camel_to_snake_dict(v)
|
||||
elif isinstance(v, list):
|
||||
snake_dict[new_key] = [
|
||||
camel_to_snake_dict(i) if isinstance(i, dict) else i for i in v
|
||||
]
|
||||
else:
|
||||
snake_dict[new_key] = v
|
||||
return snake_dict
|
||||
|
||||
|
||||
def format_end_date(end: Union[date, str, None]) -> date:
|
||||
if end is None:
|
||||
end = date.today()
|
||||
elif isinstance(end, str):
|
||||
end = date.fromisoformat(end)
|
||||
return end
|
||||
|
||||
|
||||
def date_range(date_: Union[date, str], days: int):
|
||||
date_ = date_ if isinstance(date_, date) else date.fromisoformat(date_)
|
||||
for day in range(days):
|
||||
yield date_ - timedelta(days=day)
|
||||
|
||||
|
||||
def asdict(obj):
|
||||
if dataclasses.is_dataclass(obj):
|
||||
result = {}
|
||||
for field in dataclasses.fields(obj):
|
||||
value = getattr(obj, field.name)
|
||||
result[field.name] = asdict(value)
|
||||
return result
|
||||
|
||||
if isinstance(obj, List):
|
||||
return [asdict(v) for v in obj]
|
||||
|
||||
if isinstance(obj, (datetime, date)):
|
||||
return obj.isoformat()
|
||||
|
||||
return obj
|
||||
|
||||
|
||||
def get_localized_datetime(
|
||||
gmt_timestamp: int, local_timestamp: int
|
||||
) -> datetime:
|
||||
local_diff = local_timestamp - gmt_timestamp
|
||||
local_offset = timezone(timedelta(milliseconds=local_diff))
|
||||
gmt_time = datetime.fromtimestamp(gmt_timestamp / 1000, timezone.utc)
|
||||
return gmt_time.astimezone(local_offset)
|
||||
1
garth/src/garth/version.py
Normal file
1
garth/src/garth/version.py
Normal file
@@ -0,0 +1 @@
|
||||
__version__ = "0.5.17"
|
||||
BIN
garth/tests/12129115726_ACTIVITY.fit
Normal file
BIN
garth/tests/12129115726_ACTIVITY.fit
Normal file
Binary file not shown.
461
garth/tests/cassettes/test_client_request.yaml
Normal file
461
garth/tests/cassettes/test_client_request.yaml
Normal file
File diff suppressed because one or more lines are too long
65
garth/tests/cassettes/test_connectapi.yaml
Normal file
65
garth/tests/cassettes/test_connectapi.yaml
Normal file
@@ -0,0 +1,65 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
referer:
|
||||
- https://sso.garmin.com/sso/verifyMFA/loginEnterMfaCode?id=gauth-widget&service=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&source=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountLoginUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountCreationUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/usersummary-service/stats/stress/daily/2023-07-21/2023-07-21
|
||||
response:
|
||||
body:
|
||||
string: '[{"calendarDate": "2023-07-21", "values": {"highStressDuration": 3240,
|
||||
"lowStressDuration": 20280, "overallStressLevel": 35, "restStressDuration":
|
||||
31020, "mediumStressDuration": 11640}}]'
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 7f12d932aa00b6ee-QRO
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Fri, 04 Aug 2023 00:57:49 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=FuGLLTTuU8CV4eTRQnQ7XY0oTrHoXEaIYrPbxrkK1vRVT4yAr2Zv0YIj4D%2BZ0eQTeYgycpuCP1gSE4yk0bZE2Aj2p29AIZ2Ce%2BuOUJqB9Mp54VyHR9uEC5AAcVLUYqtzpE4YIK0Fgw%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cache-control:
|
||||
- no-cache, no-store, private
|
||||
pragma:
|
||||
- no-cache
|
||||
set-cookie:
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
223
garth/tests/cassettes/test_delete.yaml
Normal file
223
garth/tests/cassettes/test_delete.yaml
Normal file
@@ -0,0 +1,223 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/activity-service/activity/12135235656
|
||||
response:
|
||||
body:
|
||||
string: !!binary |
|
||||
H4sIAAAAAAAAA6VW23LbNhD9lQ6eRZsXURL5ZstOookle2SpmbbT8ayAJYUGBDgAqETJ+N874EWk
|
||||
ZbV96Btx9oLF7p5d/iRALT9we1wwkgZhEMVhFE/iyegk2G4XdyT9SaqKM5KS2SxJqD9NvCQJY288
|
||||
zSIPwmnosSBE5tNxsIMpee3NV1AgSclvKgcyIpVB/aRVxgW6C8M4CSZ+OCLcLCth+XOptH0CjdKS
|
||||
NANhsHe0OZZ4t3l0odhjWZsHk2hUHz7jkaTk2NxR1vabVidMnPdPnDGUJ58ajdWcWmQnyGpeFLAT
|
||||
2CKvI4KH1s/Ztcnw0kpSsJgrzX8gIyNilLaPmqEmaeDXeaBozFxJq5VYV+LcWTh0Vmp+AIsuf5YX
|
||||
+LuSuJXctiaV5LZ+dhyO6kNjdFOg5hSu53tOIVdkRDKgVmmS+ld+7+iC5uuIFGiBgYX2Cm4eNc+5
|
||||
BEFSqyscEYYHTvGmLAWnYLmSC2ksCNF8u2yEQTh2/ZKjtP+iJyshWq1nq7nMO8g1wwelC7Augqz+
|
||||
cgbTUXtonplxW/eVMYpysMjmqtIGe9cCjN2WDCzeuRymJPTDyPMTL0w2YZCOkzSOr3zXhKVQwJD9
|
||||
h9qBM1RbLTr/ezBPShwFl3jqmj2Y+R60vQMLXcb2YD7pDS9wIV3ezQB/Ut/wraj143ixkJm6s6qu
|
||||
dE+Tp68DmjBuSgFH2XCqsFDwH9wVvBKiBZdgufxl00nKlmwF5LjV4gF0jt2DzmRLZLwqSEr21pYm
|
||||
vb420RUU8ENJ+GauqCquc9AFlx5VUiK1XqkVu26dvHDnxVxPZjEECSYeTHfojSEIvGSXJV4GFHwa
|
||||
syALwGvfc1XK/H2IzwUI8X+jiPww88PA85FOvTGjibfzaeQxF9yOxuDP6FkUbcp79tM9F2zBDEn/
|
||||
+LM93QxGUYsblEbp9nCapXUUDVaArBwfK+1GAvl4s14uVsSV8oCrqtg5tOvfcq4qN/mCYZt/Ufqr
|
||||
qmzf59zc2LILpQMbmi7Rwl3P5gZ0liSKwiSZziazgHTK7hWuu6LJdOZ34K+oDVfS4Q2xX+vGXUiL
|
||||
0rinSYv6AMIMKfBcCm57BG/5V1zC9xtjuLFLxVwumjBr0S1Yi/q4NdD34lCwxgK4HEyIWth7q4my
|
||||
eXzgxg64ua5kTa8vXLKGjo2IgkDJQN+7ae5MO0GuVVWuOcNmw7Xalb40WwqwdI9sfllaojZKglgj
|
||||
VbpfKVBZNQdB5yCU5gO2Z3BQmtt+jLgmAdHXtFtTZ0+6l25Bna7N6V9++HaBDRYaCjzUw3eutMbB
|
||||
rnsdEVMVBehj2ybGgrZuKj0o6gZ/PxFnmyBJAz+Np/VEPCl+XG7eDk7fH6gxlyWuJEmTq8l0MiKF
|
||||
OnCZ353DKKA0yN7h9JSvwC2wXaH7DNYrDQ6oIcdPa5LOpg4o4Ht9SAbSDRYlanDEI2k0btUuoVxe
|
||||
QK1uevA+y5Da7mIJqNWO081F6SXZEk3T52T1+HJzv368Xcxfbu9X9x8Wm5dgRv7R6VvD1bmpS3Q9
|
||||
b/AZHfeiEfkGFvW9sbyAutrB+3c8wA5dhberz6vHLysy+L1q9R4UsPo9syAI4mkYTuJJGNdZ6vrz
|
||||
Acp3Natb49lqNIakk8jdjJKdgDo/jGcZapQUO9yrFY3F0pXWlS4/mYRtxYZOX1//BraydeSxCgAA
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 80e771592928359a-DFW
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Encoding:
|
||||
- gzip
|
||||
Content-Type:
|
||||
- application/json;charset=UTF-8
|
||||
Date:
|
||||
- Fri, 29 Sep 2023 21:50:37 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=m77%2F5qqWH%2FzNYg9h9aJB23Sa8erERimKhptV3iEpuPvFpQKcBvr8kHp%2B0tcMmTnLbEN%2FZr0zE7r9yfH0C5bHKK80P8CeBzFhzo9RkFicBPRHZMMaxBDwn7fNmDGgZpOGV9NydCV2LQ%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cache-control:
|
||||
- no-cache, no-store, private
|
||||
pragma:
|
||||
- no-cache
|
||||
set-cookie:
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Length:
|
||||
- '0'
|
||||
Cookie:
|
||||
- _cfuvid=SANITIZED; ADRUM_BT1=SANITIZED; ADRUM_BTa=SANITIZED; SameSite=SANITIZED
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: DELETE
|
||||
uri: https://connectapi.garmin.com/activity-service/activity/12135235656
|
||||
response:
|
||||
body:
|
||||
string: ''
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 80e7715a5b05359a-DFW
|
||||
Connection:
|
||||
- keep-alive
|
||||
Date:
|
||||
- Fri, 29 Sep 2023 21:50:37 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=RR3a8akw5KIfJ40HMjm%2FVxtacqIHiN3EkPNr5ZFwq02kvcv2Wt8fzZL9kbXMXFTHMd3iL7ZcPj4074wQQsCMR29xUXurv6SH5Nd2hdW2qeQT%2Bl7fsosUtcPp3mglfZcBnFOy9JteAg%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cache-control:
|
||||
- no-cache, no-store, private
|
||||
pragma:
|
||||
- no-cache
|
||||
set-cookie:
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
status:
|
||||
code: 204
|
||||
message: No Content
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
Cookie:
|
||||
- _cfuvid=SANITIZED; ADRUM_BT1=SANITIZED; ADRUM_BTa=SANITIZED; SameSite=SANITIZED
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/activity-service/activity/12135235656
|
||||
response:
|
||||
body:
|
||||
string: !!binary |
|
||||
H4sIAAAAAAAAA6tWyk0tLk5MT1WyUvIICQlQMDEwUfDLL1Fwyy/NS1HSUUotKsovUrJS8ssvAQu5
|
||||
ViSnFpRk5ucp1QIAv2CADDwAAAA=
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 80e7715cbe4b359a-DFW
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Encoding:
|
||||
- gzip
|
||||
Content-Type:
|
||||
- application/json;charset=UTF-8
|
||||
Date:
|
||||
- Fri, 29 Sep 2023 21:50:37 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=sBhGq8nTIKEsQsz%2FTdQHQGlCFN93mqZLF20y8BX8Vf4lPeqs0bM27QSt8IU1udH7S7x8wGmhmS3PzVMmthvCEbT8L1GrNICizdJ6H28Z%2Bd3F%2B4Em9Upz9aThxIiFzIPB8Zw6iEN%2Fqg%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cache-control:
|
||||
- no-cache, no-store, private
|
||||
pragma:
|
||||
- no-cache
|
||||
set-cookie:
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
status:
|
||||
code: 404
|
||||
message: Not Found
|
||||
version: 1
|
||||
618
garth/tests/cassettes/test_download.yaml
Normal file
618
garth/tests/cassettes/test_download.yaml
Normal file
@@ -0,0 +1,618 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
referer:
|
||||
- https://sso.garmin.com/sso/verifyMFA/loginEnterMfaCode?id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&service=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&source=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountLoginUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountCreationUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/download-service/files/activity/11998957007
|
||||
response:
|
||||
body:
|
||||
string: !!binary |
|
||||
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
|
||||
6ujSg9z1Je76Y9z1w1263KVHurTnO993AX1tvt/O9/J8j9Ie7vv5vuvEt7a4rr7r5rsC3/Xy/SN8
|
||||
v7fvjvS9Ab430HeDfTo3pZMMbi1Sk2sYT9LncjnEY873EhN874l+vnfej7bh6+m7op03q7kH3P4r
|
||||
Pun9YePx9YV/C/9/8lrZ5KZ7930RPjvfD7/sCZ7v5Q6i9Vjo5Xv9tuAnnclblZd7Ij3PW44vr5a+
|
||||
vIC/udrsoDYnqM0Nauk76xXUHIFvTr6wAUEtfWEnBbUXFCeWBbXLA3dp4FYE7rLArQzSlwfpVUHt
|
||||
VUHt5qB2R1Dzy2DNr4I132PLk6+2s0t3cemuLt3NpQtcutCle7v0kS7dx6X7uvRRLj3QpUtc+hiX
|
||||
Ptalp7t0pUvPdelTXXqeSy916fNcutqlL3LptEvXuHStS9/m0ne69FaXvt+ld7r0wy79c5f+hUs/
|
||||
4tJPufTTLv0CrzrP971M38vi0V1opTlZad1918N3Rb43yPcG+16574/0/Rk8aY87zXcLfHeO7xb5
|
||||
brHvlvjuXN+d77uLff8S37/CuSudu8F3a53b5Pu7fe9J33vGd8/67jnfPe+7l31X77s9vtvru3a8
|
||||
nvUndg3/dwGtQ/7ZhbuE0P+P//CPnzv3wsVn/jt7pX/1L9xeWh/f5tm/+//yfeynyHMHJ7bktLT0
|
||||
dy0tjwTrcpsyz/dy/NxBqTPo77zT6tGz9TXXIBBtbYV4o4EtLfNa/uO7ivy2u4ppnnekl/MddoqZ
|
||||
QS3ts+gXl+F7AW8NLkG7rXAvyD8h+sUU0h7l6Kb/BVBLBwipDEp8X3oAAJRYAQBQSwECFAAUAAgI
|
||||
CACQkytXqQxKfF96AACUWAEAGAAAAAAAAAAAAAAAAAAAAAAAMTE5OTg5NTcwMDdfQUNUSVZJVFku
|
||||
Zml0UEsFBgAAAAABAAEARgAAAKV6AAAAAA==
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 8051f899de562845-DFW
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/x-zip-compressed
|
||||
Date:
|
||||
- Mon, 11 Sep 2023 18:28:32 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=UasM5X17vbczyPuHS8ZkKgf9dhIaPVvfztkmUlVZCFpeDvl304Gx8EwjapAM4eMIjt70PTgSNnNAMmXtzkVKh0BVUVAUf9X3p6ro5v%2FIN2mLHmxnv3AU27akiMmY8QOJmwHsSrIsqQ%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cache-control:
|
||||
- no-cache, no-store, private
|
||||
content-disposition:
|
||||
- attachment; filename="11998957007.zip"
|
||||
pragma:
|
||||
- no-cache
|
||||
set-cookie:
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
105
garth/tests/cassettes/test_exchange.yaml
Normal file
105
garth/tests/cassettes/test_exchange.yaml
Normal file
@@ -0,0 +1,105 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- python-requests/2.31.0
|
||||
method: GET
|
||||
uri: https://thegarth.s3.amazonaws.com/oauth_consumer.json
|
||||
response:
|
||||
body:
|
||||
string: '{"consumer_key": "SANITIZED", "consumer_secret": "SANITIZED"}'
|
||||
headers:
|
||||
Accept-Ranges:
|
||||
- bytes
|
||||
Content-Length:
|
||||
- '124'
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Fri, 04 Aug 2023 03:43:24 GMT
|
||||
ETag:
|
||||
- '"20240b1013cb35419bb5b2cff1407a4e"'
|
||||
Last-Modified:
|
||||
- Thu, 03 Aug 2023 00:16:11 GMT
|
||||
Server:
|
||||
- AmazonS3
|
||||
x-amz-id-2:
|
||||
- V8hHVVhXCEX7RD7Vzw8IsKS//xFr7co0468z4G834xsWIJ46GpXAwZKETm68Odczy470cauMZXo=
|
||||
x-amz-request-id:
|
||||
- Z03APPY9GXZFWZ69
|
||||
x-amz-server-side-encryption:
|
||||
- AES256
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: ''
|
||||
headers:
|
||||
Accept:
|
||||
- !!binary |
|
||||
Ki8q
|
||||
Accept-Encoding:
|
||||
- !!binary |
|
||||
Z3ppcCwgZGVmbGF0ZQ==
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- !!binary |
|
||||
a2VlcC1hbGl2ZQ==
|
||||
Content-Length:
|
||||
- !!binary |
|
||||
MA==
|
||||
Content-Type:
|
||||
- !!binary |
|
||||
YXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVk
|
||||
User-Agent:
|
||||
- !!binary |
|
||||
Y29tLmdhcm1pbi5hbmRyb2lkLmFwcHMuY29ubmVjdG1vYmlsZQ==
|
||||
method: POST
|
||||
uri: https://connectapi.garmin.com/oauth-service/oauth/exchange/user/2.0
|
||||
response:
|
||||
body:
|
||||
string: '{"scope": "COMMUNITY_COURSE_READ GARMINPAY_WRITE GOLF_API_READ ATP_READ
|
||||
GHS_SAMD GHS_UPLOAD INSIGHTS_READ COMMUNITY_COURSE_WRITE CONNECT_WRITE GCOFFER_WRITE
|
||||
GARMINPAY_READ DT_CLIENT_ANALYTICS_WRITE GOLF_API_WRITE INSIGHTS_WRITE PRODUCT_SEARCH_READ
|
||||
GCOFFER_READ CONNECT_READ ATP_WRITE", "jti": "SANITIZED", "access_token":
|
||||
"SANITIZED", "token_type": "Bearer", "refresh_token": "SANITIZED", "expires_in":
|
||||
107182, "refresh_token_expires_in": 2591999}'
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 7f13cbbc2a754790-DFW
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Fri, 04 Aug 2023 03:43:23 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=T5EHGPEATgD5SbyAMCZh1mKSEJkUest3sa7l%2FTpQ6dZl3uv3K%2BW7Ng20XTseNh3KPdqYzHdkCCB5d4npBML1ZgAAmVUYdkrYiM2uJhmn7WfvSdrIyme0uCf9p5t7RY6%2BRUxNYfhL8Q%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cache-control:
|
||||
- no-cache, no-store, private
|
||||
pragma:
|
||||
- no-cache
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
1018
garth/tests/cassettes/test_login_command.yaml
Normal file
1018
garth/tests/cassettes/test_login_command.yaml
Normal file
File diff suppressed because it is too large
Load Diff
601
garth/tests/cassettes/test_login_email_password_fail.yaml
Normal file
601
garth/tests/cassettes/test_login_email_password_fail.yaml
Normal file
@@ -0,0 +1,601 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: GET
|
||||
uri: https://sso.garmin.com/sso/embed?id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso
|
||||
response:
|
||||
body:
|
||||
string: "<html>\n\t<head>\n\t <title>GAuth Embedded Version</title>\n\t <meta
|
||||
http-equiv=\"X-UA-Compatible\" content=\"IE=edge;\" />\n\t <style type=\"text/css\">\n\t
|
||||
\ \t#gauth-widget {border: none !important;}\n\t </style>\n\t</head>\n\t<body>\n\t\t<script
|
||||
type=\"text/javascript\" src=\"/sso/js/jquery/3.1.1/jquery.min.js?20210319\"></script>\n\n<div>\n\t<pre>\n\t<span>ERROR:
|
||||
clientId parameter must be specified!!!</span>\n\n\t<span >Usage: https://sso.garmin.com/sso/embed?clientId=<clientId>&locale=<locale>...</span>\n\n\tRequest
|
||||
parameter configuration options:\n\n\tNAME REQ VALUES
|
||||
\ DESCRIPTION\n\t------------------
|
||||
\ --- -------------------------------------------------------
|
||||
\ ---------------------------------------------------------------------------------------------------\n\tclientId
|
||||
\ Yes \"MY_GARMIN\"/\"BUY_GARMIN\"/\"FLY_GARMIN\"/ Client
|
||||
identifier for your web application\n\t \"RMA\"/\"GarminConnect\"/\"OpenCaching\"/etc\n\tlocale
|
||||
\ Yes \"en\", \"bg\", \"cs\", \"da\", \"de\", \"es\",
|
||||
\"el\", \"fr\", \"hr\", User's current locale, to display the GAuth login
|
||||
widget internationalized properly.\n\t \"in\",
|
||||
\"it\", \"iw\", \"hu\", \"ms\", \"nb\", \"nl\", \"no\", \"pl\", (All the
|
||||
currently supported locales are listed in the Values section.)\n\t \"pt\",
|
||||
\"pt_BR\", \"ru\", \"sk\", \"sl\", \"fi\", \"sv\", \"tr\",\n\t \"uk\",
|
||||
\"th\", \"ja\", \"ko\", \"zh_TW\", \"zh\", \"vi_VN\"\n\tcssUrl No
|
||||
\ Absolute URL to custom CSS file. Use custom CSS
|
||||
styling for the GAuth login widget.\n\treauth No
|
||||
\ true/false (Default value is false) Specify true if
|
||||
you want to ensure that the GAuth login widget shows up,\n\t even
|
||||
if the SSO infrastructure remembers the user and would immediately log them
|
||||
in.\n\t This
|
||||
is useful if you know a user is logged on, but want a different user to be
|
||||
allowed to logon.\n\tinitialFocus No true/false (Default
|
||||
value is true) If you don't want the GAuth login widget
|
||||
to autofocus in it's \"Email or Username\" field upon initial loading,\n\t
|
||||
\ then
|
||||
specify this option and set it to false.\n\trememberMeShown No
|
||||
\ true/false (Default value is false) Whether the \"Remember
|
||||
Me\" check box is shown in the GAuth login widget.\n\trememberMeChecked No
|
||||
\ true/false (Default value is false) Whether the \"Remember
|
||||
Me\" check box feature is checked by default.\n\tcreateAccountShown No
|
||||
\ true/false (Default value is true) Whether the \"Don't
|
||||
have an account? Create One\" link is shown in the GAuth login widget.\n\tsocialEnabled
|
||||
\ No true/false (Default value is false) If
|
||||
set to false, do not show any social sign in elements or allow social sign
|
||||
ins.\n\tlockToEmailAddress No Email address to pre-load and
|
||||
lock. If specified, the specified email address will
|
||||
be pre-loaded in the main \"Email\" field in the SSO login form,\n\t as
|
||||
well as in in the \"Email Address\" field in the \"Forgot Password?\" password
|
||||
reset form,\n\t and
|
||||
both fields will be disabled so they can't be changed.\n\t (If
|
||||
for some reason you want to force re-authentications for a known customer
|
||||
account, you can make use of this option.)\n\topenCreateAccount No
|
||||
\ true/false (Default value is false) If set to true,
|
||||
immediately display the the account creation screen.\n\tdisplayNameShown No
|
||||
\ true/false (Default value is false) If set to true,
|
||||
show the \"Display Name\" field on the account creation screen, to allow the
|
||||
user\n\t to
|
||||
set their central MyGarmin display name upon account creation.\n\tglobalOptInShown
|
||||
\ No true/false (Default value is false) Whether
|
||||
the \"Global Opt-In\" check box is shown on the create account & create social
|
||||
account screens.\n\t If
|
||||
set to true these screens will show a \"Sign Up For Email\" check box with
|
||||
accompanying text\n\t \"I
|
||||
would also like to receive email about promotions and new products.\"\n\t
|
||||
\ If
|
||||
checked, the Customer 2.0 account that is created will have it's global opt-in
|
||||
flag set to true,\n\t and
|
||||
Garmin email communications will be allowed.\n\tglobalOptInChecked No
|
||||
\ true/false (Default value is false) Whether the \"Global
|
||||
Opt-In\" check box is checked by default.\n\tconsumeServiceTicket No
|
||||
\ true/false (Default value is true) IF you don't specify
|
||||
a redirectAfterAccountLoginUrl AND you set this to false, the GAuth login
|
||||
widget\n\t will
|
||||
NOT consume the service ticket assigned and will not seamlessly log you into
|
||||
your webapp.\n\t It
|
||||
will send a SUCCESS JavaScript event with the service ticket and service url
|
||||
you can take\n\t and
|
||||
explicitly validate against the SSO infrastructure yourself.\n\t (By
|
||||
using casClient's SingleSignOnUtils.authenticateServiceTicket() utility method,\n\t
|
||||
\ or
|
||||
calling web service customerWebServices_v1.2 AccountManagementService.authenticateServiceTicket().)\n\tmobile
|
||||
\ No true/false (Default value is false) Setting
|
||||
to true will cause mobile friendly views to be shown instead of the tradition
|
||||
screens.\n\ttermsOfUseUrl No Absolute URL to your custom
|
||||
terms of use URL. If not specified, defaults to http://www.garmin.com/terms\n\tprivacyStatementUrl
|
||||
\ No Absolute URL to your custom privacy statement URL. If
|
||||
not specified, defaults to http://www.garmin.com/privacy\n\tproductSupportUrl
|
||||
\ No Absolute URL to your custom product support URL. If
|
||||
not specified, defaults to http://www.garmin.com/us/support/contact\n\tgenerateExtraServiceTicket
|
||||
\ No true/false (Default value is false) If set
|
||||
to true, generate an extra unconsumed service ticket.\n\t\t (The
|
||||
service ticket validation response will include the extra service ticket.)\n\tgenerateTwoExtraServiceTickets
|
||||
\ No true/false (Default value is false) If set to true,
|
||||
generate two extra unconsumed service tickets.\n\t\t\t\t\t\t\t\t\t \t\t\t
|
||||
\ (The service ticket validation response will include the extra service
|
||||
tickets.)\n\tgenerateNoServiceTicket No true/false (Default value
|
||||
is false) If you don't want SSO to generate a service
|
||||
ticket at all when logging in to the GAuth login widget.\n (Useful
|
||||
when allowing logins to static sites that are not SSO enabled and can't consume
|
||||
the service ticket.)\n\tconnectLegalTerms No true/false (Default
|
||||
value is false) Whether to show the connectLegalTerms
|
||||
on the create account page\n\tshowTermsOfUse No true/false
|
||||
(Default value is false) Whether to show the showTermsOfUse
|
||||
on the create account page\n\tshowPrivacyPolicy No true/false
|
||||
(Default value is false) Whether to show the showPrivacyPolicy
|
||||
on the create account page\n\tshowConnectLegalAge No true/false
|
||||
(Default value is false) Whether to show the showConnectLegalAge
|
||||
on the create account page\n\tlocationPromptShown No true/false
|
||||
(Default value is false) If set to true, ask the customer
|
||||
during account creation to verify their country of residence.\n\tshowPassword
|
||||
\ No true/false (Default value is true) If
|
||||
set to false, mobile version for createAccount and login screens would hide
|
||||
the password\n\tuseCustomHeader No true/false (Default value
|
||||
is false) If set to true, the \"Sign in\" text will be
|
||||
replaced by custom text. Contact CDS team to set the i18n text for your client
|
||||
id.\n\tmfaRequired No true/false (Default value is false)
|
||||
\ Require multi factor authentication for all authenticating
|
||||
users.\n\tperformMFACheck No true/false (Default value is
|
||||
false) If set to true, ask the logged in user to pass
|
||||
a multi factor authentication check. (Only valid for an already logged in
|
||||
user.)\n\trememberMyBrowserShown No true/false (Default value is
|
||||
false) Whether the \"Remember My Browser\" check box
|
||||
is shown in the GAuth login widget MFA verification screen.\n\trememberMyBrowserChecked
|
||||
\ No true/false (Default value is false) Whether
|
||||
the \"Remember My Browser\" check box feature is checked by default.\n\tconsentTypeIds\t\t\t\t\tNo\tconsent_types
|
||||
ids\t\t \t\t\t\t\t\t\t\t multiple consent types ids can be passed as consentTypeIds=type1&consentTypeIds=type2\n\t</pre>\n</div>\n\n\n\t<script>(function(){var
|
||||
js = \"window['__CF$cv$params']={r:'7f1ab8a0eff61559'};_cpo=document.createElement('script');_cpo.nonce='',_cpo.src='/cdn-cgi/challenge-platform/scripts/invisible.js',document.getElementsByTagName('head')[0].appendChild(_cpo);\";var
|
||||
_0xh = document.createElement('iframe');_0xh.height = 1;_0xh.width = 1;_0xh.style.position
|
||||
= 'absolute';_0xh.style.top = 0;_0xh.style.left = 0;_0xh.style.border = 'none';_0xh.style.visibility
|
||||
= 'hidden';document.body.appendChild(_0xh);function handler() {var _0xi =
|
||||
_0xh.contentDocument || _0xh.contentWindow.document;if (_0xi) {var _0xj =
|
||||
_0xi.createElement('script');_0xj.innerHTML = js;_0xi.getElementsByTagName('head')[0].appendChild(_0xj);}}if
|
||||
(document.readyState !== 'loading') {handler();} else if (window.addEventListener)
|
||||
{document.addEventListener('DOMContentLoaded', handler);} else {var prev =
|
||||
document.onreadystatechange || function () {};document.onreadystatechange
|
||||
= function (e) {prev(e);if (document.readyState !== 'loading') {document.onreadystatechange
|
||||
= prev;handler();}};}})();</script></body>\n</html>\n"
|
||||
headers:
|
||||
Access-Control-Allow-Credentials:
|
||||
- 'true'
|
||||
Access-Control-Allow-Headers:
|
||||
- Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type,
|
||||
Access-Control-Request-Method, Access-Control-Request-Headers
|
||||
Access-Control-Allow-Methods:
|
||||
- GET,POST,OPTIONS
|
||||
Access-Control-Allow-Origin:
|
||||
- https://www.garmin.com
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 7f1ab8a0eff61559-QRO
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Language:
|
||||
- en
|
||||
Content-Type:
|
||||
- text/html;charset=UTF-8
|
||||
Date:
|
||||
- Fri, 04 Aug 2023 23:53:41 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=j5pKqMzrTlGTnexO0FnuZsm0YObQFg1OH0auGBikdNQ44TMOIITdLtHmkIg36gVUZ65RQe4mMPXUL0SfZUdBcVPtg%2F3Dr3d4GgcIueMqtynkohsWR86sKXRVMZroPe%2Fp"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE=SANITIZED;
|
||||
Path=SANITIZED
|
||||
- __cf_bm=SANITIZED; path=SANITIZED; expires=SANITIZED; domain=SANITIZED; HttpOnly;
|
||||
Secure; SameSite=SANITIZED
|
||||
- __cflb=SANITIZED; SameSite=SANITIZED; Secure; path=SANITIZED; expires=SANITIZED;
|
||||
HttpOnly
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Application-Context:
|
||||
- casServer:cloud,prod,prod-US_1102:6
|
||||
X-B3-Traceid:
|
||||
- 3d744417e02674885d3c4741abc1daad
|
||||
X-Robots-Tag:
|
||||
- noindex
|
||||
X-Vcap-Request-Id:
|
||||
- d62a3e74-b259-4a55-437a-2635831aa9f2
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Connection:
|
||||
- keep-alive
|
||||
Cookie:
|
||||
- __cf_bm=SANITIZED; _cfuvid=SANITIZED; __cflb=SANITIZED; org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE=SANITIZED
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
referer:
|
||||
- https://sso.garmin.com/sso/embed?id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso
|
||||
method: GET
|
||||
uri: https://sso.garmin.com/sso/signin?id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&service=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&source=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountLoginUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountCreationUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed
|
||||
response:
|
||||
body:
|
||||
string: "<!DOCTYPE html>\n<html lang=\"en\" class=\"no-js\">\n <head>\n <meta
|
||||
http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n <meta
|
||||
name=\"viewport\" content=\"width=device-width\" />\n <meta http-equiv=\"X-UA-Compatible\"
|
||||
content=\"IE=edge;\" />\n <title>GARMIN Authentication Application</title>\n
|
||||
\ <link href=\"/sso/css/GAuth.css?20210406\" rel=\"stylesheet\" type=\"text/css\"
|
||||
media=\"all\" />\n\n\t <link rel=\"stylesheet\" href=\"\"/>\n\n <script
|
||||
type=\"text/javascript\" src=\"/sso/js/jquery/3.1.1/jquery.min.js?20210319\"></script>\n
|
||||
\ <script type=\"text/javascript\">jQuery.noConflict();</script>\n\t\t<script
|
||||
type=\"text/javascript\" src=\"/sso/js/jquery-validate/1.16.0/jquery.validate.min.js?20210319\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/jsUtils.js?20210406\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/json2.js\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/consoleUtils.js?20210319\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/postmessage.js?20210319\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/popupWindow.js\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/base.js?20210406\"></script>\n\t\t<script
|
||||
type=\"text/javascript\" src=\"/sso/js/gigyaUtils.js?20210319\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/login.js?20211102\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/reCaptchaUtil.js?20230706\"></script>\n\n
|
||||
\ <script>\n var recaptchaSiteKey = null;\n var
|
||||
reCaptchaURL = \"\\\\\\/sso\\\\\\/reCaptcha?id=gauth-widget\\u0026embedWidget=true\\u0026gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed\\u0026service=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed\\u0026source=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed\\u0026redirectAfterAccountLoginUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed\\u0026redirectAfterAccountCreationUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed\";\n
|
||||
\ var isRecaptchaEnabled = null;\n var recaptchaToken
|
||||
= null; \n </script>\n <script type=\"text/javascript\">\n
|
||||
\ var parent_url = \"https:\\/\\/sso.garmin.com\\/sso\\/embed\";\n
|
||||
\ var status \t\t\t= \"\";\n\t\t\tvar result = \"\";\n\t\t\tvar
|
||||
clientId\t\t= '';\n\t\t\tvar embedWidget \t= true;\n\t\t\tvar isUsernameDefined
|
||||
= (false == true) || (false == true);\n\n // Gigya callback to
|
||||
SocialSignInController for brand new social network users redirects to this
|
||||
page\n // to popup Create or Link Social Account page, but has
|
||||
a possibly mangled source parameter\n // where \"?\" is set as
|
||||
\"<QM>\", so translate it back to \"?\" here.\n parent_url = parent_url.replace('<QM>',
|
||||
'?');\n var parent_scheme = parent_url.substring(0, parent_url.indexOf(\"://\"));\n
|
||||
\ var parent_hostname = parent_url.substring(parent_scheme.length
|
||||
+ 3, parent_url.length);\n if (parent_hostname.indexOf(\"/\") !=
|
||||
-1) {\n parent_hostname = parent_hostname.substring(0, parent_hostname.indexOf(\"/\"));\n
|
||||
\ }\n var parentHost \t = parent_scheme + \"://\"
|
||||
+ parent_hostname;\n\t\t\tvar createAccountConfigURL = '\\/sso\\/createNewAccount?id%3Dgauth-widget%26embedWidget%3Dtrue%26gauthHost%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed%26service%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed%26source%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed%26redirectAfterAccountLoginUrl%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed%26redirectAfterAccountCreationUrl%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed';\n
|
||||
\ var socialConfigURL = 'https://sso.garmin.com/sso/socialSignIn?id%3Dgauth-widget%26embedWidget%3Dtrue%26gauthHost%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed%26service%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed%26source%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed%26redirectAfterAccountLoginUrl%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed%26redirectAfterAccountCreationUrl%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed';\n
|
||||
\ var gigyaURL = \"https://cdns.gigya.com/js/gigya.js?apiKey=2_R3ZGY8Bqlwwk3_63knoD9wA_m-Y19mAgW61bF_s5k9gymYnMEAtMrJiF5MjF-U7B\";\n\n
|
||||
\ if (createAccountConfigURL.indexOf('%253A%252F%252F') != -1) {\n
|
||||
\ \tcreateAccountConfigURL = decodeURIComponent(createAccountConfigURL);\n
|
||||
\ }\n consoleInfo('signin.html embedWidget: true, createAccountConfigURL:
|
||||
\\/sso\\/createNewAccount?id%3Dgauth-widget%26embedWidget%3Dtrue%26gauthHost%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed%26service%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed%26source%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed%26redirectAfterAccountLoginUrl%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed%26redirectAfterAccountCreationUrl%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed,
|
||||
socialEnabled: true, gigyaSupported: true, socialConfigURL(): https://sso.garmin.com/sso/socialSignIn?id%3Dgauth-widget%26embedWidget%3Dtrue%26gauthHost%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed%26service%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed%26source%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed%26redirectAfterAccountLoginUrl%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed%26redirectAfterAccountCreationUrl%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed');\n\n
|
||||
\ if (socialConfigURL.indexOf('%3A%2F%2F') != -1) {\n \tsocialConfigURL
|
||||
= decodeURIComponent(socialConfigURL);\n }\n\n if( status
|
||||
!= null && status != ''){\n \tsend({'status':status});\n }\n\n
|
||||
\ jQuery(document).ready( function(){\n\n\n consoleInfo(\"signin.html:
|
||||
setting field validation rules...\");\n\n jQuery(\"#username\").rules(\"add\",{\n
|
||||
\ required: true,\n messages: {\n required:
|
||||
\ \"Email is required.\"\n }});\n\n jQuery(\"#password\").rules(\"add\",
|
||||
{\n required: true,\n messages: {\n
|
||||
\ required: \"Password is required.\"\n }\n
|
||||
\ });\n\n consoleInfo(\"signin.html: done setting
|
||||
field validation rules...\");\n\n });\n\n XD.receiveMessage(function(m){\n
|
||||
\ consoleInfo(\"signin.html: \" + m.data + \" received on \"
|
||||
+ window.location.host);\n if (m && m.data) {\n var
|
||||
md = m.data;\n if (typeof(md) === 'string') {\n md
|
||||
= JSON.parse(m.data);\n }\n if (md.setUsername)
|
||||
{\n consoleInfo(\"signin.html: Setting username \\\"\"
|
||||
+ md.username + \"\\\"...\");\n jQuery(\"#signInWithDiffLink\").click();
|
||||
// Ensure the normal login form is shown.\n jQuery(\"#username\").val(md.username);\n
|
||||
\ jQuery(\"#password\").focus();\n }\n
|
||||
\ }\n }, parentHost);\n </script>\n </head>\n
|
||||
\ <body>\n\n <!-- begin GAuth component -->\n <div id=\"GAuth-component\">\n
|
||||
\ <!-- begin login component-->\n <div id=\"login-component\"
|
||||
class=\"blueForm-basic\">\n <input type=\"hidden\" id=\"queryString\"
|
||||
value=\"id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&service=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&source=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountLoginUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountCreationUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed\"
|
||||
/>\n\t \t <input type=\"hidden\" id=\"contextPath\" value=\"/sso\" />\n
|
||||
\ <!-- begin login form -->\n <div id=\"login-state-default\">\n
|
||||
\ <h2>Sign In</h2>\n\n <form method=\"post\"
|
||||
id=\"login-form\">\n\n <div class=\"form-alert\">\n\t\t\t\t\t\t\t\n
|
||||
\ \n \n \n
|
||||
\ \n \n \n\n
|
||||
\ <div id=\"username-error\" style=\"display:none;\"></div>\n
|
||||
\ <div id=\"password-error\" style=\"display:none;\"></div>\n
|
||||
\ </div>\n <div class=\"textfield\">\n\t\t\t\t\t\t\t<label
|
||||
for=\"username\">Email</label>\n \t\t<!-- If the
|
||||
lockToEmailAddress parameter is specified then we want to mark the field as
|
||||
readonly,\n \t\tpreload the email address, and disable
|
||||
the other input so that null isn't sent to the server. We'll\n \t\talso
|
||||
style the field to have a darker grey background and disable the mouse pointer\n
|
||||
\ \t\t -->\n\t\t\t\t\t\t\t \n\t\t\t\t\t\t\t\t<!--
|
||||
If the lockToEmailAddress parameter is NOT specified then keep the existing
|
||||
functionality and disable the readonly input field\n\t\t\t\t\t\t\t -->\n\t\t\t\t\t\t\t
|
||||
\ <input class=\"login_email\" name=\"username\" id=\"username\" value=\"\"
|
||||
type=\"email\" spellcheck=\"false\" autocorrect=\"off\" autocapitalize=\"off\"/>\n\n
|
||||
\ </div>\n\n <div class=\"textfield\">\n
|
||||
\ <label for=\"password\">Password</label>\n <a
|
||||
id=\"loginforgotpassword\" class=\"login-forgot-password\" style=\"cursor:pointer\">(Forgot?)</a>\n
|
||||
\ <input type=\"password\" name=\"password\" id=\"password\"
|
||||
spellcheck=\"false\" autocorrect=\"off\" autocapitalize=\"off\" />\n <strong
|
||||
id=\"capslock-warning\" class=\"information\" title=\"Caps lock is on.\" style=\"display:
|
||||
none;\">Caps lock is on.</strong>\n\t\t\t\t\t </div>\n <input
|
||||
type=\"hidden\" name=\"embed\" value=\"true\"/>\n <input
|
||||
type=\"hidden\" name=\"_csrf\" value=\"DAA89CB8362ABB6DB2548101BE44A857AFCE1CC8C7B0825A54361D5D1FB60E47649DA070B388D04CB797C2AD4C9B9FAF9E3C\"
|
||||
/>\n <button type=\"submit\" id=\"login-btn-signin\"
|
||||
class=\"btn1\" accesskey=\"l\">Sign In</button>\n \n\n\n
|
||||
\ <!-- The existence of the \"rememberme\" parameter
|
||||
at all will remember the user! -->\n \n\n </form>\n
|
||||
\ </div>\n <!-- end login form -->\n\n <!--
|
||||
begin Create Account message -->\n\t <div id=\"login-create-account\">\n\t
|
||||
\ \n\t </div>\n\t <!-- end Create Account
|
||||
message -->\n\n\t <!-- begin Social Sign In component -->\n\t <div
|
||||
id=\"SSI-component\">\n \n\n\t\t\t\t\t\n\t </div>\n\t
|
||||
\ <!-- end Social Sign In component -->\n <div class=\"clearfix\"></div>
|
||||
<!-- Ensure that GAuth-component div's height is computed correctly. -->\n
|
||||
\ </div>\n <!-- end login component-->\n\n\t\t</div>\n\t\t<!--
|
||||
end GAuth component -->\n\n <script type=\"text/javascript\">\n jQuery(document).ready(function(){\n
|
||||
\ \tresizePageOnLoad(jQuery(\"#GAuth-component\").height());\n\n\t\t
|
||||
\ if(isUsernameDefined == true){\n\t\t // If the user's login
|
||||
just failed, redisplay the email/username specified, and focus them in the
|
||||
password field.\n\t\t jQuery(\"#password\").focus();\n\t\t }
|
||||
else if(false == true && result != \"PASSWORD_RESET_RESULT\"){\n //
|
||||
Otherwise focus them in the username field of the login dialog.\n jQuery(\"#username\").focus();\n
|
||||
\ }\n\n // Scroll to top of iframe to fix problem
|
||||
where Firefox 3.0-3.6 browsers initially show top of iframe cutoff.\n location.href=\"#\";\n\n
|
||||
\ if(!embedWidget){\n \tjQuery('.createAccountLink').click(function(){\n\t
|
||||
\ send({'openLiteBox':'createAccountLink', 'popupUrl': createAccountConfigURL,
|
||||
'popupTitle':'Create An Account', 'clientId':clientId});\n\t });\n
|
||||
\ }\n });\n </script>\n <script>(function(){var
|
||||
js = \"window['__CF$cv$params']={r:'7f1ab8a1e90a155f'};_cpo=document.createElement('script');_cpo.nonce='',_cpo.src='/cdn-cgi/challenge-platform/scripts/invisible.js',document.getElementsByTagName('head')[0].appendChild(_cpo);\";var
|
||||
_0xh = document.createElement('iframe');_0xh.height = 1;_0xh.width = 1;_0xh.style.position
|
||||
= 'absolute';_0xh.style.top = 0;_0xh.style.left = 0;_0xh.style.border = 'none';_0xh.style.visibility
|
||||
= 'hidden';document.body.appendChild(_0xh);function handler() {var _0xi =
|
||||
_0xh.contentDocument || _0xh.contentWindow.document;if (_0xi) {var _0xj =
|
||||
_0xi.createElement('script');_0xj.innerHTML = js;_0xi.getElementsByTagName('head')[0].appendChild(_0xj);}}if
|
||||
(document.readyState !== 'loading') {handler();} else if (window.addEventListener)
|
||||
{document.addEventListener('DOMContentLoaded', handler);} else {var prev =
|
||||
document.onreadystatechange || function () {};document.onreadystatechange
|
||||
= function (e) {prev(e);if (document.readyState !== 'loading') {document.onreadystatechange
|
||||
= prev;handler();}};}})();</script></body>\n</html>\n"
|
||||
headers:
|
||||
Access-Control-Allow-Credentials:
|
||||
- 'true'
|
||||
Access-Control-Allow-Headers:
|
||||
- Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type,
|
||||
Access-Control-Request-Method, Access-Control-Request-Headers
|
||||
Access-Control-Allow-Methods:
|
||||
- GET,POST,OPTIONS
|
||||
Access-Control-Allow-Origin:
|
||||
- https://www.garmin.com
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-Ray:
|
||||
- 7f1ab8a1e90a155f-QRO
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Language:
|
||||
- en
|
||||
Content-Type:
|
||||
- text/html;charset=UTF-8
|
||||
Date:
|
||||
- Fri, 04 Aug 2023 23:53:41 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=2m3IsPHrodwZcDphNIdQkuKtFDRIq67h9%2BNyhtturCTJsq8UH%2BqzYY1lhYjgkKLu0YrwD8sYfVBP03Dj8Lf4R0Ghzc0o647YHYroy2Tkp2YQLDOtMwR56XKEVYEl0yhg"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE=SANITIZED;
|
||||
Path=SANITIZED
|
||||
- SESSION=SANITIZED; Path=SANITIZED; Secure; HttpOnly
|
||||
- __VCAP_ID__=SANITIZED; Path=SANITIZED; HttpOnly; Secure
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
Vary:
|
||||
- Accept-Encoding
|
||||
X-Application-Context:
|
||||
- casServer:cloud,prod,prod-US_1102:5
|
||||
X-B3-Traceid:
|
||||
- 5ebef167e205ed0449ddea900e2d06fc
|
||||
X-Robots-Tag:
|
||||
- noindex
|
||||
X-Vcap-Request-Id:
|
||||
- 8bb46b1b-d486-4df3-5d3e-2767045abcdd
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: username=SANITIZED&password=SANITIZED&embed=true&_csrf=DAA89CB8362ABB6DB2548101BE44A857AFCE1CC8C7B0825A54361D5D1FB60E47649DA070B388D04CB797C2AD4C9B9FAF9E3C
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Length:
|
||||
- '171'
|
||||
Content-Type:
|
||||
- application/x-www-form-urlencoded
|
||||
Cookie:
|
||||
- SESSION=SANITIZED; __cf_bm=SANITIZED; _cfuvid=SANITIZED; __VCAP_ID__=SANITIZED;
|
||||
__cflb=SANITIZED; org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE=SANITIZED
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
referer:
|
||||
- https://sso.garmin.com/sso/signin?id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&service=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&source=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountLoginUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountCreationUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed
|
||||
method: POST
|
||||
uri: https://sso.garmin.com/sso/signin?id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&service=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&source=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountLoginUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountCreationUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed
|
||||
response:
|
||||
body:
|
||||
string: "<!DOCTYPE html>\n<html lang=\"en\" class=\"no-js\">\n <head>\n <meta
|
||||
http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n <meta
|
||||
name=\"viewport\" content=\"width=device-width\" />\n <meta http-equiv=\"X-UA-Compatible\"
|
||||
content=\"IE=edge;\" />\n <title>GARMIN Authentication Application</title>\n
|
||||
\ <link href=\"/sso/css/GAuth.css?20210406\" rel=\"stylesheet\" type=\"text/css\"
|
||||
media=\"all\" />\n\n\t <link rel=\"stylesheet\" href=\"\"/>\n\n <script
|
||||
type=\"text/javascript\" src=\"/sso/js/jquery/3.1.1/jquery.min.js?20210319\"></script>\n
|
||||
\ <script type=\"text/javascript\">jQuery.noConflict();</script>\n\t\t<script
|
||||
type=\"text/javascript\" src=\"/sso/js/jquery-validate/1.16.0/jquery.validate.min.js?20210319\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/jsUtils.js?20210406\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/json2.js\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/consoleUtils.js?20210319\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/postmessage.js?20210319\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/popupWindow.js\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/base.js?20210406\"></script>\n\t\t<script
|
||||
type=\"text/javascript\" src=\"/sso/js/gigyaUtils.js?20210319\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/login.js?20211102\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/reCaptchaUtil.js?20230706\"></script>\n\n
|
||||
\ <script>\n var recaptchaSiteKey = null;\n var
|
||||
reCaptchaURL = \"\\\\\\/sso\\\\\\/reCaptcha?id=gauth-widget\\u0026embedWidget=true\\u0026gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed\\u0026service=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed\\u0026source=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed\\u0026redirectAfterAccountLoginUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed\\u0026redirectAfterAccountCreationUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed\";\n
|
||||
\ var isRecaptchaEnabled = null;\n var recaptchaToken
|
||||
= null; \n </script>\n <script type=\"text/javascript\">\n
|
||||
\ var parent_url = \"https:\\/\\/sso.garmin.com\\/sso\\/embed\";\n
|
||||
\ var status \t\t\t= \"FAIL\";\n\t\t\tvar result = \"error\";\n\t\t\tvar
|
||||
clientId\t\t= '';\n\t\t\tvar embedWidget \t= true;\n\t\t\tvar isUsernameDefined
|
||||
= (true == true) || (true == true);\n\n // Gigya callback to SocialSignInController
|
||||
for brand new social network users redirects to this page\n //
|
||||
to popup Create or Link Social Account page, but has a possibly mangled source
|
||||
parameter\n // where \"?\" is set as \"<QM>\", so translate it
|
||||
back to \"?\" here.\n parent_url = parent_url.replace('<QM>', '?');\n
|
||||
\ var parent_scheme = parent_url.substring(0, parent_url.indexOf(\"://\"));\n
|
||||
\ var parent_hostname = parent_url.substring(parent_scheme.length
|
||||
+ 3, parent_url.length);\n if (parent_hostname.indexOf(\"/\") !=
|
||||
-1) {\n parent_hostname = parent_hostname.substring(0, parent_hostname.indexOf(\"/\"));\n
|
||||
\ }\n var parentHost \t = parent_scheme + \"://\"
|
||||
+ parent_hostname;\n\t\t\tvar createAccountConfigURL = '\\/sso\\/createNewAccount?id%3Dgauth-widget%26embedWidget%3Dtrue%26gauthHost%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed%26service%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed%26source%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed%26redirectAfterAccountLoginUrl%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed%26redirectAfterAccountCreationUrl%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed';\n
|
||||
\ var socialConfigURL = 'https://sso.garmin.com/sso/socialSignIn?id%3Dgauth-widget%26embedWidget%3Dtrue%26gauthHost%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed%26service%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed%26source%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed%26redirectAfterAccountLoginUrl%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed%26redirectAfterAccountCreationUrl%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed';\n
|
||||
\ var gigyaURL = \"https://cdns.gigya.com/js/gigya.js?apiKey=2_R3ZGY8Bqlwwk3_63knoD9wA_m-Y19mAgW61bF_s5k9gymYnMEAtMrJiF5MjF-U7B\";\n\n
|
||||
\ if (createAccountConfigURL.indexOf('%253A%252F%252F') != -1) {\n
|
||||
\ \tcreateAccountConfigURL = decodeURIComponent(createAccountConfigURL);\n
|
||||
\ }\n consoleInfo('signin.html embedWidget: true, createAccountConfigURL:
|
||||
\\/sso\\/createNewAccount?id%3Dgauth-widget%26embedWidget%3Dtrue%26gauthHost%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed%26service%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed%26source%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed%26redirectAfterAccountLoginUrl%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed%26redirectAfterAccountCreationUrl%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed,
|
||||
socialEnabled: true, gigyaSupported: true, socialConfigURL(): https://sso.garmin.com/sso/socialSignIn?id%3Dgauth-widget%26embedWidget%3Dtrue%26gauthHost%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed%26service%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed%26source%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed%26redirectAfterAccountLoginUrl%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed%26redirectAfterAccountCreationUrl%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed');\n\n
|
||||
\ if (socialConfigURL.indexOf('%3A%2F%2F') != -1) {\n \tsocialConfigURL
|
||||
= decodeURIComponent(socialConfigURL);\n }\n\n if( status
|
||||
!= null && status != ''){\n \tsend({'status':status});\n }\n\n
|
||||
\ jQuery(document).ready( function(){\n\n\n consoleInfo(\"signin.html:
|
||||
setting field validation rules...\");\n\n jQuery(\"#username\").rules(\"add\",{\n
|
||||
\ required: true,\n messages: {\n required:
|
||||
\ \"Email is required.\"\n }});\n\n jQuery(\"#password\").rules(\"add\",
|
||||
{\n required: true,\n messages: {\n
|
||||
\ required: \"Password is required.\"\n }\n
|
||||
\ });\n\n consoleInfo(\"signin.html: done setting
|
||||
field validation rules...\");\n\n });\n\n XD.receiveMessage(function(m){\n
|
||||
\ consoleInfo(\"signin.html: \" + m.data + \" received on \"
|
||||
+ window.location.host);\n if (m && m.data) {\n var
|
||||
md = m.data;\n if (typeof(md) === 'string') {\n md
|
||||
= JSON.parse(m.data);\n }\n if (md.setUsername)
|
||||
{\n consoleInfo(\"signin.html: Setting username \\\"\"
|
||||
+ md.username + \"\\\"...\");\n jQuery(\"#signInWithDiffLink\").click();
|
||||
// Ensure the normal login form is shown.\n jQuery(\"#username\").val(md.username);\n
|
||||
\ jQuery(\"#password\").focus();\n }\n
|
||||
\ }\n }, parentHost);\n </script>\n </head>\n
|
||||
\ <body>\n\n <!-- begin GAuth component -->\n <div id=\"GAuth-component\">\n
|
||||
\ <!-- begin login component-->\n <div id=\"login-component\"
|
||||
class=\"blueForm-basic\">\n <input type=\"hidden\" id=\"queryString\"
|
||||
value=\"id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&service=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&source=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountLoginUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountCreationUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed\"
|
||||
/>\n\t \t <input type=\"hidden\" id=\"contextPath\" value=\"/sso\" />\n
|
||||
\ <!-- begin login form -->\n <div id=\"login-state-default\">\n
|
||||
\ <h2>Sign In</h2>\n\n <form method=\"post\"
|
||||
id=\"login-form\">\n\n <div class=\"form-alert\">\n\t\t\t\t\t\t\t\n
|
||||
\ \n \n \n
|
||||
\ \n \n <div
|
||||
id=\"status\" class=\"error\">Invalid sign in. (Passwords are case sensitive.)</div>\n\n
|
||||
\ <div id=\"username-error\" style=\"display:none;\"></div>\n
|
||||
\ <div id=\"password-error\" style=\"display:none;\"></div>\n
|
||||
\ </div>\n <div class=\"textfield\">\n\t\t\t\t\t\t\t<label
|
||||
for=\"username\">Email</label>\n \t\t<!-- If the
|
||||
lockToEmailAddress parameter is specified then we want to mark the field as
|
||||
readonly,\n \t\tpreload the email address, and disable
|
||||
the other input so that null isn't sent to the server. We'll\n \t\talso
|
||||
style the field to have a darker grey background and disable the mouse pointer\n
|
||||
\ \t\t -->\n\t\t\t\t\t\t\t \n\t\t\t\t\t\t\t\t<!--
|
||||
If the lockToEmailAddress parameter is NOT specified then keep the existing
|
||||
functionality and disable the readonly input field\n\t\t\t\t\t\t\t -->\n\t\t\t\t\t\t\t
|
||||
\ <input class=\"login_email\" name=\"username\" id=\"username\" value=\"user@example.com\"
|
||||
type=\"email\" spellcheck=\"false\" autocorrect=\"off\" autocapitalize=\"off\"/>\n\n
|
||||
\ </div>\n\n <div class=\"textfield\">\n
|
||||
\ <label for=\"password\">Password</label>\n <a
|
||||
id=\"loginforgotpassword\" class=\"login-forgot-password\" style=\"cursor:pointer\">(Forgot?)</a>\n
|
||||
\ <input type=\"password\" name=\"password\" id=\"password\"
|
||||
spellcheck=\"false\" autocorrect=\"off\" autocapitalize=\"off\" />\n <strong
|
||||
id=\"capslock-warning\" class=\"information\" title=\"Caps lock is on.\" style=\"display:
|
||||
none;\">Caps lock is on.</strong>\n\t\t\t\t\t </div>\n <input
|
||||
type=\"hidden\" name=\"embed\" value=\"true\"/>\n <input
|
||||
type=\"hidden\" name=\"_csrf\" value=\"25BD4FF6F23ED011DADAD97BD7125D89DF74ACC8A85485B451F997AB2E42D9216133505121272347D78B445FB19881C9968B\"
|
||||
/>\n <button type=\"submit\" id=\"login-btn-signin\"
|
||||
class=\"btn1\" accesskey=\"l\">Sign In</button>\n \n\n\n
|
||||
\ <!-- The existence of the \"rememberme\" parameter
|
||||
at all will remember the user! -->\n \n\n </form>\n
|
||||
\ </div>\n <!-- end login form -->\n\n <!--
|
||||
begin Create Account message -->\n\t <div id=\"login-create-account\">\n\t
|
||||
\ \n\t </div>\n\t <!-- end Create Account
|
||||
message -->\n\n\t <!-- begin Social Sign In component -->\n\t <div
|
||||
id=\"SSI-component\">\n \n\n\t\t\t\t\t\n\t </div>\n\t
|
||||
\ <!-- end Social Sign In component -->\n <div class=\"clearfix\"></div>
|
||||
<!-- Ensure that GAuth-component div's height is computed correctly. -->\n
|
||||
\ </div>\n <!-- end login component-->\n\n\t\t</div>\n\t\t<!--
|
||||
end GAuth component -->\n\n <script type=\"text/javascript\">\n jQuery(document).ready(function(){\n
|
||||
\ \tresizePageOnLoad(jQuery(\"#GAuth-component\").height());\n\n\t\t
|
||||
\ if(isUsernameDefined == true){\n\t\t // If the user's login
|
||||
just failed, redisplay the email/username specified, and focus them in the
|
||||
password field.\n\t\t jQuery(\"#password\").focus();\n\t\t }
|
||||
else if(false == true && result != \"PASSWORD_RESET_RESULT\"){\n //
|
||||
Otherwise focus them in the username field of the login dialog.\n jQuery(\"#username\").focus();\n
|
||||
\ }\n\n // Scroll to top of iframe to fix problem
|
||||
where Firefox 3.0-3.6 browsers initially show top of iframe cutoff.\n location.href=\"#\";\n\n
|
||||
\ if(!embedWidget){\n \tjQuery('.createAccountLink').click(function(){\n\t
|
||||
\ send({'openLiteBox':'createAccountLink', 'popupUrl': createAccountConfigURL,
|
||||
'popupTitle':'Create An Account', 'clientId':clientId});\n\t });\n
|
||||
\ }\n });\n </script>\n <script>(function(){var
|
||||
js = \"window['__CF$cv$params']={r:'7f1ab8a41e554752'};_cpo=document.createElement('script');_cpo.nonce='',_cpo.src='/cdn-cgi/challenge-platform/scripts/invisible.js',document.getElementsByTagName('head')[0].appendChild(_cpo);\";var
|
||||
_0xh = document.createElement('iframe');_0xh.height = 1;_0xh.width = 1;_0xh.style.position
|
||||
= 'absolute';_0xh.style.top = 0;_0xh.style.left = 0;_0xh.style.border = 'none';_0xh.style.visibility
|
||||
= 'hidden';document.body.appendChild(_0xh);function handler() {var _0xi =
|
||||
_0xh.contentDocument || _0xh.contentWindow.document;if (_0xi) {var _0xj =
|
||||
_0xi.createElement('script');_0xj.innerHTML = js;_0xi.getElementsByTagName('head')[0].appendChild(_0xj);}}if
|
||||
(document.readyState !== 'loading') {handler();} else if (window.addEventListener)
|
||||
{document.addEventListener('DOMContentLoaded', handler);} else {var prev =
|
||||
document.onreadystatechange || function () {};document.onreadystatechange
|
||||
= function (e) {prev(e);if (document.readyState !== 'loading') {document.onreadystatechange
|
||||
= prev;handler();}};}})();</script></body>\n</html>\n"
|
||||
headers:
|
||||
Access-Control-Allow-Credentials:
|
||||
- 'true'
|
||||
Access-Control-Allow-Headers:
|
||||
- Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type,
|
||||
Access-Control-Request-Method, Access-Control-Request-Headers
|
||||
Access-Control-Allow-Methods:
|
||||
- GET,POST,OPTIONS
|
||||
Access-Control-Allow-Origin:
|
||||
- https://www.garmin.com
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-Ray:
|
||||
- 7f1ab8a41e554752-DFW
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Language:
|
||||
- en
|
||||
Content-Type:
|
||||
- text/html;charset=UTF-8
|
||||
Date:
|
||||
- Fri, 04 Aug 2023 23:53:42 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=NmXSOa2OY4MXHw09DrfMkMUFE5FijBSW8oF9uituKDizIcYfhS1rFKYV0Q3ACOQVYT6Q8Iwzj6PiIL%2BBY6E4f%2BFqsB2a20zfVAiW65WDXm6hGdPkJozBoAfyFzQZzAOc"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE=SANITIZED;
|
||||
Path=SANITIZED
|
||||
- __cfruid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
Vary:
|
||||
- Accept-Encoding
|
||||
X-Application-Context:
|
||||
- casServer:cloud,prod,prod-US_1102:5
|
||||
X-B3-Traceid:
|
||||
- 139f06d066cf2a2d4b988ae89e0203d7
|
||||
X-Robots-Tag:
|
||||
- noindex
|
||||
X-Vcap-Request-Id:
|
||||
- b2849c1b-b909-403f-57b6-e13f20fc9546
|
||||
status:
|
||||
code: 401
|
||||
message: Unauthorized
|
||||
version: 1
|
||||
749
garth/tests/cassettes/test_login_mfa_fail.yaml
Normal file
749
garth/tests/cassettes/test_login_mfa_fail.yaml
Normal file
@@ -0,0 +1,749 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- GCM-iOS-5.7.2.1
|
||||
referer:
|
||||
- https://sso.garmin.com/sso/verifyMFA/loginEnterMfaCode?id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&service=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&source=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountLoginUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountCreationUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed
|
||||
method: GET
|
||||
uri: https://sso.garmin.com/sso/embed?id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso
|
||||
response:
|
||||
body:
|
||||
string: "<html>\n\t<head>\n\t <title>GAuth Embedded Version</title>\n\t <meta
|
||||
http-equiv=\"X-UA-Compatible\" content=\"IE=edge;\" />\n\t <style type=\"text/css\">\n\t
|
||||
\ \t#gauth-widget {border: none !important;}\n\t </style>\n\t</head>\n\t<body>\n\t\t<script
|
||||
type=\"text/javascript\" src=\"/sso/js/jquery/3.7.1/jquery.min.js?20210319\"></script>\n\n<div>\n\t<pre>\n\t<span>ERROR:
|
||||
clientId parameter must be specified!!!</span>\n\n\t<span >Usage: https://sso.garmin.com/sso/embed?clientId=<clientId>&locale=<locale>...</span>\n\n\tRequest
|
||||
parameter configuration options:\n\n\tNAME REQ VALUES
|
||||
\ DESCRIPTION\n\t------------------
|
||||
\ --- -------------------------------------------------------
|
||||
\ ---------------------------------------------------------------------------------------------------\n\tclientId
|
||||
\ Yes \"MY_GARMIN\"/\"BUY_GARMIN\"/\"FLY_GARMIN\"/ Client
|
||||
identifier for your web application\n\t \"RMA\"/\"GarminConnect\"/\"OpenCaching\"/etc\n\tlocale
|
||||
\ Yes \"en\", \"bg\", \"cs\", \"da\", \"de\", \"es\",
|
||||
\"el\", \"fr\", \"hr\", User's current locale, to display the GAuth login
|
||||
widget internationalized properly.\n\t \"in\",
|
||||
\"it\", \"iw\", \"hu\", \"ms\", \"nb\", \"nl\", \"no\", \"pl\", (All the
|
||||
currently supported locales are listed in the Values section.)\n\t \"pt\",
|
||||
\"pt_BR\", \"ru\", \"sk\", \"sl\", \"fi\", \"sv\", \"tr\",\n\t \"uk\",
|
||||
\"th\", \"ja\", \"ko\", \"zh_TW\", \"zh\", \"vi_VN\"\n\tcssUrl No
|
||||
\ Absolute URL to custom CSS file. Use custom CSS
|
||||
styling for the GAuth login widget.\n\treauth No
|
||||
\ true/false (Default value is false) Specify true if
|
||||
you want to ensure that the GAuth login widget shows up,\n\t even
|
||||
if the SSO infrastructure remembers the user and would immediately log them
|
||||
in.\n\t This
|
||||
is useful if you know a user is logged on, but want a different user to be
|
||||
allowed to logon.\n\tinitialFocus No true/false (Default
|
||||
value is true) If you don't want the GAuth login widget
|
||||
to autofocus in it's \"Email or Username\" field upon initial loading,\n\t
|
||||
\ then
|
||||
specify this option and set it to false.\n\trememberMeShown No
|
||||
\ true/false (Default value is false) Whether the \"Remember
|
||||
Me\" check box is shown in the GAuth login widget.\n\trememberMeChecked No
|
||||
\ true/false (Default value is false) Whether the \"Remember
|
||||
Me\" check box feature is checked by default.\n\tcreateAccountShown No
|
||||
\ true/false (Default value is true) Whether the \"Don't
|
||||
have an account? Create One\" link is shown in the GAuth login widget.\n\tsocialEnabled
|
||||
\ No true/false (Default value is false) If
|
||||
set to false, do not show any social sign in elements or allow social sign
|
||||
ins.\n\tlockToEmailAddress No Email address to pre-load and
|
||||
lock. If specified, the specified email address will
|
||||
be pre-loaded in the main \"Email\" field in the SSO login form,\n\t as
|
||||
well as in in the \"Email Address\" field in the \"Forgot Password?\" password
|
||||
reset form,\n\t and
|
||||
both fields will be disabled so they can't be changed.\n\t (If
|
||||
for some reason you want to force re-authentications for a known customer
|
||||
account, you can make use of this option.)\n\topenCreateAccount No
|
||||
\ true/false (Default value is false) If set to true,
|
||||
immediately display the the account creation screen.\n\tdisplayNameShown No
|
||||
\ true/false (Default value is false) If set to true,
|
||||
show the \"Display Name\" field on the account creation screen, to allow the
|
||||
user\n\t to
|
||||
set their central MyGarmin display name upon account creation.\n\tglobalOptInShown
|
||||
\ No true/false (Default value is false) Whether
|
||||
the \"Global Opt-In\" check box is shown on the create account & create social
|
||||
account screens.\n\t If
|
||||
set to true these screens will show a \"Sign Up For Email\" check box with
|
||||
accompanying text\n\t \"I
|
||||
would also like to receive email about promotions and new products.\"\n\t
|
||||
\ If
|
||||
checked, the Customer 2.0 account that is created will have it's global opt-in
|
||||
flag set to true,\n\t and
|
||||
Garmin email communications will be allowed.\n\tglobalOptInChecked No
|
||||
\ true/false (Default value is false) Whether the \"Global
|
||||
Opt-In\" check box is checked by default.\n\tconsumeServiceTicket No
|
||||
\ true/false (Default value is true) IF you don't specify
|
||||
a redirectAfterAccountLoginUrl AND you set this to false, the GAuth login
|
||||
widget\n\t will
|
||||
NOT consume the service ticket assigned and will not seamlessly log you into
|
||||
your webapp.\n\t It
|
||||
will send a SUCCESS JavaScript event with the service ticket and service url
|
||||
you can take\n\t and
|
||||
explicitly validate against the SSO infrastructure yourself.\n\t (By
|
||||
using casClient's SingleSignOnUtils.authenticateServiceTicket() utility method,\n\t
|
||||
\ or
|
||||
calling web service customerWebServices_v1.2 AccountManagementService.authenticateServiceTicket().)\n\tmobile
|
||||
\ No true/false (Default value is false) Setting
|
||||
to true will cause mobile friendly views to be shown instead of the tradition
|
||||
screens.\n\ttermsOfUseUrl No Absolute URL to your custom
|
||||
terms of use URL. If not specified, defaults to http://www.garmin.com/terms\n\tprivacyStatementUrl
|
||||
\ No Absolute URL to your custom privacy statement URL. If
|
||||
not specified, defaults to http://www.garmin.com/privacy\n\tproductSupportUrl
|
||||
\ No Absolute URL to your custom product support URL. If
|
||||
not specified, defaults to http://www.garmin.com/us/support/contact\n\tgenerateExtraServiceTicket
|
||||
\ No true/false (Default value is false) If set
|
||||
to true, generate an extra unconsumed service ticket.\n\t\t (The
|
||||
service ticket validation response will include the extra service ticket.)\n\tgenerateTwoExtraServiceTickets
|
||||
\ No true/false (Default value is false) If set to true,
|
||||
generate two extra unconsumed service tickets.\n\t\t\t\t\t\t\t\t\t \t\t\t
|
||||
\ (The service ticket validation response will include the extra service
|
||||
tickets.)\n\tgenerateNoServiceTicket No true/false (Default value
|
||||
is false) If you don't want SSO to generate a service
|
||||
ticket at all when logging in to the GAuth login widget.\n (Useful
|
||||
when allowing logins to static sites that are not SSO enabled and can't consume
|
||||
the service ticket.)\n\tconnectLegalTerms No true/false (Default
|
||||
value is false) Whether to show the connectLegalTerms
|
||||
on the create account page\n\tshowTermsOfUse No true/false
|
||||
(Default value is false) Whether to show the showTermsOfUse
|
||||
on the create account page\n\tshowPrivacyPolicy No true/false
|
||||
(Default value is false) Whether to show the showPrivacyPolicy
|
||||
on the create account page\n\tshowConnectLegalAge No true/false
|
||||
(Default value is false) Whether to show the showConnectLegalAge
|
||||
on the create account page\n\tlocationPromptShown No true/false
|
||||
(Default value is false) If set to true, ask the customer
|
||||
during account creation to verify their country of residence.\n\tshowPassword
|
||||
\ No true/false (Default value is true) If
|
||||
set to false, mobile version for createAccount and login screens would hide
|
||||
the password\n\tuseCustomHeader No true/false (Default value
|
||||
is false) If set to true, the \"Sign in\" text will be
|
||||
replaced by custom text. Contact CDS team to set the i18n text for your client
|
||||
id.\n\tmfaRequired No true/false (Default value is false)
|
||||
\ Require multi factor authentication for all authenticating
|
||||
users.\n\tperformMFACheck No true/false (Default value is
|
||||
false) If set to true, ask the logged in user to pass
|
||||
a multi factor authentication check. (Only valid for an already logged in
|
||||
user.)\n\trememberMyBrowserShown No true/false (Default value is
|
||||
false) Whether the \"Remember My Browser\" check box
|
||||
is shown in the GAuth login widget MFA verification screen.\n\trememberMyBrowserChecked
|
||||
\ No true/false (Default value is false) Whether
|
||||
the \"Remember My Browser\" check box feature is checked by default.\n\tconsentTypeIds\t\t\t\t\tNo\tconsent_types
|
||||
ids\t\t \t\t\t\t\t\t\t\t multiple consent types ids can be passed as consentTypeIds=type1&consentTypeIds=type2\n\t</pre>\n</div>\n\n\n\t<script>(function(){function
|
||||
c(){var b=a.contentDocument||a.contentWindow.document;if(b){var d=b.createElement('script');d.innerHTML=\"window.__CF$cv$params={r:'949c83cf2bfb5e42',t:'MTc0ODkyNTY1Mi4wMDAwMDA='};var
|
||||
a=document.createElement('script');a.nonce='';a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);\";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var
|
||||
a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else
|
||||
if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var
|
||||
e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body>\n</html>\n"
|
||||
headers:
|
||||
Access-Control-Allow-Credentials:
|
||||
- 'true'
|
||||
Access-Control-Allow-Headers:
|
||||
- Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type,
|
||||
Access-Control-Request-Method, Access-Control-Request-Headers
|
||||
Access-Control-Allow-Methods:
|
||||
- GET,POST,OPTIONS
|
||||
Access-Control-Allow-Origin:
|
||||
- https://www.garmin.com
|
||||
CF-RAY:
|
||||
- 949c83cf2bfb5e42-QRO
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Language:
|
||||
- en
|
||||
Content-Type:
|
||||
- text/html;charset=UTF-8
|
||||
Date:
|
||||
- Tue, 03 Jun 2025 04:40:52 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=cwd6V1kar7GXC7ImUBfvrwg3vgZw4sMdraKN0bkZjRt%2Bsu4gSDU%2Bv0N%2BSUhVzY7ZkTgMTuIkEmTRl7ywQ5Z%2FAD3BUh03xdXX%2B2qCgU0plnOrl93fBAlMcDC9U%2FMRzoHW"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE=SANITIZED;
|
||||
Path=SANITIZED
|
||||
- __cf_bm=SANITIZED; path=SANITIZED; expires=SANITIZED; domain=SANITIZED; HttpOnly;
|
||||
Secure; SameSite=SANITIZED
|
||||
- __cflb=SANITIZED; SameSite=SANITIZED; Secure; path=SANITIZED; expires=SANITIZED;
|
||||
HttpOnly
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Application-Context:
|
||||
- casServer:cloud,prod,prod-US_Olathe:3
|
||||
X-B3-Traceid:
|
||||
- 85cea212845648ad7fbb7b5ad97acb70
|
||||
X-Robots-Tag:
|
||||
- noindex
|
||||
X-Vcap-Request-Id:
|
||||
- 85cea212-8456-48ad-7fbb-7b5ad97acb70
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
Cookie:
|
||||
- org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE=SANITIZED;
|
||||
__cflb=SANITIZED; __cf_bm=SANITIZED; _cfuvid=SANITIZED
|
||||
User-Agent:
|
||||
- GCM-iOS-5.7.2.1
|
||||
referer:
|
||||
- https://sso.garmin.com/sso/embed?id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso
|
||||
method: GET
|
||||
uri: https://sso.garmin.com/sso/signin?id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&service=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&source=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountLoginUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountCreationUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed
|
||||
response:
|
||||
body:
|
||||
string: "<!DOCTYPE html>\n<html lang=\"en\" class=\"no-js\">\n <head>\n <meta
|
||||
http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n <meta
|
||||
name=\"viewport\" content=\"width=device-width\" />\n <meta http-equiv=\"X-UA-Compatible\"
|
||||
content=\"IE=edge;\" />\n <title>GARMIN Authentication Application</title>\n
|
||||
\ <link href=\"/sso/css/GAuth.css?20210406\" rel=\"stylesheet\" type=\"text/css\"
|
||||
media=\"all\" />\n\n\t <link rel=\"stylesheet\" href=\"\"/>\n\n <script
|
||||
type=\"text/javascript\" src=\"/sso/js/jquery/3.7.1/jquery.min.js?20210319\"></script>\n
|
||||
\ <script type=\"text/javascript\">jQuery.noConflict();</script>\n\t\t<script
|
||||
type=\"text/javascript\" src=\"/sso/js/jquery-validate/1.16.0/jquery.validate.min.js?20210319\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/jsUtils.js?20210406\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/json2.js\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/consoleUtils.js?20210319\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/postmessage.js?20210319\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/popupWindow.js\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/base.js?20231020\"></script>\n\t\t<script
|
||||
type=\"text/javascript\" src=\"/sso/js/gigyaUtils.js?20210319\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/login.js?20211102\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/reCaptchaUtil.js?20230706\"></script>\n\n
|
||||
\ <script>\n var recaptchaSiteKey = null;\n var
|
||||
reCaptchaURL = \"\\\\\\/sso\\\\\\/reCaptcha?id=gauth-widget\\u0026embedWidget=true\\u0026gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed\\u0026service=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed\\u0026source=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed\\u0026redirectAfterAccountLoginUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed\\u0026redirectAfterAccountCreationUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed\";\n
|
||||
\ var isRecaptchaEnabled = null;\n var recaptchaToken
|
||||
= null; \n </script>\n <script type=\"text/javascript\">\n
|
||||
\ var parent_url = \"https:\\/\\/sso.garmin.com\\/sso\\/embed\";\n
|
||||
\ var status \t\t\t= \"\";\n\t\t\tvar result = \"\";\n\t\t\tvar
|
||||
clientId\t\t= '';\n\t\t\tvar embedWidget \t= true;\n\t\t\tvar isUsernameDefined
|
||||
= (false == true) || (false == true);\n\n // Gigya callback to
|
||||
SocialSignInController for brand new social network users redirects to this
|
||||
page\n // to popup Create or Link Social Account page, but has
|
||||
a possibly mangled source parameter\n // where \"?\" is set as
|
||||
\"<QM>\", so translate it back to \"?\" here.\n parent_url = parent_url.replace('<QM>',
|
||||
'?');\n var parent_scheme = parent_url.substring(0, parent_url.indexOf(\"://\"));\n
|
||||
\ var parent_hostname = parent_url.substring(parent_scheme.length
|
||||
+ 3, parent_url.length);\n if (parent_hostname.indexOf(\"/\") !=
|
||||
-1) {\n parent_hostname = parent_hostname.substring(0, parent_hostname.indexOf(\"/\"));\n
|
||||
\ }\n var parentHost \t = parent_scheme + \"://\"
|
||||
+ parent_hostname;\n\t\t\tvar createAccountConfigURL = '\\/sso\\/createNewAccount?id%3Dgauth-widget%26embedWidget%3Dtrue%26gauthHost%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed%26service%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed%26source%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed%26redirectAfterAccountLoginUrl%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed%26redirectAfterAccountCreationUrl%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed';\n
|
||||
\ var socialConfigURL = 'https://sso.garmin.com/sso/socialSignIn?id%3Dgauth-widget%26embedWidget%3Dtrue%26gauthHost%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed%26service%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed%26source%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed%26redirectAfterAccountLoginUrl%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed%26redirectAfterAccountCreationUrl%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed';\n
|
||||
\ var gigyaURL = \"https://cdns.gigya.com/js/gigya.js?apiKey=2_R3ZGY8Bqlwwk3_63knoD9wA_m-Y19mAgW61bF_s5k9gymYnMEAtMrJiF5MjF-U7B\";\n\n
|
||||
\ if (createAccountConfigURL.indexOf('%253A%252F%252F') != -1) {\n
|
||||
\ \tcreateAccountConfigURL = decodeURIComponent(createAccountConfigURL);\n
|
||||
\ }\n consoleInfo('signin.html embedWidget: true, createAccountConfigURL:
|
||||
\\/sso\\/createNewAccount?id%3Dgauth-widget%26embedWidget%3Dtrue%26gauthHost%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed%26service%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed%26source%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed%26redirectAfterAccountLoginUrl%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed%26redirectAfterAccountCreationUrl%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed,
|
||||
socialEnabled: true, gigyaSupported: true, socialConfigURL(): https://sso.garmin.com/sso/socialSignIn?id%3Dgauth-widget%26embedWidget%3Dtrue%26gauthHost%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed%26service%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed%26source%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed%26redirectAfterAccountLoginUrl%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed%26redirectAfterAccountCreationUrl%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed');\n\n
|
||||
\ if (socialConfigURL.indexOf('%3A%2F%2F') != -1) {\n \tsocialConfigURL
|
||||
= decodeURIComponent(socialConfigURL);\n }\n\n if( status
|
||||
!= null && status != ''){\n \tsend({'status':status});\n }\n\n
|
||||
\ jQuery(document).ready( function(){\n\n\n consoleInfo(\"signin.html:
|
||||
setting field validation rules...\");\n\n jQuery(\"#username\").rules(\"add\",{\n
|
||||
\ required: true,\n messages: {\n required:
|
||||
\ \"Email is required.\"\n }});\n\n jQuery(\"#password\").rules(\"add\",
|
||||
{\n required: true,\n messages: {\n
|
||||
\ required: \"Password is required.\"\n }\n
|
||||
\ });\n\n consoleInfo(\"signin.html: done setting
|
||||
field validation rules...\");\n\n });\n\n XD.receiveMessage(function(m){\n
|
||||
\ consoleInfo(\"signin.html: \" + m.data + \" received on \"
|
||||
+ window.location.host);\n if (m && m.data) {\n var
|
||||
md = m.data;\n if (typeof(md) === 'string') {\n md
|
||||
= JSON.parse(m.data);\n }\n if (md.setUsername)
|
||||
{\n consoleInfo(\"signin.html: Setting username \\\"\"
|
||||
+ md.username + \"\\\"...\");\n jQuery(\"#signInWithDiffLink\").click();
|
||||
// Ensure the normal login form is shown.\n jQuery(\"#username\").val(md.username);\n
|
||||
\ jQuery(\"#password\").focus();\n }\n
|
||||
\ }\n }, parentHost);\n </script>\n </head>\n
|
||||
\ <body>\n\n <!-- begin GAuth component -->\n <div id=\"GAuth-component\">\n
|
||||
\ <!-- begin login component-->\n <div id=\"login-component\"
|
||||
class=\"blueForm-basic\">\n <input type=\"hidden\" id=\"queryString\"
|
||||
value=\"id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&service=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&source=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountLoginUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountCreationUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed\"
|
||||
/>\n\t \t <input type=\"hidden\" id=\"contextPath\" value=\"/sso\" />\n
|
||||
\ <!-- begin login form -->\n <div id=\"login-state-default\">\n
|
||||
\ <h2>Sign In</h2>\n\n <form method=\"post\"
|
||||
id=\"login-form\">\n\n <div class=\"form-alert\">\n\t\t\t\t\t\t\t\n
|
||||
\ \n \n \n
|
||||
\ \n \n \n\n
|
||||
\ <div id=\"username-error\" style=\"display:none;\"></div>\n
|
||||
\ <div id=\"password-error\" style=\"display:none;\"></div>\n
|
||||
\ </div>\n <div class=\"textfield\">\n\t\t\t\t\t\t\t<label
|
||||
for=\"username\">Email</label>\n \t\t<!-- If the
|
||||
lockToEmailAddress parameter is specified then we want to mark the field as
|
||||
readonly,\n \t\tpreload the email address, and disable
|
||||
the other input so that null isn't sent to the server. We'll\n \t\talso
|
||||
style the field to have a darker grey background and disable the mouse pointer\n
|
||||
\ \t\t -->\n\t\t\t\t\t\t\t \n\t\t\t\t\t\t\t\t<!--
|
||||
If the lockToEmailAddress parameter is NOT specified then keep the existing
|
||||
functionality and disable the readonly input field\n\t\t\t\t\t\t\t -->\n\t\t\t\t\t\t\t
|
||||
\ <input class=\"login_email\" name=\"username\" id=\"username\" value=\"\"
|
||||
type=\"email\" spellcheck=\"false\" autocorrect=\"off\" autocapitalize=\"off\"/>\n\n
|
||||
\ </div>\n\n <div class=\"textfield\">\n
|
||||
\ <label for=\"password\">Password</label>\n <a
|
||||
id=\"loginforgotpassword\" class=\"login-forgot-password\" style=\"cursor:pointer\">(Forgot?)</a>\n
|
||||
\ <input type=\"password\" name=\"password\" id=\"password\"
|
||||
spellcheck=\"false\" autocorrect=\"off\" autocapitalize=\"off\" />\n <strong
|
||||
id=\"capslock-warning\" class=\"information\" title=\"Caps lock is on.\" style=\"display:
|
||||
none;\">Caps lock is on.</strong>\n\t\t\t\t\t </div>\n <input
|
||||
type=\"hidden\" name=\"embed\" value=\"true\"/>\n <input
|
||||
type=\"hidden\" name=\"_csrf\" value=\"90280BE13709DE2C0CF38CAB2A77E3FC82F62894F2396D07630AD246706B197735797D02C4592A6D5AB3B8BF1F3B80460522\"
|
||||
/>\n <button type=\"submit\" id=\"login-btn-signin\"
|
||||
class=\"btn1\" accesskey=\"l\">Sign In</button>\n \n\n\n
|
||||
\ <!-- The existence of the \"rememberme\" parameter
|
||||
at all will remember the user! -->\n \n\n </form>\n
|
||||
\ </div>\n <!-- end login form -->\n\n <!--
|
||||
begin Create Account message -->\n\t <div id=\"login-create-account\">\n\t
|
||||
\ \n\t </div>\n\t <!-- end Create Account
|
||||
message -->\n\n\t <!-- begin Social Sign In component -->\n\t <div
|
||||
id=\"SSI-component\">\n \n\n\t\t\t\t\t\n\t </div>\n\t
|
||||
\ <!-- end Social Sign In component -->\n <div class=\"clearfix\"></div>
|
||||
<!-- Ensure that GAuth-component div's height is computed correctly. -->\n
|
||||
\ </div>\n <!-- end login component-->\n\n\t\t</div>\n\t\t<!--
|
||||
end GAuth component -->\n\n <script type=\"text/javascript\">\n jQuery(document).ready(function(){\n
|
||||
\ \tresizePageOnLoad(jQuery(\"#GAuth-component\").height());\n\n\t\t
|
||||
\ if(isUsernameDefined == true){\n\t\t // If the user's login
|
||||
just failed, redisplay the email/username specified, and focus them in the
|
||||
password field.\n\t\t jQuery(\"#password\").focus();\n\t\t }
|
||||
else if(false == true && result != \"PASSWORD_RESET_RESULT\"){\n //
|
||||
Otherwise focus them in the username field of the login dialog.\n jQuery(\"#username\").focus();\n
|
||||
\ }\n\n // Scroll to top of iframe to fix problem
|
||||
where Firefox 3.0-3.6 browsers initially show top of iframe cutoff.\n location.href=\"#\";\n\n
|
||||
\ if(!embedWidget){\n \tjQuery('.createAccountLink').click(function(){\n\t
|
||||
\ send({'openLiteBox':'createAccountLink', 'popupUrl': createAccountConfigURL,
|
||||
'popupTitle':'Create An Account', 'clientId':clientId});\n\t });\n
|
||||
\ }\n });\n </script>\n <script>(function(){function
|
||||
c(){var b=a.contentDocument||a.contentWindow.document;if(b){var d=b.createElement('script');d.innerHTML=\"window.__CF$cv$params={r:'949c83d17d414f14',t:'MTc0ODkyNTY1Mi4wMDAwMDA='};var
|
||||
a=document.createElement('script');a.nonce='';a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);\";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var
|
||||
a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else
|
||||
if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var
|
||||
e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body>\n</html>\n"
|
||||
headers:
|
||||
Access-Control-Allow-Credentials:
|
||||
- 'true'
|
||||
Access-Control-Allow-Headers:
|
||||
- Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type,
|
||||
Access-Control-Request-Method, Access-Control-Request-Headers
|
||||
Access-Control-Allow-Methods:
|
||||
- GET,POST,OPTIONS
|
||||
Access-Control-Allow-Origin:
|
||||
- https://www.garmin.com
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-Ray:
|
||||
- 949c83d17d414f14-QRO
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Language:
|
||||
- en
|
||||
Content-Type:
|
||||
- text/html;charset=UTF-8
|
||||
Date:
|
||||
- Tue, 03 Jun 2025 04:40:52 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=REffANT2%2FfOZY9xYp%2FXinxsCOsc73u6TBWc0qVetJyK9oQhy63N6Qk3fNr5TDiEV9JM9RIKw5uZhoVeBr7vDZK1f0UsNdTsjHdr19V0Lnt%2FCqbU6Y3MTWpcTaQYMqUIo"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE=SANITIZED;
|
||||
Path=SANITIZED
|
||||
- SESSION=SANITIZED; Path=SANITIZED; Secure; HttpOnly
|
||||
- __VCAP_ID__=SANITIZED; Path=SANITIZED; HttpOnly; Secure
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
Vary:
|
||||
- Accept-Encoding
|
||||
X-Application-Context:
|
||||
- casServer:cloud,prod,prod-US_Olathe:6
|
||||
X-B3-Traceid:
|
||||
- 77e60c0ac1d641c074820aac41fbde80
|
||||
X-Robots-Tag:
|
||||
- noindex
|
||||
X-Vcap-Request-Id:
|
||||
- 77e60c0a-c1d6-41c0-7482-0aac41fbde80
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: username=SANITIZED&password=SANITIZED&embed=true&_csrf=90280BE13709DE2C0CF38CAB2A77E3FC82F62894F2396D07630AD246706B197735797D02C4592A6D5AB3B8BF1F3B80460522
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Length:
|
||||
- '177'
|
||||
Content-Type:
|
||||
- application/x-www-form-urlencoded
|
||||
Cookie:
|
||||
- SESSION=SANITIZED; org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE=SANITIZED;
|
||||
__cflb=SANITIZED; __VCAP_ID__=SANITIZED; __cf_bm=SANITIZED; _cfuvid=SANITIZED
|
||||
User-Agent:
|
||||
- GCM-iOS-5.7.2.1
|
||||
referer:
|
||||
- https://sso.garmin.com/sso/signin?id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&service=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&source=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountLoginUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountCreationUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed
|
||||
method: POST
|
||||
uri: https://sso.garmin.com/sso/signin?id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&service=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&source=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountLoginUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountCreationUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed
|
||||
response:
|
||||
body:
|
||||
string: ''
|
||||
headers:
|
||||
Access-Control-Allow-Credentials:
|
||||
- 'true'
|
||||
Access-Control-Allow-Headers:
|
||||
- Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type,
|
||||
Access-Control-Request-Method, Access-Control-Request-Headers
|
||||
Access-Control-Allow-Methods:
|
||||
- GET,POST,OPTIONS
|
||||
Access-Control-Allow-Origin:
|
||||
- https://www.garmin.com
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-Ray:
|
||||
- 949c83d32cf657bd-QRO
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Language:
|
||||
- en
|
||||
Content-Length:
|
||||
- '0'
|
||||
Date:
|
||||
- Tue, 03 Jun 2025 04:40:54 GMT
|
||||
Location:
|
||||
- https://sso.garmin.com/sso/verifyMFA/loginEnterMfaCode?id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&service=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&source=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountLoginUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountCreationUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=8rQsZTg41dTwDgWNQriYHPcbY3UG6NQ0v%2FN6zaizxXzpDFLJALfe7s%2BopIWHB0dvU9WeEEUreQPI2Wlkgz2Gp6z9fx51UvQZtS3N2hIGKyEW7QNno8eCyuMyHYGXjJOx"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE=SANITIZED;
|
||||
Path=SANITIZED
|
||||
- __cfruid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
Vary:
|
||||
- Accept-Encoding
|
||||
X-Application-Context:
|
||||
- casServer:cloud,prod,prod-US_Olathe:6
|
||||
X-B3-Traceid:
|
||||
- 1da874cc48894fdf4e1ac9d9e8e269c8
|
||||
X-Robots-Tag:
|
||||
- noindex
|
||||
X-Vcap-Request-Id:
|
||||
- 1da874cc-4889-4fdf-4e1a-c9d9e8e269c8
|
||||
status:
|
||||
code: 302
|
||||
message: Found
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
Cookie:
|
||||
- SESSION=SANITIZED; org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE=SANITIZED;
|
||||
__cflb=SANITIZED; __VCAP_ID__=SANITIZED; __cf_bm=SANITIZED; _cfuvid=SANITIZED;
|
||||
__cfruid=SANITIZED
|
||||
User-Agent:
|
||||
- GCM-iOS-5.7.2.1
|
||||
referer:
|
||||
- https://sso.garmin.com/sso/signin?id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&service=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&source=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountLoginUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountCreationUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed
|
||||
method: GET
|
||||
uri: https://sso.garmin.com/sso/verifyMFA/loginEnterMfaCode?id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&service=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&source=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountLoginUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountCreationUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed
|
||||
response:
|
||||
body:
|
||||
string: "<!DOCTYPE html>\n<html lang=\"en\" class=\"no-js\">\n\n<head>\n <script
|
||||
type=\"text/javascript\" src=\"/sso/js/jquery/3.7.1/jquery.min.js?20210319\"></script>\n
|
||||
\ <script type=\"text/javascript\">jQuery.noConflict();</script>\n <script
|
||||
type=\"text/javascript\" src=\"/sso/js/jquery-validate/1.16.0/jquery.validate.min.js?20210319\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/base.js?20231020\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/jsUtils.js?20210406\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/json2.js\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/postmessage.js?20210319\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/consoleUtils.js?20210319\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/setupMfaRequiredView.js?20210319\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/enterMfaCode.js?20230127\"></script>\n
|
||||
\ <script type=\"text/javascript\">\n var embedWidget = \"true\";\n
|
||||
\ if (embedWidget == \"\") {\n embedWidget = \"\";\n }\n
|
||||
\ embedWidget = (embedWidget == \"true\");\n var parent_url =
|
||||
\"https:\\/\\/sso.garmin.com\\/sso\\/embed\";\n window.onload = function()
|
||||
{\n ifrememberMyBrowserChecked();\n };\n\n jQuery(document).ready(
|
||||
function() {\n if (!embedWidget) {\n send({'gauthHeight':
|
||||
jQuery(\"#GAuth-component\").height()});\n }\n jQuery(\"#mfa-verification-code-submit\").click(function(){\n
|
||||
\ if (!validateMfaCodeAndPrivacyConsents()){\n return
|
||||
false;\n }\n jQuery('#submit-mfa-verification-code-form').submit();\n
|
||||
\ return false;\n });\n });\n var customerGuid
|
||||
= \"0690cc1d-d23d-4412-b027-80fd4ed1c0f6\";\n var mfaMethod = \"email\";\n
|
||||
\ var locale = \"\";\n var clientId = \"\";\n var codeSentTo
|
||||
= \"mt*****@gmail.com\";\n </script>\n <meta charset=\"utf-8\">\n <title>Enter
|
||||
MFA code for login</title>\n <meta name=\"description\" content=\"\">\n
|
||||
\ <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n
|
||||
\ <meta http-equiv=\"cleartype\" content=\"on\">\n <meta http-equiv=\"X-UA-Compatible\"
|
||||
content=\"IE=edge;\" />\n <link href=\"/sso/css/GAuth.css?20170505\" rel=\"stylesheet\"
|
||||
type=\"text/css\" media=\"all\" />\n <link rel=\"stylesheet\" href=\"\"
|
||||
/>\n</head>\n\n<body>\n <div id=\"GAuth-component\">\n <h2 id=\"enter-mfa-code-h2\">Enter
|
||||
security code</h2>\n <input type=\"hidden\" id=\"queryString\" value=\"id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&service=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&source=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountLoginUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountCreationUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed\"
|
||||
/>\n <input type=\"hidden\" id=\"contextPath\" value=\"/sso\" />\n\n
|
||||
\ <div id=\"login-component\" class=\"blueForm-basic\">\n <div
|
||||
id=\"login-state-verifymfa\">\n <td>\n \n
|
||||
\ <span >Code sent to <b>mt*****@gmail.com</b></span>\n
|
||||
\ </td>\n <form id=\"submit-mfa-verification-code-form\"
|
||||
name=\"submit-mfa-verification-code-form\" method=\"post\" novalidate=\"novalidate\">\n
|
||||
\ <div class=\"blueForm-v2\">\n <div
|
||||
class=\"form-alert\">\n <div id=\"genericError\"
|
||||
class=\"error\" hidden>An unexpected error has occurred.</div>\n <div
|
||||
id=\"codeSentAttention\" class=\"attention\" hidden>A new code has been sent.
|
||||
You can request another code in 30 seconds.</div>\n \n
|
||||
\ \n <div id=\"maxLimit\"
|
||||
class=\"error\" hidden=\"hidden\">You have reached the maximum amount of codes
|
||||
requested. Please use a code you've received or wait 24 hours and try
|
||||
again.</div>\n \n </div>\n
|
||||
\ <div class=\"formTextField\">\n <div
|
||||
class=\"mfaFormLabel\">\n <label>\n <span>Security
|
||||
code</span>\n <br/>\n <input
|
||||
type=\"number\" pattern=\"[0-9]*\" inputmode=\"numeric\" maxlength=\"6\" id=\"mfa-code\"
|
||||
name=\"mfa-code\" autofocus oninput=\"validateMfaCodeAndPrivacyConsents()\"/>\n
|
||||
\ </label>\n </div>\n
|
||||
\ </div>\n <br><br>\n <div>\n
|
||||
\ <a href=\"https://support.garmin.com/en-US/?faq=uGHS8ZqOIhA0usBzBMdJu7\"
|
||||
target=\"_blank\" id=\"havingTrouble\">Get help</a><br>\n </div>\n
|
||||
\ <div id=\"requestNewCodeWrapper\" class=\"requestNewCode\">\n
|
||||
\ <a href=\"#\" id=\"newCode\">Request a new code</a>\n
|
||||
\ </div>\n \n \n
|
||||
\ <br>\n \n <br/>\n
|
||||
\ <button type=\"submit\" id=\"mfa-verification-code-submit\"
|
||||
class=\"btn1\">Next</button>\n </div>\n <input
|
||||
type=\"hidden\" name=\"embed\" value=\"true\"/>\n <input
|
||||
type=\"hidden\" name=\"_csrf\" value=\"9AF199177EE70FB2511C2DE25FE2780DEF8327EDCA5AB81C391FAF6E419E83EDF20E1EE31B76E282D8AA46124E3DC5EB1391\"
|
||||
/>\n <input type=\"hidden\" name=\"fromPage\" value=\"setupEnterMfaCode\"/>\n
|
||||
\ <br/>\n </form>\n </div>\n <div
|
||||
class=\"clearfix\"></div> <!-- Ensure that GAuth-component div's height is
|
||||
computed correctly. -->\n </div>\n </div>\n <script type=\"text/javascript\">\n
|
||||
\ resizePageOnLoad(jQuery(\"#GAuth-component\").height());\n </script>\n<script>(function(){function
|
||||
c(){var b=a.contentDocument||a.contentWindow.document;if(b){var d=b.createElement('script');d.innerHTML=\"window.__CF$cv$params={r:'949c83da2a5ac1ca',t:'MTc0ODkyNTY1NC4wMDAwMDA='};var
|
||||
a=document.createElement('script');a.nonce='';a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);\";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var
|
||||
a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else
|
||||
if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var
|
||||
e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body>\n</html>"
|
||||
headers:
|
||||
Access-Control-Allow-Credentials:
|
||||
- 'true'
|
||||
Access-Control-Allow-Headers:
|
||||
- Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type,
|
||||
Access-Control-Request-Method, Access-Control-Request-Headers
|
||||
Access-Control-Allow-Methods:
|
||||
- GET,POST,OPTIONS
|
||||
Access-Control-Allow-Origin:
|
||||
- https://www.garmin.com
|
||||
CF-RAY:
|
||||
- 949c83da2a5ac1ca-QRO
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Language:
|
||||
- en
|
||||
Content-Type:
|
||||
- text/html;charset=UTF-8
|
||||
Date:
|
||||
- Tue, 03 Jun 2025 04:40:54 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=BrVkK9BHKPEC700UIxYqeYMAufPXrsMtXb56Z5naqivj9pfj%2FKyqvweC0oLp4v4n%2BecNLLGdP4o5WUnke2Iu62u0i0gzh9hqR49I8mYeEw6ABEfR8ZFJbx0waSuNNous"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE=SANITIZED;
|
||||
Path=SANITIZED
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Application-Context:
|
||||
- casServer:cloud,prod,prod-US_Olathe:6
|
||||
X-B3-Traceid:
|
||||
- acd069da786e436a7d98ba4e5220bcfc
|
||||
X-Robots-Tag:
|
||||
- noindex
|
||||
X-Vcap-Request-Id:
|
||||
- acd069da-786e-436a-7d98-ba4e5220bcfc
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: mfa-code=123456&embed=true&_csrf=9AF199177EE70FB2511C2DE25FE2780DEF8327EDCA5AB81C391FAF6E419E83EDF20E1EE31B76E282D8AA46124E3DC5EB1391&fromPage=setupEnterMfaCode
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Length:
|
||||
- '160'
|
||||
Content-Type:
|
||||
- application/x-www-form-urlencoded
|
||||
Cookie:
|
||||
- SESSION=SANITIZED; org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE=SANITIZED;
|
||||
__cflb=SANITIZED; __VCAP_ID__=SANITIZED; __cf_bm=SANITIZED; _cfuvid=SANITIZED;
|
||||
__cfruid=SANITIZED
|
||||
User-Agent:
|
||||
- GCM-iOS-5.7.2.1
|
||||
referer:
|
||||
- https://sso.garmin.com/sso/verifyMFA/loginEnterMfaCode?id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&service=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&source=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountLoginUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountCreationUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed
|
||||
method: POST
|
||||
uri: https://sso.garmin.com/sso/verifyMFA/loginEnterMfaCode?id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&service=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&source=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountLoginUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountCreationUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed
|
||||
response:
|
||||
body:
|
||||
string: "<!DOCTYPE html>\n<html lang=\"en\" class=\"no-js\">\n\n<head>\n <script
|
||||
type=\"text/javascript\" src=\"/sso/js/jquery/3.7.1/jquery.min.js?20210319\"></script>\n
|
||||
\ <script type=\"text/javascript\">jQuery.noConflict();</script>\n <script
|
||||
type=\"text/javascript\" src=\"/sso/js/jquery-validate/1.16.0/jquery.validate.min.js?20210319\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/base.js?20231020\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/jsUtils.js?20210406\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/json2.js\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/postmessage.js?20210319\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/consoleUtils.js?20210319\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/setupMfaRequiredView.js?20210319\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/enterMfaCode.js?20230127\"></script>\n
|
||||
\ <script type=\"text/javascript\">\n var embedWidget = \"true\";\n
|
||||
\ if (embedWidget == \"\") {\n embedWidget = \"\";\n }\n
|
||||
\ embedWidget = (embedWidget == \"true\");\n var parent_url =
|
||||
\"https:\\/\\/sso.garmin.com\\/sso\\/embed\";\n window.onload = function()
|
||||
{\n ifrememberMyBrowserChecked();\n };\n\n jQuery(document).ready(
|
||||
function() {\n if (!embedWidget) {\n send({'gauthHeight':
|
||||
jQuery(\"#GAuth-component\").height()});\n }\n jQuery(\"#mfa-verification-code-submit\").click(function(){\n
|
||||
\ if (!validateMfaCodeAndPrivacyConsents()){\n return
|
||||
false;\n }\n jQuery('#submit-mfa-verification-code-form').submit();\n
|
||||
\ return false;\n });\n });\n var customerGuid
|
||||
= \"0690cc1d-d23d-4412-b027-80fd4ed1c0f6\";\n var mfaMethod = \"email\";\n
|
||||
\ var locale = \"\";\n var clientId = \"\";\n var codeSentTo
|
||||
= \"mt*****@gmail.com\";\n </script>\n <meta charset=\"utf-8\">\n <title>Enter
|
||||
MFA code for login</title>\n <meta name=\"description\" content=\"\">\n
|
||||
\ <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n
|
||||
\ <meta http-equiv=\"cleartype\" content=\"on\">\n <meta http-equiv=\"X-UA-Compatible\"
|
||||
content=\"IE=edge;\" />\n <link href=\"/sso/css/GAuth.css?20170505\" rel=\"stylesheet\"
|
||||
type=\"text/css\" media=\"all\" />\n <link rel=\"stylesheet\" href=\"\"
|
||||
/>\n</head>\n\n<body>\n <div id=\"GAuth-component\">\n <h2 id=\"enter-mfa-code-h2\">Enter
|
||||
security code</h2>\n <input type=\"hidden\" id=\"queryString\" value=\"id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&service=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&source=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountLoginUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountCreationUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed\"
|
||||
/>\n <input type=\"hidden\" id=\"contextPath\" value=\"/sso\" />\n\n
|
||||
\ <div id=\"login-component\" class=\"blueForm-basic\">\n <div
|
||||
id=\"login-state-verifymfa\">\n <td>\n \n
|
||||
\ <span >Code sent to <b>mt*****@gmail.com</b></span>\n
|
||||
\ </td>\n <form id=\"submit-mfa-verification-code-form\"
|
||||
name=\"submit-mfa-verification-code-form\" method=\"post\" novalidate=\"novalidate\">\n
|
||||
\ <div class=\"blueForm-v2\">\n <div
|
||||
class=\"form-alert\">\n <div id=\"genericError\"
|
||||
class=\"error\" hidden>An unexpected error has occurred.</div>\n <div
|
||||
id=\"codeSentAttention\" class=\"attention\" hidden>A new code has been sent.
|
||||
You can request another code in 30 seconds.</div>\n <div
|
||||
id=\"invalidCode\" class=\"error\">Invalid code. Please enter a valid code.</div>\n
|
||||
\ \n <div id=\"maxLimit\"
|
||||
class=\"error\" hidden=\"hidden\">You have reached the maximum amount of codes
|
||||
requested. Please use a code you've received or wait 24 hours and try
|
||||
again.</div>\n \n </div>\n
|
||||
\ <div class=\"formTextField\">\n <div
|
||||
class=\"mfaFormLabel\">\n <label>\n <span>Security
|
||||
code</span>\n <br/>\n <input
|
||||
type=\"number\" pattern=\"[0-9]*\" inputmode=\"numeric\" maxlength=\"6\" id=\"mfa-code\"
|
||||
name=\"mfa-code\" autofocus oninput=\"validateMfaCodeAndPrivacyConsents()\"/>\n
|
||||
\ </label>\n </div>\n
|
||||
\ </div>\n <br><br>\n <div>\n
|
||||
\ <a href=\"https://support.garmin.com/en-US/?faq=uGHS8ZqOIhA0usBzBMdJu7\"
|
||||
target=\"_blank\" id=\"havingTrouble\">Get help</a><br>\n </div>\n
|
||||
\ <div id=\"requestNewCodeWrapper\" class=\"requestNewCode\">\n
|
||||
\ <a href=\"#\" id=\"newCode\">Request a new code</a>\n
|
||||
\ </div>\n \n \n
|
||||
\ <br>\n \n <br/>\n
|
||||
\ <button type=\"submit\" id=\"mfa-verification-code-submit\"
|
||||
class=\"btn1\">Next</button>\n </div>\n <input
|
||||
type=\"hidden\" name=\"embed\" value=\"true\"/>\n <input
|
||||
type=\"hidden\" name=\"_csrf\" value=\"\" />\n <input
|
||||
type=\"hidden\" name=\"fromPage\" value=\"setupEnterMfaCode\"/>\n
|
||||
\ <br/>\n </form>\n </div>\n <div
|
||||
class=\"clearfix\"></div> <!-- Ensure that GAuth-component div's height is
|
||||
computed correctly. -->\n </div>\n </div>\n <script type=\"text/javascript\">\n
|
||||
\ resizePageOnLoad(jQuery(\"#GAuth-component\").height());\n </script>\n<script>(function(){function
|
||||
c(){var b=a.contentDocument||a.contentWindow.document;if(b){var d=b.createElement('script');d.innerHTML=\"window.__CF$cv$params={r:'949c83dc9aa555c3',t:'MTc0ODkyNTY1NC4wMDAwMDA='};var
|
||||
a=document.createElement('script');a.nonce='';a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);\";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var
|
||||
a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else
|
||||
if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var
|
||||
e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body>\n</html>"
|
||||
headers:
|
||||
Access-Control-Allow-Credentials:
|
||||
- 'true'
|
||||
Access-Control-Allow-Headers:
|
||||
- Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type,
|
||||
Access-Control-Request-Method, Access-Control-Request-Headers
|
||||
Access-Control-Allow-Methods:
|
||||
- GET,POST,OPTIONS
|
||||
Access-Control-Allow-Origin:
|
||||
- https://www.garmin.com
|
||||
CF-RAY:
|
||||
- 949c83dc9aa555c3-QRO
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Language:
|
||||
- en
|
||||
Content-Type:
|
||||
- text/html;charset=UTF-8
|
||||
Date:
|
||||
- Tue, 03 Jun 2025 04:40:54 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=WVjXMWAY7m%2FICrofTUaszDZoZ1kIv1%2BQTcx49UDCpdhESBjLNt9LucYPatIj%2BHOhRkqNPuM%2F65Tz1kTrR4naiCX0yEAOcMcEAh1yxyiX%2BlU7qvovsvWodipj8YHB19mH"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE=SANITIZED;
|
||||
Path=SANITIZED
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Application-Context:
|
||||
- casServer:cloud,prod,prod-US_Olathe:6
|
||||
X-B3-Traceid:
|
||||
- 104ac62c483244cf73fb9266e97d22bb
|
||||
X-Robots-Tag:
|
||||
- noindex
|
||||
X-Vcap-Request-Id:
|
||||
- 104ac62c-4832-44cf-73fb-9266e97d22bb
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
1017
garth/tests/cassettes/test_login_return_on_mfa.yaml
Normal file
1017
garth/tests/cassettes/test_login_return_on_mfa.yaml
Normal file
File diff suppressed because it is too large
Load Diff
760
garth/tests/cassettes/test_login_success.yaml
Normal file
760
garth/tests/cassettes/test_login_success.yaml
Normal file
@@ -0,0 +1,760 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: GET
|
||||
uri: https://sso.garmin.com/sso/embed?id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso
|
||||
response:
|
||||
body:
|
||||
string: "<html>\n\t<head>\n\t <title>GAuth Embedded Version</title>\n\t <meta
|
||||
http-equiv=\"X-UA-Compatible\" content=\"IE=edge;\" />\n\t <style type=\"text/css\">\n\t
|
||||
\ \t#gauth-widget {border: none !important;}\n\t </style>\n\t</head>\n\t<body>\n\t\t<script
|
||||
type=\"text/javascript\" src=\"/sso/js/jquery/3.1.1/jquery.min.js?20210319\"></script>\n\n<div>\n\t<pre>\n\t<span>ERROR:
|
||||
clientId parameter must be specified!!!</span>\n\n\t<span >Usage: https://sso.garmin.com/sso/embed?clientId=<clientId>&locale=<locale>...</span>\n\n\tRequest
|
||||
parameter configuration options:\n\n\tNAME REQ VALUES
|
||||
\ DESCRIPTION\n\t------------------
|
||||
\ --- -------------------------------------------------------
|
||||
\ ---------------------------------------------------------------------------------------------------\n\tclientId
|
||||
\ Yes \"MY_GARMIN\"/\"BUY_GARMIN\"/\"FLY_GARMIN\"/ Client
|
||||
identifier for your web application\n\t \"RMA\"/\"GarminConnect\"/\"OpenCaching\"/etc\n\tlocale
|
||||
\ Yes \"en\", \"bg\", \"cs\", \"da\", \"de\", \"es\",
|
||||
\"el\", \"fr\", \"hr\", User's current locale, to display the GAuth login
|
||||
widget internationalized properly.\n\t \"in\",
|
||||
\"it\", \"iw\", \"hu\", \"ms\", \"nb\", \"nl\", \"no\", \"pl\", (All the
|
||||
currently supported locales are listed in the Values section.)\n\t \"pt\",
|
||||
\"pt_BR\", \"ru\", \"sk\", \"sl\", \"fi\", \"sv\", \"tr\",\n\t \"uk\",
|
||||
\"th\", \"ja\", \"ko\", \"zh_TW\", \"zh\", \"vi_VN\"\n\tcssUrl No
|
||||
\ Absolute URL to custom CSS file. Use custom CSS
|
||||
styling for the GAuth login widget.\n\treauth No
|
||||
\ true/false (Default value is false) Specify true if
|
||||
you want to ensure that the GAuth login widget shows up,\n\t even
|
||||
if the SSO infrastructure remembers the user and would immediately log them
|
||||
in.\n\t This
|
||||
is useful if you know a user is logged on, but want a different user to be
|
||||
allowed to logon.\n\tinitialFocus No true/false (Default
|
||||
value is true) If you don't want the GAuth login widget
|
||||
to autofocus in it's \"Email or Username\" field upon initial loading,\n\t
|
||||
\ then
|
||||
specify this option and set it to false.\n\trememberMeShown No
|
||||
\ true/false (Default value is false) Whether the \"Remember
|
||||
Me\" check box is shown in the GAuth login widget.\n\trememberMeChecked No
|
||||
\ true/false (Default value is false) Whether the \"Remember
|
||||
Me\" check box feature is checked by default.\n\tcreateAccountShown No
|
||||
\ true/false (Default value is true) Whether the \"Don't
|
||||
have an account? Create One\" link is shown in the GAuth login widget.\n\tsocialEnabled
|
||||
\ No true/false (Default value is false) If
|
||||
set to false, do not show any social sign in elements or allow social sign
|
||||
ins.\n\tlockToEmailAddress No Email address to pre-load and
|
||||
lock. If specified, the specified email address will
|
||||
be pre-loaded in the main \"Email\" field in the SSO login form,\n\t as
|
||||
well as in in the \"Email Address\" field in the \"Forgot Password?\" password
|
||||
reset form,\n\t and
|
||||
both fields will be disabled so they can't be changed.\n\t (If
|
||||
for some reason you want to force re-authentications for a known customer
|
||||
account, you can make use of this option.)\n\topenCreateAccount No
|
||||
\ true/false (Default value is false) If set to true,
|
||||
immediately display the the account creation screen.\n\tdisplayNameShown No
|
||||
\ true/false (Default value is false) If set to true,
|
||||
show the \"Display Name\" field on the account creation screen, to allow the
|
||||
user\n\t to
|
||||
set their central MyGarmin display name upon account creation.\n\tglobalOptInShown
|
||||
\ No true/false (Default value is false) Whether
|
||||
the \"Global Opt-In\" check box is shown on the create account & create social
|
||||
account screens.\n\t If
|
||||
set to true these screens will show a \"Sign Up For Email\" check box with
|
||||
accompanying text\n\t \"I
|
||||
would also like to receive email about promotions and new products.\"\n\t
|
||||
\ If
|
||||
checked, the Customer 2.0 account that is created will have it's global opt-in
|
||||
flag set to true,\n\t and
|
||||
Garmin email communications will be allowed.\n\tglobalOptInChecked No
|
||||
\ true/false (Default value is false) Whether the \"Global
|
||||
Opt-In\" check box is checked by default.\n\tconsumeServiceTicket No
|
||||
\ true/false (Default value is true) IF you don't specify
|
||||
a redirectAfterAccountLoginUrl AND you set this to false, the GAuth login
|
||||
widget\n\t will
|
||||
NOT consume the service ticket assigned and will not seamlessly log you into
|
||||
your webapp.\n\t It
|
||||
will send a SUCCESS JavaScript event with the service ticket and service url
|
||||
you can take\n\t and
|
||||
explicitly validate against the SSO infrastructure yourself.\n\t (By
|
||||
using casClient's SingleSignOnUtils.authenticateServiceTicket() utility method,\n\t
|
||||
\ or
|
||||
calling web service customerWebServices_v1.2 AccountManagementService.authenticateServiceTicket().)\n\tmobile
|
||||
\ No true/false (Default value is false) Setting
|
||||
to true will cause mobile friendly views to be shown instead of the tradition
|
||||
screens.\n\ttermsOfUseUrl No Absolute URL to your custom
|
||||
terms of use URL. If not specified, defaults to http://www.garmin.com/terms\n\tprivacyStatementUrl
|
||||
\ No Absolute URL to your custom privacy statement URL. If
|
||||
not specified, defaults to http://www.garmin.com/privacy\n\tproductSupportUrl
|
||||
\ No Absolute URL to your custom product support URL. If
|
||||
not specified, defaults to http://www.garmin.com/us/support/contact\n\tgenerateExtraServiceTicket
|
||||
\ No true/false (Default value is false) If set
|
||||
to true, generate an extra unconsumed service ticket.\n\t\t (The
|
||||
service ticket validation response will include the extra service ticket.)\n\tgenerateTwoExtraServiceTickets
|
||||
\ No true/false (Default value is false) If set to true,
|
||||
generate two extra unconsumed service tickets.\n\t\t\t\t\t\t\t\t\t \t\t\t
|
||||
\ (The service ticket validation response will include the extra service
|
||||
tickets.)\n\tgenerateNoServiceTicket No true/false (Default value
|
||||
is false) If you don't want SSO to generate a service
|
||||
ticket at all when logging in to the GAuth login widget.\n (Useful
|
||||
when allowing logins to static sites that are not SSO enabled and can't consume
|
||||
the service ticket.)\n\tconnectLegalTerms No true/false (Default
|
||||
value is false) Whether to show the connectLegalTerms
|
||||
on the create account page\n\tshowTermsOfUse No true/false
|
||||
(Default value is false) Whether to show the showTermsOfUse
|
||||
on the create account page\n\tshowPrivacyPolicy No true/false
|
||||
(Default value is false) Whether to show the showPrivacyPolicy
|
||||
on the create account page\n\tshowConnectLegalAge No true/false
|
||||
(Default value is false) Whether to show the showConnectLegalAge
|
||||
on the create account page\n\tlocationPromptShown No true/false
|
||||
(Default value is false) If set to true, ask the customer
|
||||
during account creation to verify their country of residence.\n\tshowPassword
|
||||
\ No true/false (Default value is true) If
|
||||
set to false, mobile version for createAccount and login screens would hide
|
||||
the password\n\tuseCustomHeader No true/false (Default value
|
||||
is false) If set to true, the \"Sign in\" text will be
|
||||
replaced by custom text. Contact CDS team to set the i18n text for your client
|
||||
id.\n\tmfaRequired No true/false (Default value is false)
|
||||
\ Require multi factor authentication for all authenticating
|
||||
users.\n\tperformMFACheck No true/false (Default value is
|
||||
false) If set to true, ask the logged in user to pass
|
||||
a multi factor authentication check. (Only valid for an already logged in
|
||||
user.)\n\trememberMyBrowserShown No true/false (Default value is
|
||||
false) Whether the \"Remember My Browser\" check box
|
||||
is shown in the GAuth login widget MFA verification screen.\n\trememberMyBrowserChecked
|
||||
\ No true/false (Default value is false) Whether
|
||||
the \"Remember My Browser\" check box feature is checked by default.\n\tconsentTypeIds\t\t\t\t\tNo\tconsent_types
|
||||
ids\t\t \t\t\t\t\t\t\t\t multiple consent types ids can be passed as consentTypeIds=type1&consentTypeIds=type2\n\t</pre>\n</div>\n\n\n\t<script>(function(){var
|
||||
js = \"window['__CF$cv$params']={r:'7f1ac05c5db34620'};_cpo=document.createElement('script');_cpo.nonce='',_cpo.src='/cdn-cgi/challenge-platform/scripts/invisible.js',document.getElementsByTagName('head')[0].appendChild(_cpo);\";var
|
||||
_0xh = document.createElement('iframe');_0xh.height = 1;_0xh.width = 1;_0xh.style.position
|
||||
= 'absolute';_0xh.style.top = 0;_0xh.style.left = 0;_0xh.style.border = 'none';_0xh.style.visibility
|
||||
= 'hidden';document.body.appendChild(_0xh);function handler() {var _0xi =
|
||||
_0xh.contentDocument || _0xh.contentWindow.document;if (_0xi) {var _0xj =
|
||||
_0xi.createElement('script');_0xj.innerHTML = js;_0xi.getElementsByTagName('head')[0].appendChild(_0xj);}}if
|
||||
(document.readyState !== 'loading') {handler();} else if (window.addEventListener)
|
||||
{document.addEventListener('DOMContentLoaded', handler);} else {var prev =
|
||||
document.onreadystatechange || function () {};document.onreadystatechange
|
||||
= function (e) {prev(e);if (document.readyState !== 'loading') {document.onreadystatechange
|
||||
= prev;handler();}};}})();</script></body>\n</html>\n"
|
||||
headers:
|
||||
Access-Control-Allow-Credentials:
|
||||
- 'true'
|
||||
Access-Control-Allow-Headers:
|
||||
- Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type,
|
||||
Access-Control-Request-Method, Access-Control-Request-Headers
|
||||
Access-Control-Allow-Methods:
|
||||
- GET,POST,OPTIONS
|
||||
Access-Control-Allow-Origin:
|
||||
- https://www.garmin.com
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 7f1ac05c5db34620-DFW
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Language:
|
||||
- en
|
||||
Content-Type:
|
||||
- text/html;charset=UTF-8
|
||||
Date:
|
||||
- Fri, 04 Aug 2023 23:58:58 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=oT4HuH19GyKTBkqXHXwnSmUp1kcqmbthvziWIXEZLe3kQQBobYqW0Qi%2FXSjy%2B2GN5dy3Cs4ZId74RXjz6bteuof9l6R2Qsum28N4Hy0wBm4fbvYMJZUPAeRAda3mHrUW"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE=SANITIZED;
|
||||
Path=SANITIZED
|
||||
- __cf_bm=SANITIZED; path=SANITIZED; expires=SANITIZED; domain=SANITIZED; HttpOnly;
|
||||
Secure; SameSite=SANITIZED
|
||||
- __cflb=SANITIZED; SameSite=SANITIZED; Secure; path=SANITIZED; expires=SANITIZED;
|
||||
HttpOnly
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
X-Application-Context:
|
||||
- casServer:cloud,prod,prod-US_1102:0
|
||||
X-B3-Traceid:
|
||||
- 0338524d977495f906e0c0e47bda3479
|
||||
X-Robots-Tag:
|
||||
- noindex
|
||||
X-Vcap-Request-Id:
|
||||
- 8cae38fd-8c5b-4d76-5f53-ed081975bd13
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Connection:
|
||||
- keep-alive
|
||||
Cookie:
|
||||
- __cf_bm=SANITIZED; _cfuvid=SANITIZED; __cflb=SANITIZED; org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE=SANITIZED
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
referer:
|
||||
- https://sso.garmin.com/sso/embed?id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso
|
||||
method: GET
|
||||
uri: https://sso.garmin.com/sso/signin?id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&service=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&source=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountLoginUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountCreationUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed
|
||||
response:
|
||||
body:
|
||||
string: "<!DOCTYPE html>\n<html lang=\"en\" class=\"no-js\">\n <head>\n <meta
|
||||
http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n <meta
|
||||
name=\"viewport\" content=\"width=device-width\" />\n <meta http-equiv=\"X-UA-Compatible\"
|
||||
content=\"IE=edge;\" />\n <title>GARMIN Authentication Application</title>\n
|
||||
\ <link href=\"/sso/css/GAuth.css?20210406\" rel=\"stylesheet\" type=\"text/css\"
|
||||
media=\"all\" />\n\n\t <link rel=\"stylesheet\" href=\"\"/>\n\n <script
|
||||
type=\"text/javascript\" src=\"/sso/js/jquery/3.1.1/jquery.min.js?20210319\"></script>\n
|
||||
\ <script type=\"text/javascript\">jQuery.noConflict();</script>\n\t\t<script
|
||||
type=\"text/javascript\" src=\"/sso/js/jquery-validate/1.16.0/jquery.validate.min.js?20210319\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/jsUtils.js?20210406\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/json2.js\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/consoleUtils.js?20210319\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/postmessage.js?20210319\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/popupWindow.js\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/base.js?20210406\"></script>\n\t\t<script
|
||||
type=\"text/javascript\" src=\"/sso/js/gigyaUtils.js?20210319\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/login.js?20211102\"></script>\n
|
||||
\ <script type=\"text/javascript\" src=\"/sso/js/reCaptchaUtil.js?20230706\"></script>\n\n
|
||||
\ <script>\n var recaptchaSiteKey = null;\n var
|
||||
reCaptchaURL = \"\\\\\\/sso\\\\\\/reCaptcha?id=gauth-widget\\u0026embedWidget=true\\u0026gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed\\u0026service=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed\\u0026source=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed\\u0026redirectAfterAccountLoginUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed\\u0026redirectAfterAccountCreationUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed\";\n
|
||||
\ var isRecaptchaEnabled = null;\n var recaptchaToken
|
||||
= null; \n </script>\n <script type=\"text/javascript\">\n
|
||||
\ var parent_url = \"https:\\/\\/sso.garmin.com\\/sso\\/embed\";\n
|
||||
\ var status \t\t\t= \"\";\n\t\t\tvar result = \"\";\n\t\t\tvar
|
||||
clientId\t\t= '';\n\t\t\tvar embedWidget \t= true;\n\t\t\tvar isUsernameDefined
|
||||
= (false == true) || (false == true);\n\n // Gigya callback to
|
||||
SocialSignInController for brand new social network users redirects to this
|
||||
page\n // to popup Create or Link Social Account page, but has
|
||||
a possibly mangled source parameter\n // where \"?\" is set as
|
||||
\"<QM>\", so translate it back to \"?\" here.\n parent_url = parent_url.replace('<QM>',
|
||||
'?');\n var parent_scheme = parent_url.substring(0, parent_url.indexOf(\"://\"));\n
|
||||
\ var parent_hostname = parent_url.substring(parent_scheme.length
|
||||
+ 3, parent_url.length);\n if (parent_hostname.indexOf(\"/\") !=
|
||||
-1) {\n parent_hostname = parent_hostname.substring(0, parent_hostname.indexOf(\"/\"));\n
|
||||
\ }\n var parentHost \t = parent_scheme + \"://\"
|
||||
+ parent_hostname;\n\t\t\tvar createAccountConfigURL = '\\/sso\\/createNewAccount?id%3Dgauth-widget%26embedWidget%3Dtrue%26gauthHost%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed%26service%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed%26source%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed%26redirectAfterAccountLoginUrl%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed%26redirectAfterAccountCreationUrl%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed';\n
|
||||
\ var socialConfigURL = 'https://sso.garmin.com/sso/socialSignIn?id%3Dgauth-widget%26embedWidget%3Dtrue%26gauthHost%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed%26service%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed%26source%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed%26redirectAfterAccountLoginUrl%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed%26redirectAfterAccountCreationUrl%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed';\n
|
||||
\ var gigyaURL = \"https://cdns.gigya.com/js/gigya.js?apiKey=2_R3ZGY8Bqlwwk3_63knoD9wA_m-Y19mAgW61bF_s5k9gymYnMEAtMrJiF5MjF-U7B\";\n\n
|
||||
\ if (createAccountConfigURL.indexOf('%253A%252F%252F') != -1) {\n
|
||||
\ \tcreateAccountConfigURL = decodeURIComponent(createAccountConfigURL);\n
|
||||
\ }\n consoleInfo('signin.html embedWidget: true, createAccountConfigURL:
|
||||
\\/sso\\/createNewAccount?id%3Dgauth-widget%26embedWidget%3Dtrue%26gauthHost%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed%26service%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed%26source%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed%26redirectAfterAccountLoginUrl%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed%26redirectAfterAccountCreationUrl%3Dhttps%253A%252F%252Fsso.garmin.com%252Fsso%252Fembed,
|
||||
socialEnabled: true, gigyaSupported: true, socialConfigURL(): https://sso.garmin.com/sso/socialSignIn?id%3Dgauth-widget%26embedWidget%3Dtrue%26gauthHost%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed%26service%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed%26source%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed%26redirectAfterAccountLoginUrl%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed%26redirectAfterAccountCreationUrl%3Dhttps%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed');\n\n
|
||||
\ if (socialConfigURL.indexOf('%3A%2F%2F') != -1) {\n \tsocialConfigURL
|
||||
= decodeURIComponent(socialConfigURL);\n }\n\n if( status
|
||||
!= null && status != ''){\n \tsend({'status':status});\n }\n\n
|
||||
\ jQuery(document).ready( function(){\n\n\n consoleInfo(\"signin.html:
|
||||
setting field validation rules...\");\n\n jQuery(\"#username\").rules(\"add\",{\n
|
||||
\ required: true,\n messages: {\n required:
|
||||
\ \"Email is required.\"\n }});\n\n jQuery(\"#password\").rules(\"add\",
|
||||
{\n required: true,\n messages: {\n
|
||||
\ required: \"Password is required.\"\n }\n
|
||||
\ });\n\n consoleInfo(\"signin.html: done setting
|
||||
field validation rules...\");\n\n });\n\n XD.receiveMessage(function(m){\n
|
||||
\ consoleInfo(\"signin.html: \" + m.data + \" received on \"
|
||||
+ window.location.host);\n if (m && m.data) {\n var
|
||||
md = m.data;\n if (typeof(md) === 'string') {\n md
|
||||
= JSON.parse(m.data);\n }\n if (md.setUsername)
|
||||
{\n consoleInfo(\"signin.html: Setting username \\\"\"
|
||||
+ md.username + \"\\\"...\");\n jQuery(\"#signInWithDiffLink\").click();
|
||||
// Ensure the normal login form is shown.\n jQuery(\"#username\").val(md.username);\n
|
||||
\ jQuery(\"#password\").focus();\n }\n
|
||||
\ }\n }, parentHost);\n </script>\n </head>\n
|
||||
\ <body>\n\n <!-- begin GAuth component -->\n <div id=\"GAuth-component\">\n
|
||||
\ <!-- begin login component-->\n <div id=\"login-component\"
|
||||
class=\"blueForm-basic\">\n <input type=\"hidden\" id=\"queryString\"
|
||||
value=\"id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&service=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&source=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountLoginUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountCreationUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed\"
|
||||
/>\n\t \t <input type=\"hidden\" id=\"contextPath\" value=\"/sso\" />\n
|
||||
\ <!-- begin login form -->\n <div id=\"login-state-default\">\n
|
||||
\ <h2>Sign In</h2>\n\n <form method=\"post\"
|
||||
id=\"login-form\">\n\n <div class=\"form-alert\">\n\t\t\t\t\t\t\t\n
|
||||
\ \n \n \n
|
||||
\ \n \n \n\n
|
||||
\ <div id=\"username-error\" style=\"display:none;\"></div>\n
|
||||
\ <div id=\"password-error\" style=\"display:none;\"></div>\n
|
||||
\ </div>\n <div class=\"textfield\">\n\t\t\t\t\t\t\t<label
|
||||
for=\"username\">Email</label>\n \t\t<!-- If the
|
||||
lockToEmailAddress parameter is specified then we want to mark the field as
|
||||
readonly,\n \t\tpreload the email address, and disable
|
||||
the other input so that null isn't sent to the server. We'll\n \t\talso
|
||||
style the field to have a darker grey background and disable the mouse pointer\n
|
||||
\ \t\t -->\n\t\t\t\t\t\t\t \n\t\t\t\t\t\t\t\t<!--
|
||||
If the lockToEmailAddress parameter is NOT specified then keep the existing
|
||||
functionality and disable the readonly input field\n\t\t\t\t\t\t\t -->\n\t\t\t\t\t\t\t
|
||||
\ <input class=\"login_email\" name=\"username\" id=\"username\" value=\"\"
|
||||
type=\"email\" spellcheck=\"false\" autocorrect=\"off\" autocapitalize=\"off\"/>\n\n
|
||||
\ </div>\n\n <div class=\"textfield\">\n
|
||||
\ <label for=\"password\">Password</label>\n <a
|
||||
id=\"loginforgotpassword\" class=\"login-forgot-password\" style=\"cursor:pointer\">(Forgot?)</a>\n
|
||||
\ <input type=\"password\" name=\"password\" id=\"password\"
|
||||
spellcheck=\"false\" autocorrect=\"off\" autocapitalize=\"off\" />\n <strong
|
||||
id=\"capslock-warning\" class=\"information\" title=\"Caps lock is on.\" style=\"display:
|
||||
none;\">Caps lock is on.</strong>\n\t\t\t\t\t </div>\n <input
|
||||
type=\"hidden\" name=\"embed\" value=\"true\"/>\n <input
|
||||
type=\"hidden\" name=\"_csrf\" value=\"13F3CDCE9481A2AA198AB31BDC20A8483C5FAAAC4C9BFA97A27733E25331ECCA20D8E912E7DBB994EC0F43F2E5E018326534\"
|
||||
/>\n <button type=\"submit\" id=\"login-btn-signin\"
|
||||
class=\"btn1\" accesskey=\"l\">Sign In</button>\n \n\n\n
|
||||
\ <!-- The existence of the \"rememberme\" parameter
|
||||
at all will remember the user! -->\n \n\n </form>\n
|
||||
\ </div>\n <!-- end login form -->\n\n <!--
|
||||
begin Create Account message -->\n\t <div id=\"login-create-account\">\n\t
|
||||
\ \n\t </div>\n\t <!-- end Create Account
|
||||
message -->\n\n\t <!-- begin Social Sign In component -->\n\t <div
|
||||
id=\"SSI-component\">\n \n\n\t\t\t\t\t\n\t </div>\n\t
|
||||
\ <!-- end Social Sign In component -->\n <div class=\"clearfix\"></div>
|
||||
<!-- Ensure that GAuth-component div's height is computed correctly. -->\n
|
||||
\ </div>\n <!-- end login component-->\n\n\t\t</div>\n\t\t<!--
|
||||
end GAuth component -->\n\n <script type=\"text/javascript\">\n jQuery(document).ready(function(){\n
|
||||
\ \tresizePageOnLoad(jQuery(\"#GAuth-component\").height());\n\n\t\t
|
||||
\ if(isUsernameDefined == true){\n\t\t // If the user's login
|
||||
just failed, redisplay the email/username specified, and focus them in the
|
||||
password field.\n\t\t jQuery(\"#password\").focus();\n\t\t }
|
||||
else if(false == true && result != \"PASSWORD_RESET_RESULT\"){\n //
|
||||
Otherwise focus them in the username field of the login dialog.\n jQuery(\"#username\").focus();\n
|
||||
\ }\n\n // Scroll to top of iframe to fix problem
|
||||
where Firefox 3.0-3.6 browsers initially show top of iframe cutoff.\n location.href=\"#\";\n\n
|
||||
\ if(!embedWidget){\n \tjQuery('.createAccountLink').click(function(){\n\t
|
||||
\ send({'openLiteBox':'createAccountLink', 'popupUrl': createAccountConfigURL,
|
||||
'popupTitle':'Create An Account', 'clientId':clientId});\n\t });\n
|
||||
\ }\n });\n </script>\n <script>(function(){var
|
||||
js = \"window['__CF$cv$params']={r:'7f1ac05e69e44796'};_cpo=document.createElement('script');_cpo.nonce='',_cpo.src='/cdn-cgi/challenge-platform/scripts/invisible.js',document.getElementsByTagName('head')[0].appendChild(_cpo);\";var
|
||||
_0xh = document.createElement('iframe');_0xh.height = 1;_0xh.width = 1;_0xh.style.position
|
||||
= 'absolute';_0xh.style.top = 0;_0xh.style.left = 0;_0xh.style.border = 'none';_0xh.style.visibility
|
||||
= 'hidden';document.body.appendChild(_0xh);function handler() {var _0xi =
|
||||
_0xh.contentDocument || _0xh.contentWindow.document;if (_0xi) {var _0xj =
|
||||
_0xi.createElement('script');_0xj.innerHTML = js;_0xi.getElementsByTagName('head')[0].appendChild(_0xj);}}if
|
||||
(document.readyState !== 'loading') {handler();} else if (window.addEventListener)
|
||||
{document.addEventListener('DOMContentLoaded', handler);} else {var prev =
|
||||
document.onreadystatechange || function () {};document.onreadystatechange
|
||||
= function (e) {prev(e);if (document.readyState !== 'loading') {document.onreadystatechange
|
||||
= prev;handler();}};}})();</script></body>\n</html>\n"
|
||||
headers:
|
||||
Access-Control-Allow-Credentials:
|
||||
- 'true'
|
||||
Access-Control-Allow-Headers:
|
||||
- Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type,
|
||||
Access-Control-Request-Method, Access-Control-Request-Headers
|
||||
Access-Control-Allow-Methods:
|
||||
- GET,POST,OPTIONS
|
||||
Access-Control-Allow-Origin:
|
||||
- https://www.garmin.com
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-Ray:
|
||||
- 7f1ac05e69e44796-DFW
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Language:
|
||||
- en
|
||||
Content-Type:
|
||||
- text/html;charset=UTF-8
|
||||
Date:
|
||||
- Fri, 04 Aug 2023 23:58:58 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=Rzl0HOKLCsW3P9JRr3RwN81d8PqMActtbSq4Dr6hqeIxBIK%2FkzLeLOMRn0KALChfJIB4cSgh1DszqkKVz6dxdWoBV7jsY4WstuGcTqyiAIeGTgRqmxgjXdR2B4Q65HZU"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE=SANITIZED;
|
||||
Path=SANITIZED
|
||||
- SESSION=SANITIZED; Path=SANITIZED; Secure; HttpOnly
|
||||
- __VCAP_ID__=SANITIZED; Path=SANITIZED; HttpOnly; Secure
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
Vary:
|
||||
- Accept-Encoding
|
||||
X-Application-Context:
|
||||
- casServer:cloud,prod,prod-US_1102:3
|
||||
X-B3-Traceid:
|
||||
- 60593e96725f1a2f5e057d199d84874b
|
||||
X-Robots-Tag:
|
||||
- noindex
|
||||
X-Vcap-Request-Id:
|
||||
- 9799eaf1-bdf2-420f-52ab-755533296a5f
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: username=SANITIZED&password=SANITIZED&embed=true&_csrf=13F3CDCE9481A2AA198AB31BDC20A8483C5FAAAC4C9BFA97A27733E25331ECCA20D8E912E7DBB994EC0F43F2E5E018326534
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Length:
|
||||
- '175'
|
||||
Content-Type:
|
||||
- application/x-www-form-urlencoded
|
||||
Cookie:
|
||||
- SESSION=SANITIZED; __cf_bm=SANITIZED; _cfuvid=SANITIZED; __VCAP_ID__=SANITIZED;
|
||||
__cflb=SANITIZED; org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE=SANITIZED
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
referer:
|
||||
- https://sso.garmin.com/sso/signin?id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&service=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&source=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountLoginUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountCreationUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed
|
||||
method: POST
|
||||
uri: https://sso.garmin.com/sso/signin?id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&service=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&source=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountLoginUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountCreationUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed
|
||||
response:
|
||||
body:
|
||||
string: "<!DOCTYPE html>\n<html class=\"no-js\">\n\t<head>\n\t\t<title>Success</title>\n\t\t<meta
|
||||
charset=\"utf-8\">\n\t\t<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge;\"
|
||||
/>\n\t\t<meta name=\"description\" content=\"\">\n\t\t<meta name=\"viewport\"
|
||||
content=\"width=device-width, initial-scale=1\">\n\t\t<meta http-equiv=\"cleartype\"
|
||||
content=\"on\">\n\t\t<script type=\"text/javascript\" src=\"/sso/js/jquery/3.1.1/jquery.min.js?20210319\"></script>\n\t\t<script
|
||||
type=\"text/javascript\">jQuery.noConflict();</script>\n\t\t<script type=\"text/javascript\"
|
||||
src=\"/sso/js/json2.js\"></script>\n\t\t<script type=\"text/javascript\" src=\"/sso/js/consoleUtils.js?20210319\"></script>\n\t\t<script
|
||||
type=\"text/javascript\" src=\"/sso/js/postmessage.js?20210319\"></script>\n\t\t<script
|
||||
type=\"text/javascript\">\n\t\t\tvar redirectAfterAccountLoginUrl \t = \"https:\\/\\/sso.garmin.com\\/sso\\/embed\";\n\t\t\tvar
|
||||
redirectAfterAccountCreationUrl = \"https:\\/\\/sso.garmin.com\\/sso\\/embed\";\n\t\t\tvar
|
||||
consumeServiceTicket \t = \"true\";\n\t\t\tvar service_url \t
|
||||
\ = \"https:\\/\\/sso.garmin.com\\/sso\\/embed\";\n\t\t\tvar parent_url \t
|
||||
\ = \"https:\\/\\/sso.garmin.com\\/sso\\/embed\";\n\t\t\tvar response_url
|
||||
\ \t = \"https:\\/\\/sso.garmin.com\\/sso\\/embed?ticket=ST-2459533-YeWiOAMHdTmHqEyWcWeI-cas\";\n\t\t\tvar
|
||||
logintoken \t = \"\";\n\t\t\tvar socialLogin \t
|
||||
\ = \"\";\n\t\t\tvar performMFACheck = \"\";\n\n\t\t\t// Decode
|
||||
url if it's encoded unnecessarily (which is happening when SSO GAuth logins
|
||||
redisplay the login page due to session timeouts.)\n\t\t\tif (response_url.indexOf('%3A%2F%2F')
|
||||
!= -1) {\n\t\t\t\tresponse_url = decodeURIComponent(response_url);\n\t\t\t}\n\t\t\tresponse_url
|
||||
= response_url.replace(new RegExp(\"&\", 'g'),\"&\");\n\n\t\t\tvar service_ticket
|
||||
= response_url.substring(response_url.indexOf('ticket=') + 7, response_url.length);\n\n\t\t\tif
|
||||
(redirectAfterAccountLoginUrl) {\n\t\t\t\tconsoleInfo('casEmbedSuccess.html:
|
||||
redirectAfterAccountLoginUrl: [' + redirectAfterAccountLoginUrl + ']');\n\t\t\t}\n\t\t\tconsoleInfo('casEmbedSuccess.html:
|
||||
consumeServiceTicket: [' + consumeServiceTicket + ']');\n\t\t\tconsoleInfo('casEmbedSuccess.html:
|
||||
service_url: [' + service_url + ']');\n\t\t\tconsoleInfo('casEmbedSuccess.html:
|
||||
parent_url: [' + parent_url + ']');\n\t\t\tconsoleInfo('casEmbedSuccess.html:
|
||||
response_url: [' + response_url + ']');\n\t\t\tconsoleInfo('casEmbedSuccess.html:
|
||||
service_ticket: [' + service_ticket + ']');\n\t\t\tif (logintoken)
|
||||
{\n\t\t\t\tconsoleInfo('casEmbedSuccess.html: logintoken: ['
|
||||
+ logintoken + ']');\n\t\t\t}\n\t\t\tif (socialLogin) {\n\t\t\t\tconsoleInfo('casEmbedSuccess.html:
|
||||
socialLogin: [' + socialLogin + ']');\n\t\t\t}\n\n\t\t\tfunction
|
||||
send(msg) {\n\t\t\t\tconsoleInfo('casEmbedSuccess.html: send(): Calling XD.postMessage(msg:['
|
||||
+ JSON.stringify(msg) + '], target_url:[' + parent_url + '])...');\n\t\t\t\tXD.postMessage(msg,
|
||||
parent_url, parent);\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tfunction redirect(target){\n\t\t\t\tvar
|
||||
embedWidget = \"true\";\n\t\t\t\tif (embedWidget != 'true') {\n\t\t\t\t\tif
|
||||
(logintoken || socialLogin) {\n\t\t\t\t\t\t// Tell parent to close opened
|
||||
Gauth lite box.\n\t\t\t\t\t\tsend({'closeLiteBox':'1'});\n\t\t\t\t\t} else
|
||||
{\n\t\t\t\t\t\t// Tell parent to resize Gauth widget's height to just that
|
||||
of the loading icon.\n\t\t\t\t\t\tsend({'gauthHeight':jQuery(\"#GAuth-component\").height()});\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif
|
||||
(window.opener != null) {\n\t\t\t\t\tvar iframeParent = window.opener.parent;\n\t\t\t\t\tiframeParent.location.href
|
||||
= target;\n\t\t\t\t\twindow.close();\n\t\t\t\t} else {\n\t\t\t\t\t// If the
|
||||
\"redirectAfterAccountLoginUrl\" GAuth config parameter was specified,\n\t\t\t\t\t//
|
||||
a full page refresh upon logon was requested, so redirect the parent window
|
||||
upon login.\n\t\t\t\t\tif (redirectAfterAccountLoginUrl || redirectAfterAccountCreationUrl
|
||||
|| (socialLogin && socialLogin === 'true') || (performMFACheck && performMFACheck
|
||||
=== 'true')) {\n\n\t\t\t\t\t\tif (embedWidget != 'true') {\n\t\t\t\t\t\t\tsend({'status':'SUCCESS',
|
||||
'successDetails':'Login Successful'});\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Redirect
|
||||
parent of login iframe to requested service URL with ?ST=<Service Ticket ID>
|
||||
appended,\n\t\t\t\t\t\t// so the webapp can validate the service ticket and
|
||||
log the user on to the webapp.\n\t\t\t\t\t\tconsoleInfo('casEmbedSuccess.html:
|
||||
Calling parent.location.href = [' + response_url + '];...');\n\t\t\t\t\t\ttop.location.href
|
||||
= response_url;\n\n\t\t\t\t\t// Else if GAuth was configured not to consume
|
||||
the service ticket, send the service ticket and service url.\n\t\t\t\t\t//
|
||||
It's then up to the parent page to hide the GAuth widget and validate the
|
||||
service ticket.\n\t\t\t\t\t} else if (consumeServiceTicket == 'false') {\n\n\t\t\t\t\t\tsend({'status':'SUCCESS',
|
||||
'successDetails':'Login Successful', 'serviceTicket':service_ticket, 'serviceUrl':service_url});\n\n\t\t\t\t\t}
|
||||
else {\n\t\t\t\t\t\t// Else consume the service ticket and log the user into
|
||||
the parent webapp, by making a JSONP request to the response url.\n\t\t\t\t\t\t//
|
||||
This service ticket request to the parent webapp will return a small amount
|
||||
of JSON that we\n\t\t\t\t\t\t// can send in the success event to the parent
|
||||
page to notify them of successful logon\n\t\t\t\t\t\t// It's then up to the
|
||||
parent page to hide the GAuth widget and do whatever it wants.\n\t\t\t\t\t\tconsoleInfo('casEmbedSuccess.html:
|
||||
Loading ajax jsonp URL: [' + response_url + ']');\n\t\t\t\t\t\tjQuery.ajax({\n\t\t\t\t\t\t\ttype:
|
||||
\"REDIRECT\",\n\t\t\t\t\t\t\turl: response_url,\n\t\t\t\t\t\t\tdataType: 'jsonp',\n\t\t\t\t\t\t\terror:
|
||||
function(xhr, status, error) {\n\t\t\t\t\t\t\t\tconsoleError('casEmbedSuccess.html:
|
||||
Error loading ajax jsonp URL: [' + response_url + ']! Error: ' + error);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tsuccess:
|
||||
function( data, status, xhr ) {\n\t\t\t\t\t\t\t\tconsoleInfo('casEmbedSuccess.html:
|
||||
success loading ajax jsonp url. data: [' + data + ']');\n\t\t\t\t\t\t\t\tvar
|
||||
userdata = data;\n\t\t\t\t\t\t\t\tif (typeof(userdata) === 'string') {\n\t\t\t\t\t\t\t\t\tuserdata
|
||||
= JSON.parse(data);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconsoleInfo('casGenericRedirect.jsp:
|
||||
customerId: [' + userdata.customerId\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t+
|
||||
'], username: [' + userdata.username\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t+
|
||||
'], rememberMe: [' + userdata.rememberMe\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t+
|
||||
'], password: [' + userdata.password\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t+
|
||||
'], email: [' + userdata.email\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t+
|
||||
'], displayName: [' + userdata.displayName\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t+
|
||||
'], firstName: [' + userdata.firstName\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t+
|
||||
'], lastName: [' + userdata.lastName\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t+
|
||||
'], localePreference: [' + userdata.localePreference\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t+
|
||||
'], addressLine1: [' + userdata.addressLine1\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t+
|
||||
'], addressLine2: [' + userdata.addressLine2\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t+
|
||||
'], cityName: [' + userdata.cityName\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t+
|
||||
'], state: [' + userdata.state\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t+
|
||||
'], postalCode: [' + userdata.postalCode\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t+
|
||||
'], country: [' + userdata.country\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t+
|
||||
'], passwordChangeRequired: [' + userdata.passwordChangeRequired\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t+
|
||||
'], lastLogin: [' + userdata.lastLogin\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t+
|
||||
'], erpCustomerNumber: [' + userdata.erpCustomerNumber\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t+
|
||||
']');\n\t\t\t\t\t\t\t\tsend({'status' : 'SUCCESS',\n\t\t\t\t\t\t\t\t\t\t\t'successDetails'
|
||||
\ : 'Login Successful',\n\t\t\t\t\t\t\t\t\t\t\t'customerId' :
|
||||
userdata.customerId,\n\t\t\t\t\t\t\t\t\t\t\t'username' : userdata.username,\n\t\t\t\t\t\t\t\t\t\t\t'rememberMe'
|
||||
\ : userdata.rememberMe,\n\t\t\t\t\t\t\t\t\t\t\t'password' :
|
||||
userdata.password,\n\t\t\t\t\t\t\t\t\t\t\t'email' : userdata.email,\n\t\t\t\t\t\t\t\t\t\t\t'displayName'
|
||||
\ : userdata.displayName,\n\t\t\t\t\t\t\t\t\t\t\t'firstName' :
|
||||
userdata.firstName,\n\t\t\t\t\t\t\t\t\t\t\t'lastName' : userdata.lastName,\n\t\t\t\t\t\t\t\t\t\t\t'localePreference'
|
||||
\ : userdata.localePreference,\n\t\t\t\t\t\t\t\t\t\t\t'addressLine1'
|
||||
\ : userdata.addressLine1,\n\t\t\t\t\t\t\t\t\t\t\t'addressLine2'
|
||||
\ : userdata.addressLine2,\n\t\t\t\t\t\t\t\t\t\t\t'cityName' :
|
||||
userdata.cityName,\n\t\t\t\t\t\t\t\t\t\t\t'state' : userdata.state,\n\t\t\t\t\t\t\t\t\t\t\t'postalCode'
|
||||
\ : userdata.postalCode,\n\t\t\t\t\t\t\t\t\t\t\t'country' :
|
||||
userdata.country,\n\t\t\t\t\t\t\t\t\t\t\t'passwordChangeRequired' : userdata.passwordChangeRequired,\n\t\t\t\t\t\t\t\t\t\t\t'lastLogin'
|
||||
\ : userdata.lastLogin,\n\t\t\t\t\t\t\t\t\t\t\t'erpCustomerNumber'
|
||||
\ : userdata.erpCustomerNumber\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t</script>\n\t</head>\n\t<body>\n\t\t<div
|
||||
id=\"GAuth-component\">\n\t\t\t<img src='/sso/images/ajax-loader.gif' class=\"loaderImage\"/>\n\t\t</div>\n\t\t<script
|
||||
type=\"text/javascript\">\n\t\t\tjQuery(document).ready(function(){\n\t\t\t\tvar
|
||||
service = \"https:\\/\\/sso.garmin.com\\/sso\\/embed\";\n\t\t\t\tconsoleInfo(\"casEmbedSuccess.html:
|
||||
ready, calling redirect('\" + service + \"')...\");\n\t\t\t\tredirect(service);\n\t\t\t});\n\t\t</script>\n\t<script>(function(){var
|
||||
js = \"window['__CF$cv$params']={r:'7f1ac060fe1846d4'};_cpo=document.createElement('script');_cpo.nonce='',_cpo.src='/cdn-cgi/challenge-platform/scripts/invisible.js',document.getElementsByTagName('head')[0].appendChild(_cpo);\";var
|
||||
_0xh = document.createElement('iframe');_0xh.height = 1;_0xh.width = 1;_0xh.style.position
|
||||
= 'absolute';_0xh.style.top = 0;_0xh.style.left = 0;_0xh.style.border = 'none';_0xh.style.visibility
|
||||
= 'hidden';document.body.appendChild(_0xh);function handler() {var _0xi =
|
||||
_0xh.contentDocument || _0xh.contentWindow.document;if (_0xi) {var _0xj =
|
||||
_0xi.createElement('script');_0xj.innerHTML = js;_0xi.getElementsByTagName('head')[0].appendChild(_0xj);}}if
|
||||
(document.readyState !== 'loading') {handler();} else if (window.addEventListener)
|
||||
{document.addEventListener('DOMContentLoaded', handler);} else {var prev =
|
||||
document.onreadystatechange || function () {};document.onreadystatechange
|
||||
= function (e) {prev(e);if (document.readyState !== 'loading') {document.onreadystatechange
|
||||
= prev;handler();}};}})();</script></body>\n</html>\n"
|
||||
headers:
|
||||
Access-Control-Allow-Credentials:
|
||||
- 'true'
|
||||
Access-Control-Allow-Headers:
|
||||
- Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type,
|
||||
Access-Control-Request-Method, Access-Control-Request-Headers
|
||||
Access-Control-Allow-Methods:
|
||||
- GET,POST,OPTIONS
|
||||
Access-Control-Allow-Origin:
|
||||
- https://www.garmin.com
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-Ray:
|
||||
- 7f1ac060fe1846d4-DFW
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Language:
|
||||
- en
|
||||
Content-Type:
|
||||
- text/html;charset=UTF-8
|
||||
Date:
|
||||
- Fri, 04 Aug 2023 23:58:59 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=G8f1H9875FHi4mMW3X7aS9aND4jZAZnlBDAVr3KGz1hFmtUrVwqS3KipwKZMkQ40Rq4vjHtTGn22Fm5%2Bg7VA0XNSbcb3q8f%2BK%2FI0nzh0GsR2fGk%2FH0S4yBe7bsMOwol%2F"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- org.springframework.web.servlet.i18n.CookieLocaleResolver.LOCALE=SANITIZED;
|
||||
Path=SANITIZED
|
||||
- CASTGC=SANITIZED; Path=SANITIZED; Secure; HttpOnly
|
||||
- CASTGC=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED; Secure;
|
||||
HttpOnly
|
||||
- GARMIN-SSO=SANITIZED; Domain=SANITIZED; Path=SANITIZED
|
||||
- GARMIN-SSO=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Domain=SANITIZED;
|
||||
Path=SANITIZED
|
||||
- GarminNoCache=SANITIZED; Domain=SANITIZED; Path=SANITIZED
|
||||
- GarminNoCache=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Domain=SANITIZED;
|
||||
Path=SANITIZED
|
||||
- GarminBuyCacheKey=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Domain=SANITIZED;
|
||||
Path=SANITIZED
|
||||
- GarminBuyCacheKey=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Domain=SANITIZED;
|
||||
Path=SANITIZED
|
||||
- GARMIN-SSO-GUID=SANITIZED; Domain=SANITIZED; Path=SANITIZED
|
||||
- GARMIN-SSO-GUID=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Domain=SANITIZED;
|
||||
Path=SANITIZED
|
||||
- GARMIN-SSO-CUST-GUID=SANITIZED; Domain=SANITIZED; Path=SANITIZED
|
||||
- GARMIN-SSO-CUST-GUID=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Domain=SANITIZED;
|
||||
Path=SANITIZED
|
||||
- CASTGC=SANITIZED; Path=SANITIZED; Secure; HttpOnly
|
||||
- CASTGC=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED; Secure;
|
||||
HttpOnly
|
||||
- __cfruid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
Vary:
|
||||
- Accept-Encoding
|
||||
X-Application-Context:
|
||||
- casServer:cloud,prod,prod-US_1102:3
|
||||
X-B3-Traceid:
|
||||
- 67d13140bb74241f5ffafc30cb590ae9
|
||||
X-Robots-Tag:
|
||||
- noindex
|
||||
X-Vcap-Request-Id:
|
||||
- bb5f25d2-31ab-4879-5100-1898910361aa
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- python-requests/2.31.0
|
||||
method: GET
|
||||
uri: https://thegarth.s3.amazonaws.com/oauth_consumer.json
|
||||
response:
|
||||
body:
|
||||
string: '{"consumer_key": "SANITIZED", "consumer_secret": "SANITIZED"}'
|
||||
headers:
|
||||
Accept-Ranges:
|
||||
- bytes
|
||||
Content-Length:
|
||||
- '124'
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Fri, 04 Aug 2023 23:59:00 GMT
|
||||
ETag:
|
||||
- '"20240b1013cb35419bb5b2cff1407a4e"'
|
||||
Last-Modified:
|
||||
- Thu, 03 Aug 2023 00:16:11 GMT
|
||||
Server:
|
||||
- AmazonS3
|
||||
x-amz-id-2:
|
||||
- MSQpXzot1gVYwlRRZFwqhhl2CaWvGVUOa87bLyZb9cWDw/4XeFMn4LLslomjV8xnr5ejXOTDRjM=
|
||||
x-amz-request-id:
|
||||
- DK3CF9X2YV7R386H
|
||||
x-amz-server-side-encryption:
|
||||
- AES256
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- !!binary |
|
||||
Ki8q
|
||||
Accept-Encoding:
|
||||
- !!binary |
|
||||
Z3ppcCwgZGVmbGF0ZQ==
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- !!binary |
|
||||
a2VlcC1hbGl2ZQ==
|
||||
User-Agent:
|
||||
- !!binary |
|
||||
Y29tLmdhcm1pbi5hbmRyb2lkLmFwcHMuY29ubmVjdG1vYmlsZQ==
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/oauth-service/oauth/preauthorized?ticket=ST-2459533-YeWiOAMHdTmHqEyWcWeI-cas&login-url=https://sso.garmin.com/sso/embed&accepts-mfa-tokens=true
|
||||
response:
|
||||
body:
|
||||
string: oauth_token=SANITIZED&oauth_token_secret=SANITIZED
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 7f1ac06849351556-QRO
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- text/plain;charset=utf-8
|
||||
Date:
|
||||
- Fri, 04 Aug 2023 23:59:00 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=3MpWsX37CvkXmKc8KOczSqUhotPJCTd8xg30IHeNtiLn%2BgRHZ1OLmUEmEzCFRFA7bLdNQMtPMOWGkA4XmepxRdPww%2F92hfzoqcwW3lwmrqWWp9BAJtSvRfdnAfH%2F43eEBK5yLU%2F8CQ%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: ''
|
||||
headers:
|
||||
Accept:
|
||||
- !!binary |
|
||||
Ki8q
|
||||
Accept-Encoding:
|
||||
- !!binary |
|
||||
Z3ppcCwgZGVmbGF0ZQ==
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- !!binary |
|
||||
a2VlcC1hbGl2ZQ==
|
||||
Content-Length:
|
||||
- !!binary |
|
||||
MA==
|
||||
Content-Type:
|
||||
- !!binary |
|
||||
YXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVk
|
||||
User-Agent:
|
||||
- !!binary |
|
||||
Y29tLmdhcm1pbi5hbmRyb2lkLmFwcHMuY29ubmVjdG1vYmlsZQ==
|
||||
method: POST
|
||||
uri: https://connectapi.garmin.com/oauth-service/oauth/exchange/user/2.0
|
||||
response:
|
||||
body:
|
||||
string: '{"scope": "COMMUNITY_COURSE_READ GARMINPAY_WRITE GOLF_API_READ ATP_READ
|
||||
GHS_SAMD GHS_UPLOAD INSIGHTS_READ COMMUNITY_COURSE_WRITE CONNECT_WRITE GCOFFER_WRITE
|
||||
GARMINPAY_READ DT_CLIENT_ANALYTICS_WRITE GOLF_API_WRITE INSIGHTS_WRITE PRODUCT_SEARCH_READ
|
||||
GCOFFER_READ CONNECT_READ ATP_WRITE", "jti": "SANITIZED", "access_token":
|
||||
"SANITIZED", "token_type": "Bearer", "refresh_token": "SANITIZED", "expires_in":
|
||||
102003, "refresh_token_expires_in": 2591999}'
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 7f1ac06b2ea41547-QRO
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Fri, 04 Aug 2023 23:59:00 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=NNCYZLaC3FVERa7klmexZY7Kh3hgtxeCrxhI99bSdoSg4HYUBlJtp%2FgVylwDlkFrU%2B0S5Ufb69lEQjC583RGLoO6U8PZFyHu1Bopl%2B%2BXq0QwIoOs36XBwicE1xqC7%2B7IWFi7pOAhKw%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cache-control:
|
||||
- no-cache, no-store, private
|
||||
pragma:
|
||||
- no-cache
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
1041
garth/tests/cassettes/test_login_success_mfa.yaml
Normal file
1041
garth/tests/cassettes/test_login_success_mfa.yaml
Normal file
File diff suppressed because it is too large
Load Diff
1144
garth/tests/cassettes/test_login_success_mfa_async.yaml
Normal file
1144
garth/tests/cassettes/test_login_success_mfa_async.yaml
Normal file
File diff suppressed because it is too large
Load Diff
82
garth/tests/cassettes/test_profile_alias.yaml
Normal file
82
garth/tests/cassettes/test_profile_alias.yaml
Normal file
@@ -0,0 +1,82 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- GCM-iOS-5.7.2.1
|
||||
referer:
|
||||
- https://sso.garmin.com/sso/verifyMFA/loginEnterMfaCode?id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&service=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&source=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountLoginUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountCreationUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/userprofile-service/socialProfile
|
||||
response:
|
||||
body:
|
||||
string: '{"id": 3154645, "profileId": 2591602, "garminGUID": "0690cc1d-d23d-4412-b027-80fd4ed1c0f6",
|
||||
"displayName": "mtamizi", "fullName": "Matin Tamizi", "userName": "mtamizi",
|
||||
"profileImageType": "UPLOADED_PHOTO", "profileImageUrlLarge": "https://s3.amazonaws.com/garmin-connect-prod/profile_images/73240e81-6e4d-43fc-8af8-c8f6c51b3b8f-2591602.png",
|
||||
"profileImageUrlMedium": "https://s3.amazonaws.com/garmin-connect-prod/profile_images/685a19e9-a7be-4a11-9bf9-faca0c5d1f1a-2591602.png",
|
||||
"profileImageUrlSmall": "https://s3.amazonaws.com/garmin-connect-prod/profile_images/6302f021-0ec7-4dc9-b0c3-d5a19bc5a08c-2591602.png",
|
||||
"location": "Ciudad de M\u00e9xico, CDMX", "facebookUrl": null, "twitterUrl":
|
||||
null, "personalWebsite": null, "motivation": null, "bio": null, "primaryActivity":
|
||||
null, "favoriteActivityTypes": [], "runningTrainingSpeed": 0.0, "cyclingTrainingSpeed":
|
||||
0.0, "favoriteCyclingActivityTypes": [], "cyclingClassification": null, "cyclingMaxAvgPower":
|
||||
0.0, "swimmingTrainingSpeed": 0.0, "profileVisibility": "private", "activityStartVisibility":
|
||||
"private", "activityMapVisibility": "public", "courseVisibility": "public",
|
||||
"activityHeartRateVisibility": "public", "activityPowerVisibility": "public",
|
||||
"badgeVisibility": "private", "showAge": false, "showWeight": false, "showHeight":
|
||||
false, "showWeightClass": false, "showAgeRange": false, "showGender": false,
|
||||
"showActivityClass": false, "showVO2Max": false, "showPersonalRecords": false,
|
||||
"showLast12Months": false, "showLifetimeTotals": false, "showUpcomingEvents":
|
||||
false, "showRecentFavorites": false, "showRecentDevice": false, "showRecentGear":
|
||||
false, "showBadges": true, "otherActivity": null, "otherPrimaryActivity":
|
||||
null, "otherMotivation": null, "userRoles": ["SCOPE_ATP_READ", "SCOPE_ATP_WRITE",
|
||||
"SCOPE_COMMUNITY_COURSE_READ", "SCOPE_COMMUNITY_COURSE_WRITE", "SCOPE_CONNECT_READ",
|
||||
"SCOPE_CONNECT_WRITE", "SCOPE_DT_CLIENT_ANALYTICS_WRITE", "SCOPE_GARMINPAY_READ",
|
||||
"SCOPE_GARMINPAY_WRITE", "SCOPE_GCOFFER_READ", "SCOPE_GCOFFER_WRITE", "SCOPE_GHS_SAMD",
|
||||
"SCOPE_GHS_UPLOAD", "SCOPE_GOLF_API_READ", "SCOPE_GOLF_API_WRITE", "SCOPE_INSIGHTS_READ",
|
||||
"SCOPE_INSIGHTS_WRITE", "SCOPE_OMT_CAMPAIGN_READ", "SCOPE_OMT_SUBSCRIPTION_READ",
|
||||
"SCOPE_PRODUCT_SEARCH_READ", "ROLE_CONNECTUSER", "ROLE_FITNESS_USER", "ROLE_WELLNESS_USER",
|
||||
"ROLE_OUTDOOR_USER", "ROLE_CONNECT_2_USER", "ROLE_TACX_APP_USER"], "nameApproved":
|
||||
true, "userProfileFullName": "Matin Tamizi", "makeGolfScorecardsPrivate":
|
||||
true, "allowGolfLiveScoring": false, "allowGolfScoringByConnections": true,
|
||||
"userLevel": 4, "userPoint": 166, "levelUpdateDate": "2024-01-31T12:18:59.0",
|
||||
"levelIsViewed": false, "levelPointThreshold": 300, "userPointOffset": 0,
|
||||
"userPro": false}'
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 8f0d992ebcfb6c34-DFW
|
||||
Cache-Control:
|
||||
- no-cache, no-store, private
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json;charset=UTF-8
|
||||
Date:
|
||||
- Thu, 12 Dec 2024 12:08:11 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=niRDLu7E86gruuRnFLQL4RUQO9vMZyAA5NQ64mt7vW0Gmmsl2%2BCut6v2k9Pj6yXDDr42mqOH%2B%2BvYhVgRbnkLUTD3xXo3YREb7jiUYBGZhBKpRQafvfz0TvC63Co%2BVJCaehygZ%2B9HlA%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
pragma:
|
||||
- no-cache
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
133
garth/tests/cassettes/test_put.yaml
Normal file
133
garth/tests/cassettes/test_put.yaml
Normal file
@@ -0,0 +1,133 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: '[{"changeState": "CHANGED", "trainingMethod": "HR_RESERVE", "lactateThresholdHeartRateUsed":
|
||||
170, "maxHeartRateUsed": 185, "restingHrAutoUpdateUsed": false, "sport": "DEFAULT",
|
||||
"zone1Floor": 130, "zone2Floor": 140, "zone3Floor": 150, "zone4Floor": 160,
|
||||
"zone5Floor": 170}]'
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Length:
|
||||
- '272'
|
||||
Content-Type:
|
||||
- application/json
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: PUT
|
||||
uri: https://connectapi.garmin.com/biometric-service/heartRateZones
|
||||
response:
|
||||
body:
|
||||
string: ''
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 80f7523e7a572165-MAD
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json;charset=UTF-8
|
||||
Date:
|
||||
- Sun, 01 Oct 2023 20:05:35 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=CgUHNfj6nCHwLWrYOFgl0OXLp6K6aHVOe5Mh7xn1vbd30JRWu6lJQSC4quw%2FLJFh%2BXkgEOsJ8R2dJ1lX7s1TLQkg5F6rm9mey6S40Yele44L6ykjlVQmfQZPBRBCH7rl%2Bdb%2B0w393g%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cache-control:
|
||||
- no-cache, no-store, private
|
||||
pragma:
|
||||
- no-cache
|
||||
set-cookie:
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
status:
|
||||
code: 204
|
||||
message: No Content
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
Cookie:
|
||||
- ADRUM_BTa=SANITIZED; SameSite=SANITIZED; ADRUM_BT1=SANITIZED; _cfuvid=SANITIZED
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/biometric-service/heartRateZones
|
||||
response:
|
||||
body:
|
||||
string: '[{"trainingMethod": "HR_RESERVE", "restingHeartRateUsed": 42, "lactateThresholdHeartRateUsed":
|
||||
170, "zone1Floor": 130, "zone2Floor": 140, "zone3Floor": 150, "zone4Floor":
|
||||
160, "zone5Floor": 170, "maxHeartRateUsed": 185, "restingHrAutoUpdateUsed":
|
||||
false, "sport": "DEFAULT", "changeState": "UNCHANGED"}]'
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 80f752435bf2666b-MAD
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json;charset=UTF-8
|
||||
Date:
|
||||
- Sun, 01 Oct 2023 20:05:36 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=kkTPrb69Gsx4APeQBgtestcf2%2BIDf9pLcQhn1Bv3hfXr%2FH1B09AZNsqL%2Bk5eQygFQbE0F9pZzdJ1XmqfHBql6tqQX5pTwiDiAHy7jV2hy7qnCZmCtlV3REKoEo%2FZ0oIEtX7vyn7FRg%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cache-control:
|
||||
- no-cache, no-store, private
|
||||
pragma:
|
||||
- no-cache
|
||||
set-cookie:
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
193
garth/tests/cassettes/test_refresh_oauth2_token.yaml
Normal file
193
garth/tests/cassettes/test_refresh_oauth2_token.yaml
Normal file
@@ -0,0 +1,193 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- python-requests/2.31.0
|
||||
method: GET
|
||||
uri: https://thegarth.s3.amazonaws.com/oauth_consumer.json
|
||||
response:
|
||||
body:
|
||||
string: '{"consumer_key": "SANITIZED", "consumer_secret": "SANITIZED"}'
|
||||
headers:
|
||||
Accept-Ranges:
|
||||
- bytes
|
||||
Content-Length:
|
||||
- '124'
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Sun, 06 Aug 2023 14:28:00 GMT
|
||||
ETag:
|
||||
- '"20240b1013cb35419bb5b2cff1407a4e"'
|
||||
Last-Modified:
|
||||
- Thu, 03 Aug 2023 00:16:11 GMT
|
||||
Server:
|
||||
- AmazonS3
|
||||
x-amz-id-2:
|
||||
- 9tePW1jJAYQ3uv6MTzPUchn0ZP+JWMF2a9Zc1mB8quIAtuIxVG/I/LUQHhTiZgd8wChW+eR2PA1ATnw0Tkjldw==
|
||||
x-amz-request-id:
|
||||
- 07RRD5QKQXX1GET8
|
||||
x-amz-server-side-encryption:
|
||||
- AES256
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: mfa_token=MFA-1578-9cKDf5iVPDPdBpT3HZtpEhyIZ4Bmger7gIpit5siAlFPGPrVJk-cas
|
||||
headers:
|
||||
Accept:
|
||||
- !!binary |
|
||||
Ki8q
|
||||
Accept-Encoding:
|
||||
- !!binary |
|
||||
Z3ppcCwgZGVmbGF0ZQ==
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- !!binary |
|
||||
a2VlcC1hbGl2ZQ==
|
||||
Content-Length:
|
||||
- '73'
|
||||
Content-Type:
|
||||
- !!binary |
|
||||
YXBwbGljYXRpb24veC13d3ctZm9ybS11cmxlbmNvZGVk
|
||||
User-Agent:
|
||||
- !!binary |
|
||||
Y29tLmdhcm1pbi5hbmRyb2lkLmFwcHMuY29ubmVjdG1vYmlsZQ==
|
||||
method: POST
|
||||
uri: https://connectapi.garmin.com/oauth-service/oauth/exchange/user/2.0
|
||||
response:
|
||||
body:
|
||||
string: '{"scope": "COMMUNITY_COURSE_READ GARMINPAY_WRITE GOLF_API_READ ATP_READ
|
||||
GHS_SAMD GHS_UPLOAD INSIGHTS_READ COMMUNITY_COURSE_WRITE CONNECT_WRITE GCOFFER_WRITE
|
||||
GARMINPAY_READ DT_CLIENT_ANALYTICS_WRITE GOLF_API_WRITE INSIGHTS_WRITE PRODUCT_SEARCH_READ
|
||||
GCOFFER_READ CONNECT_READ ATP_WRITE", "jti": "SANITIZED", "access_token":
|
||||
"SANITIZED", "token_type": "Bearer", "refresh_token": "SANITIZED", "expires_in":
|
||||
65481, "refresh_token_expires_in": 2591999}'
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 7f27f6bfcef57ea5-LAX
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Sun, 06 Aug 2023 14:28:01 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=1gipzcbhAhY0bP2r3RhDsanmOc8Hzo0tPhViR5ht3fD6bJu%2B7FT8M8H333APSe3wQ4WHJUwlX3xu0DvvdK3SbcTDTNWWEf3aMp1wQ0D7llAZ3%2Bjbf1ZBEE53kH2xR%2BCpZRg%2FQwx9fg%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cache-control:
|
||||
- no-cache, no-store, private
|
||||
pragma:
|
||||
- no-cache
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/userprofile-service/socialProfile
|
||||
response:
|
||||
body:
|
||||
string: '{"id": 3154645, "profileId": 2591602, "garminGUID": "0690cc1d-d23d-4412-b027-80fd4ed1c0f6",
|
||||
"displayName": "mtamizi", "fullName": "Matin Tamizi", "userName": "mtamizi",
|
||||
"profileImageUuid": "73240e81-6e4d-43fc-8af8-c8f6c51b3b8f", "profileImageUrlLarge":
|
||||
"https://s3.amazonaws.com/garmin-connect-prod/profile_images/73240e81-6e4d-43fc-8af8-c8f6c51b3b8f-2591602.png",
|
||||
"profileImageUrlMedium": "https://s3.amazonaws.com/garmin-connect-prod/profile_images/685a19e9-a7be-4a11-9bf9-faca0c5d1f1a-2591602.png",
|
||||
"profileImageUrlSmall": "https://s3.amazonaws.com/garmin-connect-prod/profile_images/6302f021-0ec7-4dc9-b0c3-d5a19bc5a08c-2591602.png",
|
||||
"location": "Ciudad de M\u00e9xico, CDMX", "facebookUrl": null, "twitterUrl":
|
||||
null, "personalWebsite": null, "motivation": null, "bio": null, "primaryActivity":
|
||||
null, "favoriteActivityTypes": [], "runningTrainingSpeed": 0.0, "cyclingTrainingSpeed":
|
||||
0.0, "favoriteCyclingActivityTypes": [], "cyclingClassification": null, "cyclingMaxAvgPower":
|
||||
0.0, "swimmingTrainingSpeed": 0.0, "profileVisibility": "private", "activityStartVisibility":
|
||||
"private", "activityMapVisibility": "public", "courseVisibility": "public",
|
||||
"activityHeartRateVisibility": "public", "activityPowerVisibility": "public",
|
||||
"badgeVisibility": "private", "showAge": false, "showWeight": false, "showHeight":
|
||||
false, "showWeightClass": false, "showAgeRange": false, "showGender": false,
|
||||
"showActivityClass": false, "showVO2Max": false, "showPersonalRecords": false,
|
||||
"showLast12Months": false, "showLifetimeTotals": false, "showUpcomingEvents":
|
||||
false, "showRecentFavorites": false, "showRecentDevice": false, "showRecentGear":
|
||||
false, "showBadges": true, "otherActivity": null, "otherPrimaryActivity":
|
||||
null, "otherMotivation": null, "userRoles": ["SCOPE_ATP_READ", "SCOPE_ATP_WRITE",
|
||||
"SCOPE_COMMUNITY_COURSE_READ", "SCOPE_COMMUNITY_COURSE_WRITE", "SCOPE_CONNECT_READ",
|
||||
"SCOPE_CONNECT_WRITE", "SCOPE_DT_CLIENT_ANALYTICS_WRITE", "SCOPE_GARMINPAY_READ",
|
||||
"SCOPE_GARMINPAY_WRITE", "SCOPE_GCOFFER_READ", "SCOPE_GCOFFER_WRITE", "SCOPE_GHS_SAMD",
|
||||
"SCOPE_GHS_UPLOAD", "SCOPE_GOLF_API_READ", "SCOPE_GOLF_API_WRITE", "SCOPE_INSIGHTS_READ",
|
||||
"SCOPE_INSIGHTS_WRITE", "SCOPE_PRODUCT_SEARCH_READ", "ROLE_CONNECTUSER", "ROLE_FITNESS_USER",
|
||||
"ROLE_WELLNESS_USER", "ROLE_OUTDOOR_USER", "ROLE_CONNECT_2_USER", "ROLE_TACX_APP_USER"],
|
||||
"nameApproved": true, "userProfileFullName": "Matin Tamizi", "makeGolfScorecardsPrivate":
|
||||
true, "allowGolfLiveScoring": false, "allowGolfScoringByConnections": true,
|
||||
"userLevel": 3, "userPoint": 117, "levelUpdateDate": "2020-12-12T15:20:38.0",
|
||||
"levelIsViewed": false, "levelPointThreshold": 140, "userPointOffset": 0,
|
||||
"userPro": false}'
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 7f27f6cc6d8ecfb4-SJC
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json;charset=UTF-8
|
||||
Date:
|
||||
- Sun, 06 Aug 2023 14:28:02 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=vQb1I7nl3NUQFoAFFYc8Ez1h%2Br4sa%2FdhPV%2FmnWwcDKVnx9u5zcZbfNerjOwsMYYxfu60yEfBHhJjfrZuTtiFPW6oJF0TsOcft20SZ2E4CrbOEPJuYqkHg05AR%2BKaWXab1WNJ3csk9Q%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cache-control:
|
||||
- no-cache, no-store, private
|
||||
pragma:
|
||||
- no-cache
|
||||
set-cookie:
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
1018
garth/tests/cassettes/test_resume_login.yaml
Normal file
1018
garth/tests/cassettes/test_resume_login.yaml
Normal file
File diff suppressed because it is too large
Load Diff
181
garth/tests/cassettes/test_upload.yaml
Normal file
181
garth/tests/cassettes/test_upload.yaml
Normal file
@@ -0,0 +1,181 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: !!binary |
|
||||
LS0yYTllN2ZiNzAxNjU4NzI1M2M0ZmIwNWZhMGFkMzZkOQ0KQ29udGVudC1EaXNwb3NpdGlvbjog
|
||||
Zm9ybS1kYXRhOyBuYW1lPSJmaWxlIjsgZmlsZW5hbWU9IjEyMTI5MTE1NzI2X0FDVElWSVRZLmZp
|
||||
dCINCg0KDhB5UpkUAAAuRklUyeVAAAAAAAcDBIwEBIYHBIYBAoQCAoQFAoQAAQAAOXF7xhLKeD//
|
||||
////AQDbDP//BEEAADEABQIUBwAChAEBAgMBAAQBAAEAAAAAAAAAAAAAAAAAAAAAAAAAACgK////
|
||||
QgAAIAEE/QSGAgKEAAEBAQEBAhHKeD///39/QwAARwEI/QSGAwSGBASGBQSGAAECAQECAgECBgEA
|
||||
AxHKeD8MAAAAoCAAAAQAAAAJAQIAAxHKeD8MAAAAoCAAAAQAAAAJAQIARAAARgED/QSGAASGAQSG
|
||||
BBHKeD8xAAAAAwAAAEUAABUAB/0EhgMEhgABAAEBAAQBAhMBAhQBAgURyng/AAAAAAAAAP//RgAA
|
||||
FwAc/QSGAwSMBwSGCASGDwSGEASGESAHGASMHwSGAgKEBAKEBQKECgKEDQKEFQKLAAECAQECBgEC
|
||||
CQECCwECEgEAFAEKFgEAFwECGQEAHQYCHgECIAECBhHKeD85cXvG/////////////////////wAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP////8BANsMKAr/////AAAA//////8A
|
||||
//8F//////////8GEcp4PwAAAAD/////////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAA/////wEA2wwoCv////8AAAEE/////wD//wX//////////wYRyng/AAAA
|
||||
AP////////////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////
|
||||
/////////////wAAAgj/////AP//Bf//////////BhHKeD8AAAAA/////////////////////wAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//////////ZAD/////AAADCv////8A
|
||||
//8F//////////8GEcp4PwAAAAD/////////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAA/////wEA4AyhCP////8AAAQM/////wD//wX//////////0cAABYAC/0E
|
||||
hgABAgEBAgIBAgMBAgQBAgUBAAYBAggBAg4BAg8BAgcRyng//////wMD/////0gAAI0ABf0EhgEE
|
||||
hgIEhgMEhgABAAgRyng/AHZ3P4CwgD//////AUkAAAIAawEEhgIEhloEhqdABwgChDEChDoChDsC
|
||||
hAABAgMBAAQBAAUBAQkBAAoBAAsBAAwBAA0BAg4BAg8BAhYBABcBABkBABoBABwBAB4BAB8BACIB
|
||||
ACMBACQBACYBACkBACoBACsBACwBAC0BAC4BAC8BADABADUBADYBADcBADgBAD8NAEABAEEBAEIB
|
||||
AEMBAEQBAEUBAEsBAEwBAE0BAFABAFEBAFIBAFMBAFQBAFUBAFcBAF4BAmABAGEBCmIBAGUBAGge
|
||||
AGsBAGwBAG0BAG4BAG8BAHABAHQBAHUBAHwBAn0BAn4BAH8BAIABAIEBAIIBAIMBAIQBAIUBAIkB
|
||||
AIoBAIsBAI0BAo4BAo8BApABAJEBAJQBAJUBAJcBAKABAKEBAKMBAKQBAqgBAKoPArEBArIBArMB
|
||||
ALQBALUBANoBANsBAAkAAAAAoKv//////38AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/////9AH8AAAAgEA/wADAwQeMgABAf7/
|
||||
Af//AQEBAQEA/wEAAQAB//8BAQAAAAAAAAEAAQABAQEAAgEBAwEBCQEBAQIAAQH/AAMAAgAhBBAO
|
||||
DAYaAwId/////////////////////////wEBAQEBAQACMgQBAQIAAQIAAQAAAQMAZAEBAQAAAQAA
|
||||
KAAIBwkNCwwFBg4KAQQAAgMFHgEBAAEBSgAAAwAkHASGHQSGKQSGKgSGBAKEHwKEIAKEIQKEJQKE
|
||||
JgKEAQEAAwECBQEABgEABwEACAECDAEADQEADgEAEQEAEgEAFQEAGAECHgEAJAECKwEALAECLQEK
|
||||
LwEANAEANQECNgECOQEAOgEAPAcAPgEACsBdAAAQOgEA/ChFPv////9FA////////30A//8BtgAA
|
||||
AAABAAAyAgBUAAAAEQoBAAMEAAEAAAAAAAICAUsAAJMAQQAEjAIQBw0EhhIEChQMCkAEhkEEhkYE
|
||||
hv4ChAoChAsChBUChBkChBoChCAChCEChCIChCMChCgCizcChDgChDkChEkCi1UChAEBAgMBAAQB
|
||||
AAUBAAYBAAcBAAkBAgwBAg4BAg8BAhABAhEBChMBChgBAB8BAiQBACUHACYHACcHACkHAioBACsB
|
||||
ACwBCi0BAC4BAC8BADABADIGAjMBADQBADUBAjoBADsBAjwBAD0BAD4BAj8BAEcBAFIBAFYBAFcB
|
||||
AAvkR3ihSFJNLVBybzo2NzM3NjQAAP////8AAAAAAAAAAAAAAAAAAAAA////////////////AAD/
|
||||
////////////5AwBAHAD//8AAP///////wMA//8AAf///////////wAA////////////////////
|
||||
////////////////////////vwEB//9U5BJ+o90AAAH/////AP//////CwAAAABCZWF0cyBGaXQg
|
||||
UHJvAAAA/////wAAAAAAAAAAAAAAAAAAAAD///////////////8BAP//////////////////////
|
||||
/wAA////////BAD///8B////////////AAD/////////////////////////////////////////
|
||||
//8A//////TUiNzp7wIW//////////////9MAABPACf9BIYQBIYRBIUSBIUTBIUVBIUWBIUZBIYa
|
||||
BIYdBIYeBIYjBIYkBIUAAoQDAoQIAoQJAoQLAoQMAoQNAoQUAoQXAoMhAoQiAoQlAoMBAQICAQIE
|
||||
AQAFAQAGAQIHAQEKAQIOAQIPAQIYAQIbAQIcAQIfAQIgAQIMEcp4P0MEN+sHwQwAi70OACTADABP
|
||||
cAEAIdAeAMBdAAAQOgEAfDIpAGj+ZgCSAizrHTEWAAQzPgMBAH8CqADIAH0AAACY/gAAswAAACa2
|
||||
AUa+ASoAGf8MAP//TQAADAANAyAHCgQCAAEAAQEABQEABgEACQECCwEADAECDQEADwEAEwMAFQEA
|
||||
DVlvZ2EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/1UA/worAQAAAf8A/wD//wFOAAANAEsE
|
||||
BIYFBIUGBIUfBIYhBIYpBIU2BIZEBIVJEIVKEIVaBIb+AoQCAoQIAoQWAoQgAoRAAoQBAQADAQAH
|
||||
AQALAQIMAQANAQAOAQAPAQAQAQAVAQAXAQAYAQAbAQAcAQAdAQAeAQAiAQAjAQAkAQAlAQAmAQIn
|
||||
AQIoAQAqAQIrAQAsAQAtAQAuAQAvAQAwAQAzAQA1AQA3AQA5AQA8AQA+AQA/AQBBAQBCAQBHAQBI
|
||||
AQBLAQBMAQBOAQBPAQBRAQBSAQBTAQBVAQBWAQJZAQBdAQBeAQBoAQBrAQBsAQB0AQB4AQAOoIYB
|
||||
AP///3////9/pnQCAP////8bQQAA/////////3////9/////f////3////9/////f////3////9/
|
||||
////f/////8AAEcQbQX//zIaTQAABgAKAAAAAAH//wAA//8AAQAAAP//AR7///8B/////wD/Af8A
|
||||
AAD///8AAf///////wD///8AAP//TwAABwANEASG/gKEAwKECAKEDwKEAQECAgECBQEABwEACQEA
|
||||
CgEACwEADAEAD/////8AAMgA/////76oAQEAAQEAQAAAFAAH/QSGdAKEAwECDQEBhgEChwECiAEC
|
||||
ABHKeD+cGFYi/7BWQQAA6QABAgQNARsBBAAAEsp4P5wYVSL/sFUBHqAPCwATyng/nBhWIv+wVgEI
|
||||
AAAQABTKeD+cGFci/7BXQgAA4QAO/gSGAASGBgSGAwKEBAKEBwaECAaECQKECgKECwKEDAKEDQKE
|
||||
AgMKBQECAhTKeD9lCwAAEcp4P////////////////////////wAA////////AAAAAUMAANgADf0E
|
||||
hgIchgUghgkQhAAChAEChA8ChAYGAgoBAAsBAgwBAg0BAg4BAAMUyng/ywoAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAP//////////////////////////////////////////////////////////
|
||||
/////9gAAAD//3yJlqOxvgG+AKj/AQoAABgAFcp4P5wYViL/sFYBCkAJIAAWyng/nBhWIv+wVgEE
|
||||
XgUoABjKeD+cGFki/7BZAcAf8DcAGcp4P5wYWCL/sFgCGcp4PyUPAAAUyng/////////////////
|
||||
////////AQD///////8AAAABAxnKeD8gDwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////
|
||||
////////////////////////////////////////////////////////2AABAP//fImWo7G+Ab4A
|
||||
qP8BrsgCOAAayng/cBdYIv+wWAEoZCBAABvKeD9wF1ci/7BXBRvKeD8AAAAAAAQA//8GG8p4Pzlx
|
||||
e8b/////////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////
|
||||
/wEA2wwoCv////8AAAD//////wD//wX//////////wYbyng/AAAAAP////////////////////8A
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////AQDbDCgK/////wAAAQT/////
|
||||
AP//Bf//////////BhvKeD8AAAAA/////////////////////wAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAAAAAAAAAAAAAAAAAP//////////////////AAACCP////8A//8F//////////8GG8p4PwAA
|
||||
AAD/////////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////
|
||||
//////9kAP////8AAAMK/////wD//wX//////////wYbyng/AAAAAP////////////////////8A
|
||||
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/////AQDgDKEI/////wAABAz/////
|
||||
AP//Bf//////////AQwADEgBAAAAUAEbAQQAAR6gDwsBEgAAEAEKAAAYAiDKeD9CCwAAGcp4P///
|
||||
/////////////////////wIA////////AAAAAQMgyng/PgsAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
AAAAAP///////////////////////////////////////////////////////////////9gAAgD/
|
||||
/3yJlqOxvgG+AKj/BSHKeD/YAAAAMAMB//9EAACMADv9BIYCBIUDBIUFBIUGBIUHBIUVBIUYBIUa
|
||||
BIUbBIUcBIUdBIUgBIUjBIYkBIYlBIUmBIUnBIUoBIUwBIY2BIUJAoQKAoQOAoQPAoQQAoQrAoMs
|
||||
AoMtAoM3AoQ5AoQ6AoQAAQIBAQIEAQIIAQELAQAMAQANAQIRAQESAQITAQIUAQIWAQIXAQIZAQIe
|
||||
AQEfAQEhAQIiAQApAQAqAQIuAQIxAQIyAQIzAQA0AQA1AQI4AQIEIcp4P8QUAADEFAAAn9MEAKJD
|
||||
AQAAAAAA+KYVALXAGwDEFAAAAAAAAAAAAAAAAAAAAAAgTucmAAD/////xBQAAAAAAADEFAAAAAAA
|
||||
AMF1eD8AAAAAAQCn8AAAAAAAAAAAAAAAAAAA/////1oAAAAKKwAAARQAEgAZnP8AAAD/Av//AP8A
|
||||
CkcAABIAdP0EhgIEhgMEhQQEhQcEhggEhgkEhgoEhh0EhR4EhR8EhSAEhSYEhScEhSkEhjAEhk4E
|
||||
hm4gB3AEhnQEAnUEAnYEAncEAngEhHkEhHwEhn0EhpgEhqgEhbUEiLsEiP4ChAsChA4ChA8ChBQC
|
||||
hBUChBYChBcChBkChBoChCEChCIChCMChCQChCUChCoChCwChC0ChC8ChE8ChFAChFkChFoChFsC
|
||||
hGoChGsChGwChHEChIQChIUChIYChJcChJ0ChJ4ChKkChKoChLEChLIChLMChLQChL0ChL4ChMQC
|
||||
hAABAAEBAAUBAAYBABABAhEBAhIBAhMBAhgBAhsBAhwBACsBAC4BADkBAToBAVEBAFwBAl0BAl4B
|
||||
AmUBAmYBAmcBAmgBAmkBAm0BAnIBAXMBAXoCAnsCAokBAooCApYBAbgBALwBAMABAsEBAsIBAsMB
|
||||
AscBAsgBAskBAsoBAgchyng/Ecp4P////3////9/zCUAAMwlAAD/////AAAAAP///3////9/////
|
||||
f////3////9/////f///////////zCUAAFlvZ2EAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
|
||||
////////////////////////////////////////////////AAAAAMQUAAD//////////wAAAQD/
|
||||
//////////////8AAAMA/////////////////////////////////////wAA////////////////
|
||||
AwD/////////////AQD/////nBhwFwAACQEKK1dZ//8A/wD//yIiAP///////////39//////wAS
|
||||
ACIAAP///z7//z//AyHKeD8pJQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA////////////////
|
||||
////////////////////////////////////////////////EgAAAP//fImWo7G+Ab4AqP9IAAAi
|
||||
AAn9BIYABIYFBIYBAoQCAQADAQAEAQAGAQIHAQIIIcp4P8wlAADBdXg/AQAAGgH//z81DQotLTJh
|
||||
OWU3ZmI3MDE2NTg3MjUzYzRmYjA1ZmEwYWQzNmQ5LS0NCg==
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Length:
|
||||
- '5449'
|
||||
Content-Type:
|
||||
- multipart/form-data; boundary=2a9e7fb7016587253c4fb05fa0ad36d9
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: POST
|
||||
uri: https://connectapi.garmin.com/upload-service/upload
|
||||
response:
|
||||
body:
|
||||
string: '{"detailedImportResult": {"uploadId": 212157427938, "uploadUuid": {"uuid":
|
||||
"6e56051d-1dd4-4f2c-b8ba-00a1a7d82eb3"}, "owner": 2591602, "fileSize": 5289,
|
||||
"processingTime": 36, "creationDate": "2023-09-29 01:58:19.113 GMT", "ipAddress":
|
||||
null, "fileName": "12129115726_ACTIVITY.fit", "report": null, "successes":
|
||||
[], "failures": []}}'
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 80e09ed13b9a2e61-DFW
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Length:
|
||||
- '306'
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Fri, 29 Sep 2023 01:58:19 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=quFuhjwXbu43jBRTUG6ygPyLzpTrl7hb9J0FKhbc18TnrdjIvIWV8RmYcKVuXmw6QNmCb9E7IxGdoO0h1zUCKS0JhGmlfUxPi39dpZ%2Fz80FyuPPGQSSsSBY9SmgJAWComnBkN5LiNw%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cache-control:
|
||||
- no-cache, no-store, private
|
||||
location:
|
||||
- https://connectapi.garmin.com/activity-service/activity/status/1695952699113/6e56051d1dd44f2cb8ba00a1a7d82eb3
|
||||
location-in-milliseconds:
|
||||
- '1000'
|
||||
pragma:
|
||||
- no-cache
|
||||
set-cookie:
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
status:
|
||||
code: 202
|
||||
message: Accepted
|
||||
version: 1
|
||||
105
garth/tests/cassettes/test_user_profile.yaml
Normal file
105
garth/tests/cassettes/test_user_profile.yaml
Normal file
@@ -0,0 +1,105 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
referer:
|
||||
- https://sso.garmin.com/sso/verifyMFA/loginEnterMfaCode?id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&service=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&source=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountLoginUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountCreationUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/userprofile-service/socialProfile
|
||||
response:
|
||||
body:
|
||||
string: '{"id": 3154645, "profileId": 2591602, "garminGUID": "0690cc1d-d23d-4412-b027-80fd4ed1c0f6",
|
||||
"displayName": "mtamizi", "fullName": "Matin Tamizi", "userName": "mtamizi",
|
||||
"profileImageType": "UPLOADED_PHOTO", "profileImageUrlLarge":
|
||||
"https://s3.amazonaws.com/garmin-connect-prod/profile_images/73240e81-6e4d-43fc-8af8-c8f6c51b3b8f-2591602.png",
|
||||
"profileImageUrlMedium": "https://s3.amazonaws.com/garmin-connect-prod/profile_images/685a19e9-a7be-4a11-9bf9-faca0c5d1f1a-2591602.png",
|
||||
"profileImageUrlSmall": "https://s3.amazonaws.com/garmin-connect-prod/profile_images/6302f021-0ec7-4dc9-b0c3-d5a19bc5a08c-2591602.png",
|
||||
"location": "Ciudad de M\u00e9xico, CDMX", "facebookUrl": null, "twitterUrl":
|
||||
null, "personalWebsite": null, "motivation": null, "bio": null, "primaryActivity":
|
||||
null, "favoriteActivityTypes": [], "runningTrainingSpeed": 0.0, "cyclingTrainingSpeed":
|
||||
0.0, "favoriteCyclingActivityTypes": [], "cyclingClassification": null, "cyclingMaxAvgPower":
|
||||
0.0, "swimmingTrainingSpeed": 0.0, "profileVisibility": "private", "activityStartVisibility":
|
||||
"private", "activityMapVisibility": "public", "courseVisibility": "public",
|
||||
"activityHeartRateVisibility": "public", "activityPowerVisibility": "public",
|
||||
"badgeVisibility": "private", "showAge": false, "showWeight": false, "showHeight":
|
||||
false, "showWeightClass": false, "showAgeRange": false, "showGender": false,
|
||||
"showActivityClass": false, "showVO2Max": false, "showPersonalRecords": false,
|
||||
"showLast12Months": false, "showLifetimeTotals": false, "showUpcomingEvents":
|
||||
false, "showRecentFavorites": false, "showRecentDevice": false, "showRecentGear":
|
||||
false, "showBadges": true, "otherActivity": null, "otherPrimaryActivity":
|
||||
null, "otherMotivation": null, "userRoles": ["SCOPE_ATP_READ", "SCOPE_ATP_WRITE",
|
||||
"SCOPE_COMMUNITY_COURSE_READ", "SCOPE_COMMUNITY_COURSE_WRITE", "SCOPE_CONNECT_READ",
|
||||
"SCOPE_CONNECT_WRITE", "SCOPE_DT_CLIENT_ANALYTICS_WRITE", "SCOPE_GARMINPAY_READ",
|
||||
"SCOPE_GARMINPAY_WRITE", "SCOPE_GCOFFER_READ", "SCOPE_GCOFFER_WRITE", "SCOPE_GHS_SAMD",
|
||||
"SCOPE_GHS_UPLOAD", "SCOPE_GOLF_API_READ", "SCOPE_GOLF_API_WRITE", "SCOPE_INSIGHTS_READ",
|
||||
"SCOPE_INSIGHTS_WRITE", "SCOPE_PRODUCT_SEARCH_READ", "ROLE_CONNECTUSER", "ROLE_FITNESS_USER",
|
||||
"ROLE_WELLNESS_USER", "ROLE_OUTDOOR_USER", "ROLE_CONNECT_2_USER", "ROLE_TACX_APP_USER"],
|
||||
"nameApproved": true, "userProfileFullName": "Matin Tamizi", "makeGolfScorecardsPrivate":
|
||||
true, "allowGolfLiveScoring": false, "allowGolfScoringByConnections": true,
|
||||
"userLevel": 3, "userPoint": 118, "levelUpdateDate": "2020-12-12T15:20:38.0",
|
||||
"levelIsViewed": false, "levelPointThreshold": 140, "userPointOffset": 0,
|
||||
"userPro": false}'
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 8151c9a72daf49d9-MFE
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json;charset=UTF-8
|
||||
Date:
|
||||
- Thu, 12 Oct 2023 19:35:44 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=R8lDBzpYOR3%2FKDhkcX7x9WXPLE2Ta1MmqgZzYqack%2F9HmkidgIW8z0cDhgWQ6rWprYuN3oyv%2FS5AGsx8HRk8jJf7qR%2B6ogw0VgKh%2BXZXH83duGw3x3g492pKPeIj6hazTlIxSeK6HA%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cache-control:
|
||||
- no-cache, no-store, private
|
||||
pragma:
|
||||
- no-cache
|
||||
set-cookie:
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
92
garth/tests/cassettes/test_user_settings.yaml
Normal file
92
garth/tests/cassettes/test_user_settings.yaml
Normal file
@@ -0,0 +1,92 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
referer:
|
||||
- https://sso.garmin.com/sso/verifyMFA/loginEnterMfaCode?id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&service=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&source=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountLoginUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountCreationUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/userprofile-service/userprofile/user-settings
|
||||
response:
|
||||
body:
|
||||
string: '{"id": 2591602, "userData": {"gender": "MALE", "weight": 83000.0, "height":
|
||||
182.0, "timeFormat": "time_twenty_four_hr", "birthDate": "2000-01-01", "measurementSystem":
|
||||
"metric", "activityLevel": null, "handedness": "RIGHT", "powerFormat": {"formatId":
|
||||
30, "formatKey": "watt", "minFraction": 0, "maxFraction": 0, "groupingUsed":
|
||||
true, "displayFormat": null}, "heartRateFormat": {"formatId": 21, "formatKey":
|
||||
"bpm", "minFraction": 0, "maxFraction": 0, "groupingUsed": false, "displayFormat":
|
||||
null}, "firstDayOfWeek": {"dayId": 2, "dayName": "sunday", "sortOrder": 2,
|
||||
"isPossibleFirstDay": true}, "vo2MaxRunning": 45.0, "vo2MaxCycling": null,
|
||||
"lactateThresholdSpeed": 0.34722125000000004, "lactateThresholdHeartRate":
|
||||
null, "diveNumber": null, "intensityMinutesCalcMethod": "AUTO", "moderateIntensityMinutesHrZone":
|
||||
3, "vigorousIntensityMinutesHrZone": 4, "hydrationMeasurementUnit": "milliliter",
|
||||
"hydrationContainers": [], "hydrationAutoGoalEnabled": true, "firstbeatMaxStressScore":
|
||||
null, "firstbeatCyclingLtTimestamp": null, "firstbeatRunningLtTimestamp":
|
||||
1044719868, "thresholdHeartRateAutoDetected": true, "ftpAutoDetected": null,
|
||||
"trainingStatusPausedDate": null, "weatherLocation": null, "golfDistanceUnit":
|
||||
"statute_us", "golfElevationUnit": null, "golfSpeedUnit": null, "externalBottomTime":
|
||||
null}, "userSleep": {"sleepTime": 80400, "defaultSleepTime": false, "wakeTime":
|
||||
24000, "defaultWakeTime": false}, "connectDate": null, "sourceType": null}'
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 8151c6bdebf649df-MFE
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json;charset=UTF-8
|
||||
Date:
|
||||
- Thu, 12 Oct 2023 19:33:45 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=Mi%2Fn%2B7ExcrekhLBUA3vIWTc6xe5E0cMSeFdwGALv%2FKUypkIKnfCikzoAsV5BFgVarsRYtmB0Um640X9SA9p0dMp%2F1saQUEs7VRjVvLn4W0I2%2FLTw5qKr6A1w%2BN8x3SWzFXZX5dojfA%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cache-control:
|
||||
- no-cache, no-store, private
|
||||
pragma:
|
||||
- no-cache
|
||||
set-cookie:
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
116
garth/tests/cassettes/test_user_settings_sleep_windows.yaml
Normal file
116
garth/tests/cassettes/test_user_settings_sleep_windows.yaml
Normal file
@@ -0,0 +1,116 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
referer:
|
||||
- https://sso.garmin.com/sso/verifyMFA/loginEnterMfaCode?id=gauth-widget&embedWidget=true&gauthHost=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&service=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&source=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountLoginUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed&redirectAfterAccountCreationUrl=https%3A%2F%2Fsso.garmin.com%2Fsso%2Fembed
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/userprofile-service/userprofile/user-settings
|
||||
response:
|
||||
body:
|
||||
string: '{"id": 2591602, "userData": {"gender": "MALE", "weight": 83000.0, "height":
|
||||
182.0, "timeFormat": "time_twenty_four_hr", "birthDate": "2000-01-01", "measurementSystem":
|
||||
"metric", "activityLevel": null, "handedness": "RIGHT", "powerFormat": {"formatId":
|
||||
30, "formatKey": "watt", "minFraction": 0, "maxFraction": 0, "groupingUsed":
|
||||
true, "displayFormat": null}, "heartRateFormat": {"formatId": 21, "formatKey":
|
||||
"bpm", "minFraction": 0, "maxFraction": 0, "groupingUsed": false, "displayFormat":
|
||||
null}, "firstDayOfWeek": {"dayId": 2, "dayName": "sunday", "sortOrder": 2,
|
||||
"isPossibleFirstDay": true}, "vo2MaxRunning": 45.0, "vo2MaxCycling": null,
|
||||
"lactateThresholdSpeed": 0.34722125000000004, "lactateThresholdHeartRate":
|
||||
null, "diveNumber": null, "intensityMinutesCalcMethod": "AUTO", "moderateIntensityMinutesHrZone":
|
||||
3, "vigorousIntensityMinutesHrZone": 4, "hydrationMeasurementUnit": "milliliter",
|
||||
"hydrationContainers": [], "hydrationAutoGoalEnabled": true, "firstbeatMaxStressScore":
|
||||
null, "firstbeatCyclingLtTimestamp": null, "firstbeatRunningLtTimestamp":
|
||||
1044719868, "thresholdHeartRateAutoDetected": true, "ftpAutoDetected": null,
|
||||
"trainingStatusPausedDate": null, "weatherLocation": null, "golfDistanceUnit":
|
||||
"statute_us", "golfElevationUnit": null, "golfSpeedUnit": null, "externalBottomTime":
|
||||
null}, "userSleep": {"sleepTime": 80400, "defaultSleepTime": false, "wakeTime":
|
||||
24000, "defaultWakeTime": false}, "connectDate": null, "sourceType": null,
|
||||
"userSleepWindows": [{"sleepWindowFrequency": "SUNDAY",
|
||||
"startSleepTimeSecondsFromMidnight": 77400,
|
||||
"endSleepTimeSecondsFromMidnight": 19800},
|
||||
{"sleepWindowFrequency": "MONDAY",
|
||||
"startSleepTimeSecondsFromMidnight": 77400,
|
||||
"endSleepTimeSecondsFromMidnight": 19800},
|
||||
{"sleepWindowFrequency": "TUESDAY",
|
||||
"startSleepTimeSecondsFromMidnight": 77400,
|
||||
"endSleepTimeSecondsFromMidnight": 19800},
|
||||
{"sleepWindowFrequency": "WEDNESDAY",
|
||||
"startSleepTimeSecondsFromMidnight": 77400,
|
||||
"endSleepTimeSecondsFromMidnight": 19800},
|
||||
{"sleepWindowFrequency": "THURSDAY",
|
||||
"startSleepTimeSecondsFromMidnight": 77400,
|
||||
"endSleepTimeSecondsFromMidnight": 19800},
|
||||
{"sleepWindowFrequency": "FRIDAY",
|
||||
"startSleepTimeSecondsFromMidnight": 77400,
|
||||
"endSleepTimeSecondsFromMidnight": 19800},
|
||||
{"sleepWindowFrequency": "SATURDAY",
|
||||
"startSleepTimeSecondsFromMidnight": 77400,
|
||||
"endSleepTimeSecondsFromMidnight": 19800},
|
||||
{"sleepWindowFrequency": "DAILY",
|
||||
"startSleepTimeSecondsFromMidnight": 77400,
|
||||
"endSleepTimeSecondsFromMidnight": 19800}]}'
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 8151c6bdebf649df-MFE
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json;charset=UTF-8
|
||||
Date:
|
||||
- Thu, 12 Oct 2023 19:33:45 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=Mi%2Fn%2B7ExcrekhLBUA3vIWTc6xe5E0cMSeFdwGALv%2FKUypkIKnfCikzoAsV5BFgVarsRYtmB0Um640X9SA9p0dMp%2F1saQUEs7VRjVvLn4W0I2%2FLTw5qKr6A1w%2BN8x3SWzFXZX5dojfA%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cache-control:
|
||||
- no-cache, no-store, private
|
||||
pragma:
|
||||
- no-cache
|
||||
set-cookie:
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
91
garth/tests/cassettes/test_username.yaml
Normal file
91
garth/tests/cassettes/test_username.yaml
Normal file
@@ -0,0 +1,91 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/userprofile-service/socialProfile
|
||||
response:
|
||||
body:
|
||||
string: '{"id": 3154645, "profileId": 2591602, "garminGUID": "0690cc1d-d23d-4412-b027-80fd4ed1c0f6",
|
||||
"displayName": "mtamizi", "fullName": "Matin Tamizi", "userName": "mtamizi",
|
||||
"profileImageUuid": "73240e81-6e4d-43fc-8af8-c8f6c51b3b8f", "profileImageUrlLarge":
|
||||
"https://s3.amazonaws.com/garmin-connect-prod/profile_images/73240e81-6e4d-43fc-8af8-c8f6c51b3b8f-2591602.png",
|
||||
"profileImageUrlMedium": "https://s3.amazonaws.com/garmin-connect-prod/profile_images/685a19e9-a7be-4a11-9bf9-faca0c5d1f1a-2591602.png",
|
||||
"profileImageUrlSmall": "https://s3.amazonaws.com/garmin-connect-prod/profile_images/6302f021-0ec7-4dc9-b0c3-d5a19bc5a08c-2591602.png",
|
||||
"location": "Ciudad de M\u00e9xico, CDMX", "facebookUrl": null, "twitterUrl":
|
||||
null, "personalWebsite": null, "motivation": null, "bio": null, "primaryActivity":
|
||||
null, "favoriteActivityTypes": [], "runningTrainingSpeed": 0.0, "cyclingTrainingSpeed":
|
||||
0.0, "favoriteCyclingActivityTypes": [], "cyclingClassification": null, "cyclingMaxAvgPower":
|
||||
0.0, "swimmingTrainingSpeed": 0.0, "profileVisibility": "private", "activityStartVisibility":
|
||||
"private", "activityMapVisibility": "public", "courseVisibility": "public",
|
||||
"activityHeartRateVisibility": "public", "activityPowerVisibility": "public",
|
||||
"badgeVisibility": "private", "showAge": false, "showWeight": false, "showHeight":
|
||||
false, "showWeightClass": false, "showAgeRange": false, "showGender": false,
|
||||
"showActivityClass": false, "showVO2Max": false, "showPersonalRecords": false,
|
||||
"showLast12Months": false, "showLifetimeTotals": false, "showUpcomingEvents":
|
||||
false, "showRecentFavorites": false, "showRecentDevice": false, "showRecentGear":
|
||||
false, "showBadges": true, "otherActivity": null, "otherPrimaryActivity":
|
||||
null, "otherMotivation": null, "userRoles": ["SCOPE_ATP_READ", "SCOPE_ATP_WRITE",
|
||||
"SCOPE_COMMUNITY_COURSE_READ", "SCOPE_COMMUNITY_COURSE_WRITE", "SCOPE_CONNECT_READ",
|
||||
"SCOPE_CONNECT_WRITE", "SCOPE_DT_CLIENT_ANALYTICS_WRITE", "SCOPE_GARMINPAY_READ",
|
||||
"SCOPE_GARMINPAY_WRITE", "SCOPE_GCOFFER_READ", "SCOPE_GCOFFER_WRITE", "SCOPE_GHS_SAMD",
|
||||
"SCOPE_GHS_UPLOAD", "SCOPE_GOLF_API_READ", "SCOPE_GOLF_API_WRITE", "SCOPE_INSIGHTS_READ",
|
||||
"SCOPE_INSIGHTS_WRITE", "SCOPE_PRODUCT_SEARCH_READ", "ROLE_CONNECTUSER", "ROLE_FITNESS_USER",
|
||||
"ROLE_WELLNESS_USER", "ROLE_OUTDOOR_USER", "ROLE_CONNECT_2_USER", "ROLE_TACX_APP_USER"],
|
||||
"nameApproved": true, "userProfileFullName": "Matin Tamizi", "makeGolfScorecardsPrivate":
|
||||
true, "allowGolfLiveScoring": false, "allowGolfScoringByConnections": true,
|
||||
"userLevel": 3, "userPoint": 117, "levelUpdateDate": "2020-12-12T15:20:38.0",
|
||||
"levelIsViewed": false, "levelPointThreshold": 140, "userPointOffset": 0,
|
||||
"userPro": false}'
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 7f12d41c38a11557-QRO
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json;charset=UTF-8
|
||||
Date:
|
||||
- Fri, 04 Aug 2023 00:54:20 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=G2IB1TvEJWwpf1qx1pmxuSXoRW%2FSZEU8HyTOlPTBTCyG2dipRZTZe8A9ulowoM3j%2F6hYqLLs1uU2ifU%2FnZpB2j2uzj6ePqBlmx4IjtlABarp0HGJAvv98zpC%2F5t8DFDPLSS58dBYsA%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cache-control:
|
||||
- no-cache, no-store, private
|
||||
pragma:
|
||||
- no-cache
|
||||
set-cookie:
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
169
garth/tests/conftest.py
Normal file
169
garth/tests/conftest.py
Normal file
@@ -0,0 +1,169 @@
|
||||
import gzip
|
||||
import io
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import time
|
||||
|
||||
import pytest
|
||||
from requests import Session
|
||||
|
||||
from garth.auth_tokens import OAuth1Token, OAuth2Token
|
||||
from garth.http import Client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def session():
|
||||
return Session()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def client(session) -> Client:
|
||||
return Client(session=session)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def oauth1_token_dict() -> dict:
|
||||
return dict(
|
||||
oauth_token="7fdff19aa9d64dda83e9d7858473aed1",
|
||||
oauth_token_secret="49919d7c4c8241ac93fb4345886fbcea",
|
||||
mfa_token="ab316f8640f3491f999f3298f3d6f1bb",
|
||||
mfa_expiration_timestamp="2024-08-02 05:56:10.000",
|
||||
domain="garmin.com",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def oauth1_token(oauth1_token_dict) -> OAuth1Token:
|
||||
return OAuth1Token(**oauth1_token_dict)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def oauth2_token_dict() -> dict:
|
||||
return dict(
|
||||
scope="CONNECT_READ CONNECT_WRITE",
|
||||
jti="foo",
|
||||
token_type="Bearer",
|
||||
access_token="bar",
|
||||
refresh_token="baz",
|
||||
expires_in=3599,
|
||||
refresh_token_expires_in=7199,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def oauth2_token(oauth2_token_dict: dict) -> OAuth2Token:
|
||||
token = OAuth2Token(
|
||||
expires_at=int(time.time() + 3599),
|
||||
refresh_token_expires_at=int(time.time() + 7199),
|
||||
**oauth2_token_dict,
|
||||
)
|
||||
return token
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def authed_client(
|
||||
oauth1_token: OAuth1Token, oauth2_token: OAuth2Token
|
||||
) -> Client:
|
||||
client = Client()
|
||||
try:
|
||||
client.load(os.environ["GARTH_HOME"])
|
||||
except KeyError:
|
||||
client.configure(oauth1_token=oauth1_token, oauth2_token=oauth2_token)
|
||||
assert client.oauth2_token and isinstance(client.oauth2_token, OAuth2Token)
|
||||
assert not client.oauth2_token.expired
|
||||
return client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def vcr(vcr):
|
||||
if "GARTH_HOME" not in os.environ:
|
||||
vcr.record_mode = "none"
|
||||
return vcr
|
||||
|
||||
|
||||
def sanitize_cookie(cookie_value) -> str:
|
||||
return re.sub(r"=[^;]*", "=SANITIZED", cookie_value)
|
||||
|
||||
|
||||
def sanitize_request(request):
|
||||
if request.body:
|
||||
try:
|
||||
body = request.body.decode("utf8")
|
||||
except UnicodeDecodeError:
|
||||
...
|
||||
else:
|
||||
for key in ["username", "password", "refresh_token"]:
|
||||
body = re.sub(key + r"=[^&]*", f"{key}=SANITIZED", body)
|
||||
request.body = body.encode("utf8")
|
||||
|
||||
if "Cookie" in request.headers:
|
||||
cookies = request.headers["Cookie"].split("; ")
|
||||
sanitized_cookies = [sanitize_cookie(cookie) for cookie in cookies]
|
||||
request.headers["Cookie"] = "; ".join(sanitized_cookies)
|
||||
return request
|
||||
|
||||
|
||||
def sanitize_response(response):
|
||||
try:
|
||||
encoding = response["headers"].pop("Content-Encoding")
|
||||
except KeyError:
|
||||
...
|
||||
else:
|
||||
if encoding[0] == "gzip":
|
||||
body = response["body"]["string"]
|
||||
buffer = io.BytesIO(body)
|
||||
try:
|
||||
body = gzip.GzipFile(fileobj=buffer).read()
|
||||
except gzip.BadGzipFile: # pragma: no cover
|
||||
...
|
||||
else:
|
||||
response["body"]["string"] = body
|
||||
|
||||
for key in ["set-cookie", "Set-Cookie"]:
|
||||
if key in response["headers"]:
|
||||
cookies = response["headers"][key]
|
||||
sanitized_cookies = [sanitize_cookie(cookie) for cookie in cookies]
|
||||
response["headers"][key] = sanitized_cookies
|
||||
|
||||
try:
|
||||
body = response["body"]["string"].decode("utf8")
|
||||
except UnicodeDecodeError:
|
||||
pass
|
||||
else:
|
||||
patterns = [
|
||||
"oauth_token=[^&]*",
|
||||
"oauth_token_secret=[^&]*",
|
||||
"mfa_token=[^&]*",
|
||||
]
|
||||
for pattern in patterns:
|
||||
body = re.sub(pattern, pattern.split("=")[0] + "=SANITIZED", body)
|
||||
try:
|
||||
body_json = json.loads(body)
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
else:
|
||||
if body_json and isinstance(body_json, dict):
|
||||
for field in [
|
||||
"access_token",
|
||||
"refresh_token",
|
||||
"jti",
|
||||
"consumer_key",
|
||||
"consumer_secret",
|
||||
]:
|
||||
if field in body_json:
|
||||
body_json[field] = "SANITIZED"
|
||||
|
||||
body = json.dumps(body_json)
|
||||
response["body"]["string"] = body.encode("utf8")
|
||||
|
||||
return response
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def vcr_config():
|
||||
return {
|
||||
"filter_headers": [("Authorization", "Bearer SANITIZED")],
|
||||
"before_record_request": sanitize_request,
|
||||
"before_record_response": sanitize_response,
|
||||
}
|
||||
35
garth/tests/data/cassettes/test_body_battery_data_get.yaml
Normal file
35
garth/tests/data/cassettes/test_body_battery_data_get.yaml
Normal file
@@ -0,0 +1,35 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/wellness-service/wellness/bodyBattery/events/2023-07-20
|
||||
response:
|
||||
body:
|
||||
string: '[{"event": {"eventType": "sleep", "eventStartTimeGmt": "2023-07-19T21:30:00.000",
|
||||
"timezoneOffset": -25200000, "durationInMilliseconds": 28800000, "bodyBatteryImpact": 35,
|
||||
"feedbackType": "good_sleep", "shortFeedback": "Good sleep restored your Body Battery"},
|
||||
"activityName": null, "activityType": null, "activityId": null, "averageStress": 15.5,
|
||||
"stressValuesArray": [[1689811800000, 12], [1689812100000, 18], [1689812400000, 15]],
|
||||
"bodyBatteryValuesArray": [[1689811800000, "charging", 45, 1.0], [1689812100000, "charging", 48, 1.0],
|
||||
[1689812400000, "charging", 52, 1.0], [1689840600000, "draining", 85, 1.0]]}]'
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json;charset=UTF-8
|
||||
Date:
|
||||
- Thu, 20 Jul 2023 12:00:00 GMT
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
90
garth/tests/data/cassettes/test_body_battery_data_list.yaml
Normal file
90
garth/tests/data/cassettes/test_body_battery_data_list.yaml
Normal file
@@ -0,0 +1,90 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/wellness-service/wellness/bodyBattery/events/2023-07-20
|
||||
response:
|
||||
body:
|
||||
string: '[{"event": {"eventType": "sleep", "eventStartTimeGmt": "2023-07-19T21:30:00.000",
|
||||
"timezoneOffset": -25200000, "durationInMilliseconds": 28800000, "bodyBatteryImpact": 35,
|
||||
"feedbackType": "good_sleep", "shortFeedback": "Good sleep restored your Body Battery"},
|
||||
"activityName": null, "activityType": null, "activityId": null, "averageStress": 15.5,
|
||||
"stressValuesArray": [[1689811800000, 12], [1689812100000, 18]],
|
||||
"bodyBatteryValuesArray": [[1689811800000, "charging", 45, 1.0], [1689840600000, "draining", 85, 1.0]]}]'
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json;charset=UTF-8
|
||||
Date:
|
||||
- Thu, 20 Jul 2023 12:00:00 GMT
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/wellness-service/wellness/bodyBattery/events/2023-07-19
|
||||
response:
|
||||
body:
|
||||
string: '[]'
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json;charset=UTF-8
|
||||
Date:
|
||||
- Thu, 19 Jul 2023 12:00:00 GMT
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/wellness-service/wellness/bodyBattery/events/2023-07-18
|
||||
response:
|
||||
body:
|
||||
string: '[{"event": null, "activityName": "Running", "activityType": "running",
|
||||
"activityId": "12345", "averageStress": 45.2, "stressValuesArray": [],
|
||||
"bodyBatteryValuesArray": [[1689667200000, "draining", 75, 1.0], [1689670800000, "draining", 65, 1.0]]}]'
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json;charset=UTF-8
|
||||
Date:
|
||||
- Tue, 18 Jul 2023 12:00:00 GMT
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
@@ -0,0 +1,33 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/wellness-service/wellness/dailyStress/2023-07-20
|
||||
response:
|
||||
body:
|
||||
string: '{"userProfilePK": 2591602, "calendarDate": "2023-07-20",
|
||||
"startTimestampGMT": "2023-07-20T06:00:00.000Z", "endTimestampGMT": "2023-07-21T05:59:59.999Z",
|
||||
"startTimestampLocal": "2023-07-19T23:00:00.000Z", "endTimestampLocal": "2023-07-20T22:59:59.999Z",
|
||||
"maxStressLevel": 0, "avgStressLevel": 0, "stressChartValueOffset": 0, "stressChartYAxisOrigin": 0,
|
||||
"stressValuesArray": [], "bodyBatteryValuesArray": []}'
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json;charset=UTF-8
|
||||
Date:
|
||||
- Thu, 20 Jul 2023 12:00:00 GMT
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
@@ -0,0 +1,41 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/wellness-service/wellness/dailyStress/2023-07-20
|
||||
response:
|
||||
body:
|
||||
string: '{"userProfilePK": 2591602, "calendarDate": "2023-07-20",
|
||||
"startTimestampGMT": "2023-07-20T06:00:00.000Z", "endTimestampGMT": "2023-07-21T05:59:59.999Z",
|
||||
"startTimestampLocal": "2023-07-19T23:00:00.000Z", "endTimestampLocal": "2023-07-20T22:59:59.999Z",
|
||||
"maxStressLevel": 85, "avgStressLevel": 25, "stressChartValueOffset": 0, "stressChartYAxisOrigin": 0,
|
||||
"stressValuesArray": [[1689811800000, 12], [1689812100000, 18], [1689812400000, 15],
|
||||
[1689815700000, 45], [1689819300000, 85], [1689822900000, 35], [1689826500000, 20],
|
||||
[1689830100000, 15], [1689833700000, 25], [1689837300000, 30]],
|
||||
"bodyBatteryValuesArray": [[1689811800000, "charging", 45, 1.0], [1689812100000, "charging", 48, 1.0],
|
||||
[1689812400000, "charging", 52, 1.0], [1689815700000, "charging", 65, 1.0],
|
||||
[1689819300000, "draining", 85, 1.0], [1689822900000, "draining", 75, 1.0],
|
||||
[1689826500000, "draining", 65, 1.0], [1689830100000, "draining", 55, 1.0],
|
||||
[1689833700000, "draining", 45, 1.0], [1689837300000, "draining", 35, 1.0],
|
||||
[1689840900000, "draining", 25, 1.0]]}'
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json;charset=UTF-8
|
||||
Date:
|
||||
- Thu, 20 Jul 2023 12:00:00 GMT
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
@@ -0,0 +1,29 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/wellness-service/wellness/dailyStress/2020-01-01
|
||||
response:
|
||||
body:
|
||||
string: 'null'
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json;charset=UTF-8
|
||||
Date:
|
||||
- Wed, 01 Jan 2020 12:00:00 GMT
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
@@ -0,0 +1,93 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/wellness-service/wellness/dailyStress/2023-07-20
|
||||
response:
|
||||
body:
|
||||
string: '{"userProfilePK": 2591602, "calendarDate": "2023-07-20",
|
||||
"startTimestampGMT": "2023-07-20T06:00:00.000Z", "endTimestampGMT": "2023-07-21T05:59:59.999Z",
|
||||
"startTimestampLocal": "2023-07-19T23:00:00.000Z", "endTimestampLocal": "2023-07-20T22:59:59.999Z",
|
||||
"maxStressLevel": 85, "avgStressLevel": 25, "stressChartValueOffset": 0, "stressChartYAxisOrigin": 0,
|
||||
"stressValuesArray": [[1689811800000, 12], [1689812100000, 18]],
|
||||
"bodyBatteryValuesArray": [[1689811800000, "charging", 45, 1.0], [1689840900000, "draining", 25, 1.0]]}'
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json;charset=UTF-8
|
||||
Date:
|
||||
- Thu, 20 Jul 2023 12:00:00 GMT
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/wellness-service/wellness/dailyStress/2023-07-19
|
||||
response:
|
||||
body:
|
||||
string: '{"userProfilePK": 2591602, "calendarDate": "2023-07-19",
|
||||
"startTimestampGMT": "2023-07-19T06:00:00.000Z", "endTimestampGMT": "2023-07-20T05:59:59.999Z",
|
||||
"startTimestampLocal": "2023-07-18T23:00:00.000Z", "endTimestampLocal": "2023-07-19T22:59:59.999Z",
|
||||
"maxStressLevel": 65, "avgStressLevel": 30, "stressChartValueOffset": 0, "stressChartYAxisOrigin": 0,
|
||||
"stressValuesArray": [[1689725400000, 25], [1689729000000, 40]],
|
||||
"bodyBatteryValuesArray": [[1689725400000, "draining", 80, 1.0], [1689754200000, "charging", 30, 1.0]]}'
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json;charset=UTF-8
|
||||
Date:
|
||||
- Wed, 19 Jul 2023 12:00:00 GMT
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/wellness-service/wellness/dailyStress/2023-07-18
|
||||
response:
|
||||
body:
|
||||
string: 'null'
|
||||
headers:
|
||||
Content-Type:
|
||||
- application/json;charset=UTF-8
|
||||
Date:
|
||||
- Tue, 18 Jul 2023 12:00:00 GMT
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
58
garth/tests/data/cassettes/test_get_daily_weight_data.yaml
Normal file
58
garth/tests/data/cassettes/test_get_daily_weight_data.yaml
Normal file
@@ -0,0 +1,58 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- GCM-iOS-5.7.2.1
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/weight-service/weight/dayview/2025-06-15
|
||||
response:
|
||||
body:
|
||||
string: '{"startDate": "2025-06-15", "endDate": "2025-06-15", "dateWeightList":
|
||||
[{"samplePk": 1749996902851, "date": 1749975276000, "calendarDate": "2025-06-15",
|
||||
"weight": 59720.0, "bmi": 22.799999237060547, "bodyFat": 19.3, "bodyWater":
|
||||
58.9, "boneMass": 3539, "muscleMass": 26979, "physiqueRating": null, "visceralFat":
|
||||
null, "metabolicAge": null, "sourceType": "INDEX_SCALE", "timestampGMT": 1749996876000,
|
||||
"weightDelta": 200.00000000000284}], "totalAverage": {"from": 1749945600000,
|
||||
"until": 1750031999999, "weight": 59720.0, "bmi": 22.799999237060547, "bodyFat":
|
||||
19.3, "bodyWater": 58.9, "boneMass": 3539, "muscleMass": 26979, "physiqueRating":
|
||||
null, "visceralFat": null, "metabolicAge": null}}'
|
||||
headers:
|
||||
CF-RAY:
|
||||
- 9505ceb74f5fd875-QRO
|
||||
Cache-Control:
|
||||
- no-cache, no-store, private
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Sun, 15 Jun 2025 23:22:05 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=UH9A8geP4FHMJjd6y07FBmPslL9A%2B3JKPZa2WHai0fGEwDokhD2YqXsG155VIgSjGPY8Av3IYcI%2B%2FH%2B38GY15TN4THIsNucuROuGSc3NlFY1c%2BmSER4pGCfCqwF1BH1cmMr%2Fu8TKFKFjkS8bzk1P%2FM4l%2Bg%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
pragma:
|
||||
- no-cache
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
62
garth/tests/data/cassettes/test_get_manual_weight_data.yaml
Normal file
62
garth/tests/data/cassettes/test_get_manual_weight_data.yaml
Normal file
@@ -0,0 +1,62 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- GCM-iOS-5.7.2.1
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/weight-service/weight/dayview/2025-06-14
|
||||
response:
|
||||
body:
|
||||
string: '{"startDate": "2025-06-14", "endDate": "2025-06-14", "dateWeightList":
|
||||
[{"samplePk": 1749948744411, "date": 1749927125175, "calendarDate": "2025-06-14",
|
||||
"weight": 59500.0, "bmi": null, "bodyFat": null, "bodyWater": null, "boneMass":
|
||||
null, "muscleMass": null, "physiqueRating": null, "visceralFat": null, "metabolicAge":
|
||||
null, "sourceType": "MANUAL", "timestampGMT": 1749948725175, "weightDelta":
|
||||
399.9999999999986}, {"samplePk": 1749909217098, "date": 1749887580000, "calendarDate":
|
||||
"2025-06-14", "weight": 59130.0, "bmi": 22.5, "bodyFat": 20.3, "bodyWater":
|
||||
58.2, "boneMass": 3430, "muscleMass": 26840, "physiqueRating": null, "visceralFat":
|
||||
null, "metabolicAge": null, "sourceType": "INDEX_SCALE", "timestampGMT": 1749909180000,
|
||||
"weightDelta": -100.00000000000142}], "totalAverage": {"from": 1749859200000,
|
||||
"until": 1749945599999, "weight": 59315.0, "bmi": 22.5, "bodyFat": 20.3, "bodyWater":
|
||||
58.2, "boneMass": 3430, "muscleMass": 26840, "physiqueRating": null, "visceralFat":
|
||||
null, "metabolicAge": null}}'
|
||||
headers:
|
||||
CF-RAY:
|
||||
- 95064313addc55c3-QRO
|
||||
Cache-Control:
|
||||
- no-cache, no-store, private
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Mon, 16 Jun 2025 00:41:31 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=4k83lvSQ0EaF3v3SPJQfFUMAz%2FRDzu%2BJpC4JQGBU9eLNTYcx5pUfnIWwmgS0AdXoavdcbR4CvPW0TyI%2BRQBpV%2FQo3qe1GcGAaUzrQq5KRBbEpkHZseRhz1jmcUv17rnmwMucrfKf9wMne477n%2BBT0wPNgg%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
pragma:
|
||||
- no-cache
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
@@ -0,0 +1,53 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- GCM-iOS-5.7.2.1
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/weight-service/weight/dayview/2020-01-01
|
||||
response:
|
||||
body:
|
||||
string: '{"startDate": "2020-01-01", "endDate": "2020-01-01", "dateWeightList":
|
||||
[], "totalAverage": {"from": 1577836800000, "until": 1577923199999, "weight":
|
||||
null, "bmi": null, "bodyFat": null, "bodyWater": null, "boneMass": null, "muscleMass":
|
||||
null, "physiqueRating": null, "visceralFat": null, "metabolicAge": null}}'
|
||||
headers:
|
||||
CF-RAY:
|
||||
- 950644801ca4d9f7-QRO
|
||||
Cache-Control:
|
||||
- no-cache, no-store, private
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Mon, 16 Jun 2025 00:42:29 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=BJIcx9Zh1zt1cLoKMZAVgYYQfsvpXBQP04VMD2f4iMyfjHCnNu5Sh%2BdA7NcZ2FkaJ7%2Bxw8up1nDASiKuMf8XqsAi1BYG2kqRNBvEQQEkvhY0L63y%2BXwDbb%2BUcurig3gGRUI3oNq%2F8II2iL62j6q0iLJ9pA%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
pragma:
|
||||
- no-cache
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
222
garth/tests/data/cassettes/test_hrv_data_get.yaml
Normal file
222
garth/tests/data/cassettes/test_hrv_data_get.yaml
Normal file
@@ -0,0 +1,222 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/hrv-service/hrv/2023-07-20
|
||||
response:
|
||||
body:
|
||||
string: '{"userProfilePk": 2591602, "hrvSummary": {"calendarDate": "2023-07-20",
|
||||
"weeklyAvg": 39, "lastNightAvg": 42, "lastNight5MinHigh": 66, "baseline":
|
||||
{"lowUpper": 36, "balancedLow": 39, "balancedUpper": 52, "markerValue": 0.25},
|
||||
"status": "BALANCED", "feedbackPhrase": "HRV_BALANCED_7", "createTimeStamp":
|
||||
"2023-07-20T12:14:11.898"}, "hrvReadings": [{"hrvValue": 54, "readingTimeGMT":
|
||||
"2023-07-20T05:29:48.0", "readingTimeLocal": "2023-07-19T23:29:48.0"}, {"hrvValue":
|
||||
56, "readingTimeGMT": "2023-07-20T05:34:48.0", "readingTimeLocal": "2023-07-19T23:34:48.0"},
|
||||
{"hrvValue": 51, "readingTimeGMT": "2023-07-20T05:39:48.0", "readingTimeLocal":
|
||||
"2023-07-19T23:39:48.0"}, {"hrvValue": 50, "readingTimeGMT": "2023-07-20T05:44:48.0",
|
||||
"readingTimeLocal": "2023-07-19T23:44:48.0"}, {"hrvValue": 55, "readingTimeGMT":
|
||||
"2023-07-20T05:49:48.0", "readingTimeLocal": "2023-07-19T23:49:48.0"}, {"hrvValue":
|
||||
55, "readingTimeGMT": "2023-07-20T05:54:48.0", "readingTimeLocal": "2023-07-19T23:54:48.0"},
|
||||
{"hrvValue": 55, "readingTimeGMT": "2023-07-20T05:59:48.0", "readingTimeLocal":
|
||||
"2023-07-19T23:59:48.0"}, {"hrvValue": 43, "readingTimeGMT": "2023-07-20T06:04:48.0",
|
||||
"readingTimeLocal": "2023-07-20T00:04:48.0"}, {"hrvValue": 53, "readingTimeGMT":
|
||||
"2023-07-20T06:09:48.0", "readingTimeLocal": "2023-07-20T00:09:48.0"}, {"hrvValue":
|
||||
53, "readingTimeGMT": "2023-07-20T06:14:48.0", "readingTimeLocal": "2023-07-20T00:14:48.0"},
|
||||
{"hrvValue": 41, "readingTimeGMT": "2023-07-20T06:19:48.0", "readingTimeLocal":
|
||||
"2023-07-20T00:19:48.0"}, {"hrvValue": 47, "readingTimeGMT": "2023-07-20T06:24:48.0",
|
||||
"readingTimeLocal": "2023-07-20T00:24:48.0"}, {"hrvValue": 45, "readingTimeGMT":
|
||||
"2023-07-20T06:29:48.0", "readingTimeLocal": "2023-07-20T00:29:48.0"}, {"hrvValue":
|
||||
38, "readingTimeGMT": "2023-07-20T06:34:48.0", "readingTimeLocal": "2023-07-20T00:34:48.0"},
|
||||
{"hrvValue": 33, "readingTimeGMT": "2023-07-20T06:39:48.0", "readingTimeLocal":
|
||||
"2023-07-20T00:39:48.0"}, {"hrvValue": 43, "readingTimeGMT": "2023-07-20T06:44:48.0",
|
||||
"readingTimeLocal": "2023-07-20T00:44:48.0"}, {"hrvValue": 49, "readingTimeGMT":
|
||||
"2023-07-20T06:49:48.0", "readingTimeLocal": "2023-07-20T00:49:48.0"}, {"hrvValue":
|
||||
43, "readingTimeGMT": "2023-07-20T06:54:48.0", "readingTimeLocal": "2023-07-20T00:54:48.0"},
|
||||
{"hrvValue": 47, "readingTimeGMT": "2023-07-20T06:59:48.0", "readingTimeLocal":
|
||||
"2023-07-20T00:59:48.0"}, {"hrvValue": 38, "readingTimeGMT": "2023-07-20T07:04:48.0",
|
||||
"readingTimeLocal": "2023-07-20T01:04:48.0"}, {"hrvValue": 39, "readingTimeGMT":
|
||||
"2023-07-20T07:09:48.0", "readingTimeLocal": "2023-07-20T01:09:48.0"}, {"hrvValue":
|
||||
33, "readingTimeGMT": "2023-07-20T07:14:48.0", "readingTimeLocal": "2023-07-20T01:14:48.0"},
|
||||
{"hrvValue": 43, "readingTimeGMT": "2023-07-20T07:19:48.0", "readingTimeLocal":
|
||||
"2023-07-20T01:19:48.0"}, {"hrvValue": 45, "readingTimeGMT": "2023-07-20T07:24:48.0",
|
||||
"readingTimeLocal": "2023-07-20T01:24:48.0"}, {"hrvValue": 36, "readingTimeGMT":
|
||||
"2023-07-20T07:29:48.0", "readingTimeLocal": "2023-07-20T01:29:48.0"}, {"hrvValue":
|
||||
47, "readingTimeGMT": "2023-07-20T07:34:48.0", "readingTimeLocal": "2023-07-20T01:34:48.0"},
|
||||
{"hrvValue": 46, "readingTimeGMT": "2023-07-20T07:39:48.0", "readingTimeLocal":
|
||||
"2023-07-20T01:39:48.0"}, {"hrvValue": 53, "readingTimeGMT": "2023-07-20T07:44:48.0",
|
||||
"readingTimeLocal": "2023-07-20T01:44:48.0"}, {"hrvValue": 35, "readingTimeGMT":
|
||||
"2023-07-20T07:49:48.0", "readingTimeLocal": "2023-07-20T01:49:48.0"}, {"hrvValue":
|
||||
28, "readingTimeGMT": "2023-07-20T07:54:48.0", "readingTimeLocal": "2023-07-20T01:54:48.0"},
|
||||
{"hrvValue": 30, "readingTimeGMT": "2023-07-20T07:59:48.0", "readingTimeLocal":
|
||||
"2023-07-20T01:59:48.0"}, {"hrvValue": 38, "readingTimeGMT": "2023-07-20T08:04:48.0",
|
||||
"readingTimeLocal": "2023-07-20T02:04:48.0"}, {"hrvValue": 49, "readingTimeGMT":
|
||||
"2023-07-20T08:09:48.0", "readingTimeLocal": "2023-07-20T02:09:48.0"}, {"hrvValue":
|
||||
46, "readingTimeGMT": "2023-07-20T08:14:48.0", "readingTimeLocal": "2023-07-20T02:14:48.0"},
|
||||
{"hrvValue": 31, "readingTimeGMT": "2023-07-20T08:19:48.0", "readingTimeLocal":
|
||||
"2023-07-20T02:19:48.0"}, {"hrvValue": 29, "readingTimeGMT": "2023-07-20T08:24:48.0",
|
||||
"readingTimeLocal": "2023-07-20T02:24:48.0"}, {"hrvValue": 32, "readingTimeGMT":
|
||||
"2023-07-20T08:29:48.0", "readingTimeLocal": "2023-07-20T02:29:48.0"}, {"hrvValue":
|
||||
27, "readingTimeGMT": "2023-07-20T08:34:48.0", "readingTimeLocal": "2023-07-20T02:34:48.0"},
|
||||
{"hrvValue": 31, "readingTimeGMT": "2023-07-20T08:39:48.0", "readingTimeLocal":
|
||||
"2023-07-20T02:39:48.0"}, {"hrvValue": 33, "readingTimeGMT": "2023-07-20T08:44:48.0",
|
||||
"readingTimeLocal": "2023-07-20T02:44:48.0"}, {"hrvValue": 32, "readingTimeGMT":
|
||||
"2023-07-20T08:49:48.0", "readingTimeLocal": "2023-07-20T02:49:48.0"}, {"hrvValue":
|
||||
29, "readingTimeGMT": "2023-07-20T08:54:48.0", "readingTimeLocal": "2023-07-20T02:54:48.0"},
|
||||
{"hrvValue": 35, "readingTimeGMT": "2023-07-20T08:59:48.0", "readingTimeLocal":
|
||||
"2023-07-20T02:59:48.0"}, {"hrvValue": 30, "readingTimeGMT": "2023-07-20T09:04:48.0",
|
||||
"readingTimeLocal": "2023-07-20T03:04:48.0"}, {"hrvValue": 32, "readingTimeGMT":
|
||||
"2023-07-20T09:09:48.0", "readingTimeLocal": "2023-07-20T03:09:48.0"}, {"hrvValue":
|
||||
37, "readingTimeGMT": "2023-07-20T09:14:48.0", "readingTimeLocal": "2023-07-20T03:14:48.0"},
|
||||
{"hrvValue": 40, "readingTimeGMT": "2023-07-20T09:19:48.0", "readingTimeLocal":
|
||||
"2023-07-20T03:19:48.0"}, {"hrvValue": 39, "readingTimeGMT": "2023-07-20T09:24:48.0",
|
||||
"readingTimeLocal": "2023-07-20T03:24:48.0"}, {"hrvValue": 47, "readingTimeGMT":
|
||||
"2023-07-20T09:29:48.0", "readingTimeLocal": "2023-07-20T03:29:48.0"}, {"hrvValue":
|
||||
45, "readingTimeGMT": "2023-07-20T09:34:48.0", "readingTimeLocal": "2023-07-20T03:34:48.0"},
|
||||
{"hrvValue": 43, "readingTimeGMT": "2023-07-20T09:39:48.0", "readingTimeLocal":
|
||||
"2023-07-20T03:39:48.0"}, {"hrvValue": 31, "readingTimeGMT": "2023-07-20T09:44:48.0",
|
||||
"readingTimeLocal": "2023-07-20T03:44:48.0"}, {"hrvValue": 35, "readingTimeGMT":
|
||||
"2023-07-20T09:49:48.0", "readingTimeLocal": "2023-07-20T03:49:48.0"}, {"hrvValue":
|
||||
40, "readingTimeGMT": "2023-07-20T09:54:48.0", "readingTimeLocal": "2023-07-20T03:54:48.0"},
|
||||
{"hrvValue": 44, "readingTimeGMT": "2023-07-20T09:59:48.0", "readingTimeLocal":
|
||||
"2023-07-20T03:59:48.0"}, {"hrvValue": 35, "readingTimeGMT": "2023-07-20T10:04:48.0",
|
||||
"readingTimeLocal": "2023-07-20T04:04:48.0"}, {"hrvValue": 43, "readingTimeGMT":
|
||||
"2023-07-20T10:09:48.0", "readingTimeLocal": "2023-07-20T04:09:48.0"}, {"hrvValue":
|
||||
54, "readingTimeGMT": "2023-07-20T10:14:48.0", "readingTimeLocal": "2023-07-20T04:14:48.0"},
|
||||
{"hrvValue": 57, "readingTimeGMT": "2023-07-20T10:19:48.0", "readingTimeLocal":
|
||||
"2023-07-20T04:19:48.0"}, {"hrvValue": 66, "readingTimeGMT": "2023-07-20T10:24:48.0",
|
||||
"readingTimeLocal": "2023-07-20T04:24:48.0"}, {"hrvValue": 58, "readingTimeGMT":
|
||||
"2023-07-20T10:29:48.0", "readingTimeLocal": "2023-07-20T04:29:48.0"}, {"hrvValue":
|
||||
61, "readingTimeGMT": "2023-07-20T10:34:48.0", "readingTimeLocal": "2023-07-20T04:34:48.0"},
|
||||
{"hrvValue": 45, "readingTimeGMT": "2023-07-20T10:39:48.0", "readingTimeLocal":
|
||||
"2023-07-20T04:39:48.0"}, {"hrvValue": 52, "readingTimeGMT": "2023-07-20T10:44:48.0",
|
||||
"readingTimeLocal": "2023-07-20T04:44:48.0"}, {"hrvValue": 43, "readingTimeGMT":
|
||||
"2023-07-20T10:49:48.0", "readingTimeLocal": "2023-07-20T04:49:48.0"}, {"hrvValue":
|
||||
33, "readingTimeGMT": "2023-07-20T10:54:48.0", "readingTimeLocal": "2023-07-20T04:54:48.0"},
|
||||
{"hrvValue": 31, "readingTimeGMT": "2023-07-20T10:59:48.0", "readingTimeLocal":
|
||||
"2023-07-20T04:59:48.0"}, {"hrvValue": 11, "readingTimeGMT": "2023-07-20T11:04:48.0",
|
||||
"readingTimeLocal": "2023-07-20T05:04:48.0"}, {"hrvValue": 32, "readingTimeGMT":
|
||||
"2023-07-20T11:09:48.0", "readingTimeLocal": "2023-07-20T05:09:48.0"}, {"hrvValue":
|
||||
38, "readingTimeGMT": "2023-07-20T11:14:48.0", "readingTimeLocal": "2023-07-20T05:14:48.0"},
|
||||
{"hrvValue": 38, "readingTimeGMT": "2023-07-20T11:19:48.0", "readingTimeLocal":
|
||||
"2023-07-20T05:19:48.0"}, {"hrvValue": 29, "readingTimeGMT": "2023-07-20T11:24:48.0",
|
||||
"readingTimeLocal": "2023-07-20T05:24:48.0"}, {"hrvValue": 38, "readingTimeGMT":
|
||||
"2023-07-20T11:29:48.0", "readingTimeLocal": "2023-07-20T05:29:48.0"}, {"hrvValue":
|
||||
27, "readingTimeGMT": "2023-07-20T11:34:48.0", "readingTimeLocal": "2023-07-20T05:34:48.0"},
|
||||
{"hrvValue": 30, "readingTimeGMT": "2023-07-20T11:39:48.0", "readingTimeLocal":
|
||||
"2023-07-20T05:39:48.0"}, {"hrvValue": 55, "readingTimeGMT": "2023-07-20T11:44:48.0",
|
||||
"readingTimeLocal": "2023-07-20T05:44:48.0"}, {"hrvValue": 33, "readingTimeGMT":
|
||||
"2023-07-20T11:49:48.0", "readingTimeLocal": "2023-07-20T05:49:48.0"}, {"hrvValue":
|
||||
40, "readingTimeGMT": "2023-07-20T11:54:48.0", "readingTimeLocal": "2023-07-20T05:54:48.0"},
|
||||
{"hrvValue": 42, "readingTimeGMT": "2023-07-20T11:59:48.0", "readingTimeLocal":
|
||||
"2023-07-20T05:59:48.0"}, {"hrvValue": 50, "readingTimeGMT": "2023-07-20T12:04:48.0",
|
||||
"readingTimeLocal": "2023-07-20T06:04:48.0"}, {"hrvValue": 38, "readingTimeGMT":
|
||||
"2023-07-20T12:09:48.0", "readingTimeLocal": "2023-07-20T06:09:48.0"}], "startTimestampGMT":
|
||||
"2023-07-20T05:25:00.0", "endTimestampGMT": "2023-07-20T12:09:48.0", "startTimestampLocal":
|
||||
"2023-07-19T23:25:00.0", "endTimestampLocal": "2023-07-20T06:09:48.0", "sleepStartTimestampGMT":
|
||||
"2023-07-20T05:25:00.0", "sleepEndTimestampGMT": "2023-07-20T12:11:00.0",
|
||||
"sleepStartTimestampLocal": "2023-07-19T23:25:00.0", "sleepEndTimestampLocal":
|
||||
"2023-07-20T06:11:00.0"}'
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 7fea5791ac5b155e-QRO
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Wed, 30 Aug 2023 04:38:03 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=WPNDBwn%2Bfxqb0O6vEmlCrJ8G95qISZzDCH%2BKyUwlszTlFuY1obIOOv%2Flxxab8UwAeJMjXilnv4GT5%2Be6xR62sdLvcthLHgBQS9Imf3tn%2FOtkM0RaVRGP7MnFn1Klb26eMJo3fXWLjw%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cache-control:
|
||||
- no-cache, no-store, private
|
||||
pragma:
|
||||
- no-cache
|
||||
set-cookie:
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
Cookie:
|
||||
- _cfuvid=SANITIZED; ADRUM_BT1=SANITIZED; ADRUM_BTa=SANITIZED; SameSite=SANITIZED
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/hrv-service/hrv/2021-07-20
|
||||
response:
|
||||
body:
|
||||
string: ''
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 7fea5792ff3b154b-QRO
|
||||
Connection:
|
||||
- keep-alive
|
||||
Date:
|
||||
- Wed, 30 Aug 2023 04:38:04 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=AbFAU6ePddXCH6t5Ivd3iEs8icSg%2BboWeYfDzAOhakCxvGS0NvnCzxvNFMi5Cog2IvU82WhBEv8tUKYeIFzeOW%2BvE4c%2BQ1E4RCmoWq%2FeAc859qFp7U0wQgbZXS%2F5r8dBFEb0244AOA%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cache-control:
|
||||
- no-cache, no-store, private
|
||||
pragma:
|
||||
- no-cache
|
||||
set-cookie:
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
status:
|
||||
code: 204
|
||||
message: No Content
|
||||
version: 1
|
||||
162
garth/tests/data/cassettes/test_hrv_data_list.yaml
Normal file
162
garth/tests/data/cassettes/test_hrv_data_list.yaml
Normal file
@@ -0,0 +1,162 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/hrv-service/hrv/2023-07-20
|
||||
response:
|
||||
body:
|
||||
string: !!binary |
|
||||
H4sIAAAAAAAAA52aXU/bMBSG/4uvA/LxRxLnjg00LgAh6LiZJhRa01ZNP5S0IIT636cUiiizd975
|
||||
ksT49WMnT46TvopN59vrdvk4bfz1TFTKOsqlysSkfbrdzOd1+yKqVzGsG78Y1e1pvfaiEkoqfSSL
|
||||
IyVFJp69nzUvJ09jUWmXiabu1lfT8WS9O2LUpyP2cro4n44nosrzTDzUnW+mC9/33yyff65WvhWV
|
||||
3p1p6sXQjy6Wz2997g+8t7EqE/O6nfn2rm42XlTyWNltJrp1vd50ohLfTi5Orr6fnYpMPHo/eqiH
|
||||
s+tJW3f92M9v7u735+8LkYlh6+u1H0zn/nZdz1cHeANSFZmK6Lh0pdjupuXG16PpYtyJ6tdr//f7
|
||||
GKzJRPt2qu/rx+XgsCdpK+UqUx73c/ap4cVyWDefmpIbKP3RdJsdZORshjZwxr7plwziM3AOHeaQ
|
||||
bIbBOUyYw/IZOIdxiRkW57CpHBbnsEEOo5mMvJIIR99UfjT9wgFkODzDJWYQzkFBDkN8Bs5B4fUo
|
||||
2AyFc6gwh+UzcI6wr3TJZmicI+wrza+5xjl06v1hcI6wr4zjM3AOk8phcY6wr4Br1+IcNu26KnBf
|
||||
UcRX2vEZDs9wSddugfuKYr4CMnCOiK8sm6FwjrCvdM5n4BwqzbsF7iuK+MrwHBrn0GnPwQL3FUV8
|
||||
pfk1NzhH2FeKv88tzhH2lZZ8Bs6R6KsS95WK+Ip9fpS4r1TEV+y1W+K+UhFfaeIzcI6wrxQ/Vwrn
|
||||
iPhK8Rk4R9hXqmAzNM6hU9dD4xw67TlY4r5SMV/x62FwDpN6XVmcw6Z5t8R9pWK+4pzocF/pWH2l
|
||||
+AyHZ4Q5CjaDcI5IfcXPFeEcYV+xtajDfaVj+0F+rhTOodLqRIf7Kvb+iq13He6r2Psr1okO95VO
|
||||
ra8c7qvY+yvg2rU4R2Q/aPgMnCPiK2auSOK+MrH6SvMZDs8I1+2GzSCcI+wrW/AZOEfYV3nOZiic
|
||||
I+wrW/IZOEfYVzmxGRrn0Env4UjivjKx/aBiMwzOYVLvD4NzmKQ6kSTuKxOrr/g1tzhH2FfEZRDu
|
||||
K5tYXxHhvrKx+qpkMwjniOwHgQycI20/SIT7ysb2gzyHwjnS9oNEuK9sbD8o+QycQyd9VyPCfWVj
|
||||
9ZXmM3COtPqKCPeVjdVX/H1ucQ6b9L2WFO6rPOarks9weMae4/fuZwHtum/a9d/3gx/mbSXlrmO/
|
||||
GMVbHgzhsNfIV/xgv/8ecSa6xvvV7X8MevcPZ8zIe9/LWP/c8P9KCDPsM7Z/ALlCPuhYIgAA
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 812800c81f4c479e-DFW
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Encoding:
|
||||
- gzip
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Sat, 07 Oct 2023 17:53:20 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=1efXaZ9bQvYJF8jZkRNJeCENTFUGwDN6ljNCY3V9t8M17uF6Eq4aI4bI%2BrhUvg2sw5dEWCCf7SVlDKE0sYvbYE1MtrVNYOqposaDhcgu3w7t9t1Vpz1Lher36IKgicfzMAEw5RmXtw%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cache-control:
|
||||
- no-cache, no-store, private
|
||||
pragma:
|
||||
- no-cache
|
||||
set-cookie:
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
Cookie:
|
||||
- _cfuvid=SANITIZED; ADRUM_BT1=SANITIZED; ADRUM_BTa=SANITIZED; SameSite=SANITIZED
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/hrv-service/hrv/2023-07-19
|
||||
response:
|
||||
body:
|
||||
string: !!binary |
|
||||
H4sIAAAAAAAAA52aUW/TPBSG/4uvw+Rz7JPUuRsf6ONioIn14wYhFFqzVUu7Ke2Y0LT//qmFTVT4
|
||||
cF58ucTz68eOn9pOHtzdNk/n083X1ZjPr13Pkqj13Lir6dvF3Xo9TN9d/+AWw5g3y2F6Neyy6x17
|
||||
Di9894KSa9x9ztfj99Nvl64PqXHjsN29W11e7Q5XYvjlirxdbd6sLq9c3/rGfRm2eVxt8r7+8eb+
|
||||
v9vbPLk+tPs747BZ5OXZzf2POp8u/Cwj3Lj1MF3n6cMw3mXX+xOWx8Ztd8Pubut69/L07PTdP69f
|
||||
ucZ9zXn5ZVhcn19Nw3bf9jfvP3x+uv85usYtpjzs8ny1zhe7YX17hDen2MfQU3sSUnSPh255n4fl
|
||||
anO5df3Hh/3fP9sQuXHTj1v7uv59Oz+uyUsfUh9nJ94dFTy7WQzjr0Vncw7PRR+bv8yIEc54Knqc
|
||||
EcTOwDlimcObGYJzSJmjtTNwDilyhJmR0fYe4khz75+LHmdwZ2ckPKPIwcnMIJyDyuMBZOAcVB6P
|
||||
YGYwzsFlDrYzcA4uz49oZgScI5SfK/vZDThH2VdAX0WcQ/GVzRFxjkpftaCvDhlS+1wJziF186PD
|
||||
fUWKr5CMhGeUOTozg3AOqvv96HBfkeYrm4NxjrKvxOZgnIPr5keH+4oUX0WbI+AcyvpqZmZEnKPs
|
||||
KyQD54i181xwjkpfdbivSPOVNeYz3Fes+CqSnZHwjLp5PsN9xZqv2M7AOcq+EruvGOeo9NUM9xVr
|
||||
vrI5As6h+Moe84BzKOurZGZEnKPsK/F2Bs6hrK9sDsE5pJZDcA6pmx8J91VQfCViZyQ8o8zRmRmE
|
||||
c1DdeCTcV0HxVbT7inEOrluXJNxXQfPVzMwIOIfiq2Rn4BxlXwmbGRHniLXPVcQ5Yi2H4BxlX7U2
|
||||
h+Aciq+M54o87quo+SrYGQnPqBoP8rivouIra49DHvdV1Hxl9xXjHFx1XkIe91VUfBWimRFwjlC1
|
||||
PyeP+ypq+0E7I+IcdftB8rivouIrFjNDcI6yr9juK8E5yr5ia54T7ivR9oOdnZHwjKrzEiLcV1Lr
|
||||
K8J9Jdp+MJgZjHNw1XsDItxXovhK7DEPOEfZV2I/uwHnqFtfEeG+ksr3g0S4r0Q7v7IzBOeQut9B
|
||||
wn0liq+sNTUx7qtWO2/v7IyEZ1TtP4hxX7XafjDYGThHpa8Y91WrnV+JnYFzcNW5DzHuq1ZbX0U7
|
||||
A+eoez9IjPuq1dZX9phHnOPZV58On5tMu33R7f67EeWDD+8PFefNUi951ITjWtWvQwr1/rnFjduO
|
||||
Od9e/EWjD//w2mi5UB9Fq99q/m8JZYanjMf/AU1pYOmwJAAA
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 812800c8f836479e-DFW
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Encoding:
|
||||
- gzip
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Sat, 07 Oct 2023 17:53:20 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=aYF7%2Fjff6q%2BSW3DoLj4L0k%2BOVpTIJg1uTyZAJlPPxWfk8qcKnyoYSbdeMeFKDwiveT57hbjuS7p2fiY%2FKfvkmhY4ie2PKVugdBz29e64xajlg0jA3eE5e9fSMkV%2BZHMbJkLa4r%2BPOw%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cache-control:
|
||||
- no-cache, no-store, private
|
||||
pragma:
|
||||
- no-cache
|
||||
set-cookie:
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
170
garth/tests/data/cassettes/test_sleep_data_get.yaml
Normal file
170
garth/tests/data/cassettes/test_sleep_data_get.yaml
Normal file
@@ -0,0 +1,170 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/userprofile-service/socialProfile
|
||||
response:
|
||||
body:
|
||||
string: '{"id": 3154645, "profileId": 2591602, "garminGUID": "0690cc1d-d23d-4412-b027-80fd4ed1c0f6",
|
||||
"displayName": "mtamizi", "fullName": "Matin Tamizi", "userName": "mtamizi",
|
||||
"profileImageUuid": "73240e81-6e4d-43fc-8af8-c8f6c51b3b8f", "profileImageUrlLarge":
|
||||
"https://s3.amazonaws.com/garmin-connect-prod/profile_images/73240e81-6e4d-43fc-8af8-c8f6c51b3b8f-2591602.png",
|
||||
"profileImageUrlMedium": "https://s3.amazonaws.com/garmin-connect-prod/profile_images/685a19e9-a7be-4a11-9bf9-faca0c5d1f1a-2591602.png",
|
||||
"profileImageUrlSmall": "https://s3.amazonaws.com/garmin-connect-prod/profile_images/6302f021-0ec7-4dc9-b0c3-d5a19bc5a08c-2591602.png",
|
||||
"location": "Ciudad de M\u00e9xico, CDMX", "facebookUrl": null, "twitterUrl":
|
||||
null, "personalWebsite": null, "motivation": null, "bio": null, "primaryActivity":
|
||||
null, "favoriteActivityTypes": [], "runningTrainingSpeed": 0.0, "cyclingTrainingSpeed":
|
||||
0.0, "favoriteCyclingActivityTypes": [], "cyclingClassification": null, "cyclingMaxAvgPower":
|
||||
0.0, "swimmingTrainingSpeed": 0.0, "profileVisibility": "private", "activityStartVisibility":
|
||||
"private", "activityMapVisibility": "public", "courseVisibility": "public",
|
||||
"activityHeartRateVisibility": "public", "activityPowerVisibility": "public",
|
||||
"badgeVisibility": "private", "showAge": false, "showWeight": false, "showHeight":
|
||||
false, "showWeightClass": false, "showAgeRange": false, "showGender": false,
|
||||
"showActivityClass": false, "showVO2Max": false, "showPersonalRecords": false,
|
||||
"showLast12Months": false, "showLifetimeTotals": false, "showUpcomingEvents":
|
||||
false, "showRecentFavorites": false, "showRecentDevice": false, "showRecentGear":
|
||||
false, "showBadges": true, "otherActivity": null, "otherPrimaryActivity":
|
||||
null, "otherMotivation": null, "userRoles": ["SCOPE_ATP_READ", "SCOPE_ATP_WRITE",
|
||||
"SCOPE_COMMUNITY_COURSE_READ", "SCOPE_COMMUNITY_COURSE_WRITE", "SCOPE_CONNECT_READ",
|
||||
"SCOPE_CONNECT_WRITE", "SCOPE_DT_CLIENT_ANALYTICS_WRITE", "SCOPE_GARMINPAY_READ",
|
||||
"SCOPE_GARMINPAY_WRITE", "SCOPE_GCOFFER_READ", "SCOPE_GCOFFER_WRITE", "SCOPE_GHS_SAMD",
|
||||
"SCOPE_GHS_UPLOAD", "SCOPE_GOLF_API_READ", "SCOPE_GOLF_API_WRITE", "SCOPE_INSIGHTS_READ",
|
||||
"SCOPE_INSIGHTS_WRITE", "SCOPE_PRODUCT_SEARCH_READ", "ROLE_CONNECTUSER", "ROLE_FITNESS_USER",
|
||||
"ROLE_WELLNESS_USER", "ROLE_OUTDOOR_USER", "ROLE_CONNECT_2_USER", "ROLE_TACX_APP_USER"],
|
||||
"nameApproved": true, "userProfileFullName": "Matin Tamizi", "makeGolfScorecardsPrivate":
|
||||
true, "allowGolfLiveScoring": false, "allowGolfScoringByConnections": true,
|
||||
"userLevel": 3, "userPoint": 117, "levelUpdateDate": "2020-12-12T15:20:38.0",
|
||||
"levelIsViewed": false, "levelPointThreshold": 140, "userPointOffset": 0,
|
||||
"userPro": false}'
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 7f12d0081a724797-DFW
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json;charset=UTF-8
|
||||
Date:
|
||||
- Fri, 04 Aug 2023 00:51:33 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=7Jm3%2BQkQNnW8yDQr8zz%2FW93FRmApY0UugFBzK5SgwmO8atpbxSWdU7uz2UcLfuGZajQJkLQ28E%2FBQ7f0ki9S6f2eY3EDSfVn7CNNgmuZdvo38guNpuMK5guqKP3cYIQy4fF5GLPHUQ%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cache-control:
|
||||
- no-cache, no-store, private
|
||||
pragma:
|
||||
- no-cache
|
||||
set-cookie:
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
Cookie:
|
||||
- _cfuvid=SANITIZED; ADRUM_BT1=SANITIZED; ADRUM_BTa=SANITIZED; SameSite=SANITIZED
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/wellness-service/wellness/dailySleepData/mtamizi?nonSleepBufferMinutes=60&date=2021-07-20
|
||||
response:
|
||||
body:
|
||||
string: '{"dailySleepDTO": {"id": 1626758400000, "userProfilePK": 2591602, "calendarDate":
|
||||
"2021-07-20", "sleepTimeSeconds": 25740, "napTimeSeconds": 0, "sleepWindowConfirmed":
|
||||
true, "sleepWindowConfirmationType": "enhanced_confirmed_final", "sleepStartTimestampGMT":
|
||||
1626758400000, "sleepEndTimestampGMT": 1626785940000, "sleepStartTimestampLocal":
|
||||
1626740400000, "sleepEndTimestampLocal": 1626767940000, "autoSleepStartTimestampGMT":
|
||||
null, "autoSleepEndTimestampGMT": null, "sleepQualityTypePK": null, "sleepResultTypePK":
|
||||
null, "unmeasurableSleepSeconds": 0, "deepSleepSeconds": 4440, "lightSleepSeconds":
|
||||
18720, "remSleepSeconds": 2580, "awakeSleepSeconds": 1800, "deviceRemCapable":
|
||||
true, "retro": false, "sleepFromDevice": true, "averageSpO2Value": 92.0, "lowestSpO2Value":
|
||||
84, "highestSpO2Value": 100, "averageSpO2HRSleep": 51.0, "averageRespirationValue":
|
||||
15.0, "lowestRespirationValue": 8.0, "highestRespirationValue": 18.0, "sleepVersion":
|
||||
1}, "sleepMovement": [], "remSleepData": true, "sleepLevels": [], "wellnessSpO2SleepSummaryDTO":
|
||||
{"userProfilePk": 2591602, "deviceId": 3329978681, "sleepMeasurementStartGMT":
|
||||
"2021-07-20T05:21:00.0", "sleepMeasurementEndGMT": "2021-07-20T12:56:00.0",
|
||||
"alertThresholdValue": null, "numberOfEventsBelowThreshold": null, "durationOfEventsBelowThreshold":
|
||||
null, "averageSPO2": 92.0, "averageSpO2HR": 51.0, "lowestSPO2": 84}}'
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 7f12d0093b58b6e2-QRO
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json;charset=UTF-8
|
||||
Date:
|
||||
- Fri, 04 Aug 2023 00:51:34 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=asU9NqPsuSmxxN96St1ca3wY06WSkANwfXch1MOBitkjzSuq45fyuSUC%2BXGE%2F7S9FHwRT13HYOi%2FDLINfwbYmWT9Vrwld3zjhkDxpGkSP2EsN7vdWSKXrF23uV3RSIoBkU9JthnrZQ%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cache-control:
|
||||
- no-cache, no-store, private
|
||||
pragma:
|
||||
- no-cache
|
||||
set-cookie:
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
1665
garth/tests/data/cassettes/test_sleep_data_list.yaml
Normal file
1665
garth/tests/data/cassettes/test_sleep_data_list.yaml
Normal file
File diff suppressed because it is too large
Load Diff
97
garth/tests/data/cassettes/test_weight_data_list.yaml
Normal file
97
garth/tests/data/cassettes/test_weight_data_list.yaml
Normal file
@@ -0,0 +1,97 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- GCM-iOS-5.7.2.1
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/weight-service/weight/range/2025-06-01/2025-06-15?includeAll=true
|
||||
response:
|
||||
body:
|
||||
string: '{"dailyWeightSummaries": [{"summaryDate": "2025-06-15", "numOfWeightEntries":
|
||||
1, "minWeight": 59720.0, "maxWeight": 59720.0, "latestWeight": {"samplePk":
|
||||
1749996902851, "date": 1749975276000, "calendarDate": "2025-06-15", "weight":
|
||||
59720.0, "bmi": 22.799999237060547, "bodyFat": 19.3, "bodyWater": 58.9, "boneMass":
|
||||
3539, "muscleMass": 26979, "physiqueRating": null, "visceralFat": null, "metabolicAge":
|
||||
null, "sourceType": "INDEX_SCALE", "timestampGMT": 1749996876000, "weightDelta":
|
||||
200.00000000000284}, "allWeightMetrics": [{"samplePk": 1749996902851, "date":
|
||||
1749975276000, "calendarDate": "2025-06-15", "weight": 59720.0, "bmi": 22.799999237060547,
|
||||
"bodyFat": 19.3, "bodyWater": 58.9, "boneMass": 3539, "muscleMass": 26979,
|
||||
"physiqueRating": null, "visceralFat": null, "metabolicAge": null, "sourceType":
|
||||
"INDEX_SCALE", "timestampGMT": 1749996876000, "weightDelta": 200.00000000000284}]},
|
||||
{"summaryDate": "2025-06-14", "numOfWeightEntries": 2, "minWeight": 59130.0,
|
||||
"maxWeight": 59500.0, "latestWeight": {"samplePk": 1749948744411, "date":
|
||||
1749927125175, "calendarDate": "2025-06-14", "weight": 59500.0, "bmi": null,
|
||||
"bodyFat": null, "bodyWater": null, "boneMass": null, "muscleMass": null,
|
||||
"physiqueRating": null, "visceralFat": null, "metabolicAge": null, "sourceType":
|
||||
"MANUAL", "timestampGMT": 1749948725175, "weightDelta": 299.99999999999716},
|
||||
"allWeightMetrics": [{"samplePk": 1749948744411, "date": 1749927125175, "calendarDate":
|
||||
"2025-06-14", "weight": 59500.0, "bmi": null, "bodyFat": null, "bodyWater":
|
||||
null, "boneMass": null, "muscleMass": null, "physiqueRating": null, "visceralFat":
|
||||
null, "metabolicAge": null, "sourceType": "MANUAL", "timestampGMT": 1749948725175,
|
||||
"weightDelta": 399.9999999999986}, {"samplePk": 1749909217098, "date": 1749887580000,
|
||||
"calendarDate": "2025-06-14", "weight": 59130.0, "bmi": 22.5, "bodyFat": 20.3,
|
||||
"bodyWater": 58.2, "boneMass": 3430, "muscleMass": 26840, "physiqueRating":
|
||||
null, "visceralFat": null, "metabolicAge": null, "sourceType": "INDEX_SCALE",
|
||||
"timestampGMT": 1749909180000, "weightDelta": -100.00000000000142}]}, {"summaryDate":
|
||||
"2025-06-07", "numOfWeightEntries": 1, "minWeight": 59189.0, "maxWeight":
|
||||
59189.0, "latestWeight": {"samplePk": 1749307692871, "date": 1749286058000,
|
||||
"calendarDate": "2025-06-07", "weight": 59189.0, "bmi": 22.600000381469727,
|
||||
"bodyFat": 20.0, "bodyWater": 58.4, "boneMass": 3450, "muscleMass": 26850,
|
||||
"physiqueRating": null, "visceralFat": null, "metabolicAge": null, "sourceType":
|
||||
"INDEX_SCALE", "timestampGMT": 1749307658000, "weightDelta": 500.0}, "allWeightMetrics":
|
||||
[{"samplePk": 1749307692871, "date": 1749286058000, "calendarDate": "2025-06-07",
|
||||
"weight": 59189.0, "bmi": 22.600000381469727, "bodyFat": 20.0, "bodyWater":
|
||||
58.4, "boneMass": 3450, "muscleMass": 26850, "physiqueRating": null, "visceralFat":
|
||||
null, "metabolicAge": null, "sourceType": "INDEX_SCALE", "timestampGMT": 1749307658000,
|
||||
"weightDelta": 500.0}]}], "totalAverage": {"from": 1748736000000, "until":
|
||||
1750031999999, "weight": 59469.666666666664, "bmi": 22.699999809265137, "bodyFat":
|
||||
19.7, "bodyWater": 58.7, "boneMass": 3494, "muscleMass": 26914, "physiqueRating":
|
||||
null, "visceralFat": null, "metabolicAge": null}, "previousDateWeight": {"samplePk":
|
||||
1748709464191, "date": 1748687805000, "calendarDate": "2025-05-31", "weight":
|
||||
58700.0, "bmi": 22.399999618530273, "bodyFat": 20.7, "bodyWater": 57.9, "boneMass":
|
||||
3369, "muscleMass": 26729, "physiqueRating": null, "visceralFat": null, "metabolicAge":
|
||||
null, "sourceType": "INDEX_SCALE", "timestampGMT": 1748709405000, "weightDelta":
|
||||
null}, "nextDateWeight": {"samplePk": null, "date": null, "calendarDate":
|
||||
null, "weight": null, "bmi": null, "bodyFat": null, "bodyWater": null, "boneMass":
|
||||
null, "muscleMass": null, "physiqueRating": null, "visceralFat": null, "metabolicAge":
|
||||
null, "sourceType": null, "timestampGMT": null, "weightDelta": null}}'
|
||||
headers:
|
||||
CF-RAY:
|
||||
- 95064b68690b1f84-QRO
|
||||
Cache-Control:
|
||||
- no-cache, no-store, private
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Mon, 16 Jun 2025 00:47:12 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=9ExXlb7xohhNWV42MyBDMFTW8fcvDW7s7NvGZZsQ7QjtfUyT%2FYq2LTKDwbG1mdwpyUYacYYBjzSOZNw3Tu8MfsrfLBPOgWE4LsgwmirKigo5mVi5%2FJqIfZgyWULExMxPoeX88D%2B6iXcluwUE8dlp4VL2yA%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
pragma:
|
||||
- no-cache
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
61
garth/tests/data/cassettes/test_weight_data_list_empty.yaml
Normal file
61
garth/tests/data/cassettes/test_weight_data_list_empty.yaml
Normal file
@@ -0,0 +1,61 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- GCM-iOS-5.7.2.1
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/weight-service/weight/range/2019-12-18/2020-01-01?includeAll=true
|
||||
response:
|
||||
body:
|
||||
string: '{"dailyWeightSummaries": [], "totalAverage": {"from": 1576627200000,
|
||||
"until": 1577923199999, "weight": null, "bmi": null, "bodyFat": null, "bodyWater":
|
||||
null, "boneMass": null, "muscleMass": null, "physiqueRating": null, "visceralFat":
|
||||
null, "metabolicAge": null}, "previousDateWeight": {"samplePk": null, "date":
|
||||
null, "calendarDate": null, "weight": null, "bmi": null, "bodyFat": null,
|
||||
"bodyWater": null, "boneMass": null, "muscleMass": null, "physiqueRating":
|
||||
null, "visceralFat": null, "metabolicAge": null, "sourceType": null, "timestampGMT":
|
||||
null, "weightDelta": null}, "nextDateWeight": {"samplePk": null, "date": null,
|
||||
"calendarDate": null, "weight": null, "bmi": null, "bodyFat": null, "bodyWater":
|
||||
null, "boneMass": null, "muscleMass": null, "physiqueRating": null, "visceralFat":
|
||||
null, "metabolicAge": null, "sourceType": null, "timestampGMT": null, "weightDelta":
|
||||
null}}'
|
||||
headers:
|
||||
CF-RAY:
|
||||
- 95065fe3afea2fde-QRO
|
||||
Cache-Control:
|
||||
- no-cache, no-store, private
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Mon, 16 Jun 2025 01:01:11 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=YZscbyvERqaC8NIT00%2Fv%2FV3X%2BSEXE8LosKNrrmILOYZWBqF906BX31WBG9C5u4NtRh73%2BRPj2OyWX%2FgtYZcd9QricbIkwL8Np9kXdKtPcufnwwAg7odFD4X4JnUeS0d%2BEk1u5Kb2VAIyW4O%2BxfR1FNVErw%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
pragma:
|
||||
- no-cache
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
@@ -0,0 +1,77 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- GCM-iOS-5.7.2.1
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/weight-service/weight/range/2025-06-14/2025-06-14?includeAll=true
|
||||
response:
|
||||
body:
|
||||
string: '{"dailyWeightSummaries": [{"summaryDate": "2025-06-14", "numOfWeightEntries":
|
||||
2, "minWeight": 59130.0, "maxWeight": 59500.0, "latestWeight": {"samplePk":
|
||||
1749948744411, "date": 1749927125175, "calendarDate": "2025-06-14", "weight":
|
||||
59500.0, "bmi": null, "bodyFat": null, "bodyWater": null, "boneMass": null,
|
||||
"muscleMass": null, "physiqueRating": null, "visceralFat": null, "metabolicAge":
|
||||
null, "sourceType": "MANUAL", "timestampGMT": 1749948725175, "weightDelta":
|
||||
299.99999999999716}, "allWeightMetrics": [{"samplePk": 1749948744411, "date":
|
||||
1749927125175, "calendarDate": "2025-06-14", "weight": 59500.0, "bmi": null,
|
||||
"bodyFat": null, "bodyWater": null, "boneMass": null, "muscleMass": null,
|
||||
"physiqueRating": null, "visceralFat": null, "metabolicAge": null, "sourceType":
|
||||
"MANUAL", "timestampGMT": 1749948725175, "weightDelta": 399.9999999999986},
|
||||
{"samplePk": 1749909217098, "date": 1749887580000, "calendarDate": "2025-06-14",
|
||||
"weight": 59130.0, "bmi": 22.5, "bodyFat": 20.3, "bodyWater": 58.2, "boneMass":
|
||||
3430, "muscleMass": 26840, "physiqueRating": null, "visceralFat": null, "metabolicAge":
|
||||
null, "sourceType": "INDEX_SCALE", "timestampGMT": 1749909180000, "weightDelta":
|
||||
-100.00000000000142}]}], "totalAverage": {"from": 1749859200000, "until":
|
||||
1749945599999, "weight": 59500.0, "bmi": null, "bodyFat": null, "bodyWater":
|
||||
null, "boneMass": null, "muscleMass": null, "physiqueRating": null, "visceralFat":
|
||||
null, "metabolicAge": null}, "previousDateWeight": {"samplePk": 1749307692871,
|
||||
"date": 1749286058000, "calendarDate": "2025-06-07", "weight": 59189.0, "bmi":
|
||||
22.600000381469727, "bodyFat": 20.0, "bodyWater": 58.4, "boneMass": 3450,
|
||||
"muscleMass": 26850, "physiqueRating": null, "visceralFat": null, "metabolicAge":
|
||||
null, "sourceType": "INDEX_SCALE", "timestampGMT": 1749307658000, "weightDelta":
|
||||
null}, "nextDateWeight": {"samplePk": 1749996902851, "date": 1749975276000,
|
||||
"calendarDate": "2025-06-15", "weight": 59720.0, "bmi": 22.799999237060547,
|
||||
"bodyFat": 19.3, "bodyWater": 58.9, "boneMass": 3539, "muscleMass": 26979,
|
||||
"physiqueRating": null, "visceralFat": null, "metabolicAge": null, "sourceType":
|
||||
"INDEX_SCALE", "timestampGMT": 1749996876000, "weightDelta": null}}'
|
||||
headers:
|
||||
CF-RAY:
|
||||
- 9506620b6d30b6e5-QRO
|
||||
Cache-Control:
|
||||
- no-cache, no-store, private
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Mon, 16 Jun 2025 01:02:39 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=TNhpJPozun%2FMkRaaYU%2FYH7B2ZrX%2BfDEbq2rKyeuDmDpxQgshSkQTs0eOWr0XO1i1EwLmf%2F0SYtAecesFwm0eugpiNFopVrsvBlvMj1vk3RaPwL37B46JBHOYvrvm1OhMNzNqTF7QWLHAFYe%2Bk6zeTmTcVQ%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
pragma:
|
||||
- no-cache
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
335
garth/tests/data/test_body_battery_data.py
Normal file
335
garth/tests/data/test_body_battery_data.py
Normal file
@@ -0,0 +1,335 @@
|
||||
from datetime import date
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from garth import BodyBatteryData, DailyBodyBatteryStress
|
||||
from garth.http import Client
|
||||
|
||||
|
||||
@pytest.mark.vcr
|
||||
def test_body_battery_data_get(authed_client: Client):
|
||||
body_battery_data = BodyBatteryData.get("2023-07-20", client=authed_client)
|
||||
assert isinstance(body_battery_data, list)
|
||||
|
||||
if body_battery_data:
|
||||
# Check first event if available
|
||||
event = body_battery_data[0]
|
||||
assert event is not None
|
||||
|
||||
# Test body battery readings property
|
||||
readings = event.body_battery_readings
|
||||
assert isinstance(readings, list)
|
||||
|
||||
if readings:
|
||||
# Test reading structure
|
||||
reading = readings[0]
|
||||
assert hasattr(reading, "timestamp")
|
||||
assert hasattr(reading, "status")
|
||||
assert hasattr(reading, "level")
|
||||
assert hasattr(reading, "version")
|
||||
|
||||
# Test level properties
|
||||
assert event.current_level is not None and isinstance(
|
||||
event.current_level, int
|
||||
)
|
||||
assert event.max_level is not None and isinstance(
|
||||
event.max_level, int
|
||||
)
|
||||
assert event.min_level is not None and isinstance(
|
||||
event.min_level, int
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.vcr
|
||||
def test_body_battery_data_list(authed_client: Client):
|
||||
days = 3
|
||||
end = date(2023, 7, 20)
|
||||
body_battery_data = BodyBatteryData.list(end, days, client=authed_client)
|
||||
assert isinstance(body_battery_data, list)
|
||||
|
||||
# Test that we get data (may be empty if no events)
|
||||
assert len(body_battery_data) >= 0
|
||||
|
||||
|
||||
@pytest.mark.vcr
|
||||
def test_daily_body_battery_stress_get(authed_client: Client):
|
||||
daily_data = DailyBodyBatteryStress.get("2023-07-20", client=authed_client)
|
||||
|
||||
if daily_data:
|
||||
# Test basic structure
|
||||
assert daily_data.user_profile_pk
|
||||
assert daily_data.calendar_date == date(2023, 7, 20)
|
||||
assert daily_data.start_timestamp_gmt
|
||||
assert daily_data.end_timestamp_gmt
|
||||
|
||||
# Test stress data
|
||||
assert isinstance(daily_data.max_stress_level, int)
|
||||
assert isinstance(daily_data.avg_stress_level, int)
|
||||
assert isinstance(daily_data.stress_values_array, list)
|
||||
assert isinstance(daily_data.body_battery_values_array, list)
|
||||
|
||||
# Test stress readings property
|
||||
stress_readings = daily_data.stress_readings
|
||||
assert isinstance(stress_readings, list)
|
||||
|
||||
if stress_readings:
|
||||
stress_reading = stress_readings[0]
|
||||
assert hasattr(stress_reading, "timestamp")
|
||||
assert hasattr(stress_reading, "stress_level")
|
||||
|
||||
# Test body battery readings property
|
||||
bb_readings = daily_data.body_battery_readings
|
||||
assert isinstance(bb_readings, list)
|
||||
|
||||
if bb_readings:
|
||||
bb_reading = bb_readings[0]
|
||||
assert hasattr(bb_reading, "timestamp")
|
||||
assert hasattr(bb_reading, "status")
|
||||
assert hasattr(bb_reading, "level")
|
||||
assert hasattr(bb_reading, "version")
|
||||
|
||||
# Test computed properties
|
||||
assert daily_data.current_body_battery is not None and isinstance(
|
||||
daily_data.current_body_battery, int
|
||||
)
|
||||
assert daily_data.max_body_battery is not None and isinstance(
|
||||
daily_data.max_body_battery, int
|
||||
)
|
||||
assert daily_data.min_body_battery is not None and isinstance(
|
||||
daily_data.min_body_battery, int
|
||||
)
|
||||
|
||||
# Test body battery change
|
||||
if len(bb_readings) >= 2:
|
||||
change = daily_data.body_battery_change
|
||||
assert change is not None
|
||||
|
||||
|
||||
@pytest.mark.vcr
|
||||
def test_daily_body_battery_stress_get_no_data(authed_client: Client):
|
||||
# Test with a date that likely has no data
|
||||
daily_data = DailyBodyBatteryStress.get("2020-01-01", client=authed_client)
|
||||
# Should return None if no data available
|
||||
assert daily_data is None or isinstance(daily_data, DailyBodyBatteryStress)
|
||||
|
||||
|
||||
@pytest.mark.vcr
|
||||
def test_daily_body_battery_stress_list(authed_client: Client):
|
||||
days = 3
|
||||
end = date(2023, 7, 20)
|
||||
# Use max_workers=1 to avoid VCR issues with concurrent requests
|
||||
daily_data_list = DailyBodyBatteryStress.list(
|
||||
end, days, client=authed_client, max_workers=1
|
||||
)
|
||||
assert isinstance(daily_data_list, list)
|
||||
assert (
|
||||
len(daily_data_list) <= days
|
||||
) # May be less if some days have no data
|
||||
|
||||
# Test that each item is correct type
|
||||
for daily_data in daily_data_list:
|
||||
assert isinstance(daily_data, DailyBodyBatteryStress)
|
||||
assert isinstance(daily_data.calendar_date, date)
|
||||
assert daily_data.user_profile_pk
|
||||
|
||||
|
||||
@pytest.mark.vcr
|
||||
def test_body_battery_properties_edge_cases(authed_client: Client):
|
||||
# Test empty data handling
|
||||
daily_data = DailyBodyBatteryStress.get("2023-07-20", client=authed_client)
|
||||
|
||||
if daily_data:
|
||||
# Test with potentially empty arrays
|
||||
if not daily_data.body_battery_values_array:
|
||||
assert daily_data.body_battery_readings == []
|
||||
assert daily_data.current_body_battery is None
|
||||
assert daily_data.max_body_battery is None
|
||||
assert daily_data.min_body_battery is None
|
||||
assert daily_data.body_battery_change is None
|
||||
|
||||
if not daily_data.stress_values_array:
|
||||
assert daily_data.stress_readings == []
|
||||
|
||||
|
||||
# Error handling tests for BodyBatteryData.get()
|
||||
def test_body_battery_data_get_api_error():
|
||||
"""Test handling of API errors."""
|
||||
mock_client = MagicMock()
|
||||
mock_client.connectapi.side_effect = Exception("API Error")
|
||||
|
||||
result = BodyBatteryData.get("2023-07-20", client=mock_client)
|
||||
assert result == []
|
||||
|
||||
|
||||
def test_body_battery_data_get_invalid_response():
|
||||
"""Test handling of non-list responses."""
|
||||
mock_client = MagicMock()
|
||||
mock_client.connectapi.return_value = {"error": "Invalid response"}
|
||||
|
||||
result = BodyBatteryData.get("2023-07-20", client=mock_client)
|
||||
assert result == []
|
||||
|
||||
|
||||
def test_body_battery_data_get_missing_event_data():
|
||||
"""Test handling of items with missing event data."""
|
||||
mock_client = MagicMock()
|
||||
mock_client.connectapi.return_value = [
|
||||
{"activityName": "Test", "averageStress": 25} # Missing "event" key
|
||||
]
|
||||
|
||||
result = BodyBatteryData.get("2023-07-20", client=mock_client)
|
||||
assert len(result) == 1
|
||||
assert result[0].event is None
|
||||
|
||||
|
||||
def test_body_battery_data_get_missing_event_start_time():
|
||||
"""Test handling of event data missing eventStartTimeGmt."""
|
||||
mock_client = MagicMock()
|
||||
mock_client.connectapi.return_value = [
|
||||
{
|
||||
"event": {"eventType": "sleep"}, # Missing eventStartTimeGmt
|
||||
"activityName": "Test",
|
||||
"averageStress": 25,
|
||||
}
|
||||
]
|
||||
|
||||
result = BodyBatteryData.get("2023-07-20", client=mock_client)
|
||||
assert result == [] # Should skip invalid items
|
||||
|
||||
|
||||
def test_body_battery_data_get_invalid_datetime_format():
|
||||
"""Test handling of invalid datetime format."""
|
||||
mock_client = MagicMock()
|
||||
mock_client.connectapi.return_value = [
|
||||
{
|
||||
"event": {
|
||||
"eventType": "sleep",
|
||||
"eventStartTimeGmt": "invalid-date",
|
||||
},
|
||||
"activityName": "Test",
|
||||
"averageStress": 25,
|
||||
}
|
||||
]
|
||||
|
||||
result = BodyBatteryData.get("2023-07-20", client=mock_client)
|
||||
assert result == [] # Should skip invalid items
|
||||
|
||||
|
||||
def test_body_battery_data_get_invalid_field_types():
|
||||
"""Test handling of invalid field types."""
|
||||
mock_client = MagicMock()
|
||||
mock_client.connectapi.return_value = [
|
||||
{
|
||||
"event": {
|
||||
"eventType": "sleep",
|
||||
"eventStartTimeGmt": "2023-07-20T10:00:00.000Z",
|
||||
"timezoneOffset": "invalid", # Should be number
|
||||
"durationInMilliseconds": "invalid", # Should be number
|
||||
"bodyBatteryImpact": "invalid", # Should be number
|
||||
},
|
||||
"activityName": "Test",
|
||||
"averageStress": "invalid", # Should be number
|
||||
"stressValuesArray": "invalid", # Should be list
|
||||
"bodyBatteryValuesArray": "invalid", # Should be list
|
||||
}
|
||||
]
|
||||
|
||||
result = BodyBatteryData.get("2023-07-20", client=mock_client)
|
||||
assert len(result) == 1
|
||||
# Should handle invalid types gracefully
|
||||
|
||||
|
||||
def test_body_battery_data_get_validation_error():
|
||||
"""Test handling of validation errors during object creation."""
|
||||
mock_client = MagicMock()
|
||||
mock_client.connectapi.return_value = [
|
||||
{
|
||||
"event": {
|
||||
"eventType": "sleep",
|
||||
"eventStartTimeGmt": "2023-07-20T10:00:00.000Z",
|
||||
# Missing required fields for BodyBatteryEvent
|
||||
},
|
||||
# Missing required fields for BodyBatteryData
|
||||
}
|
||||
]
|
||||
|
||||
result = BodyBatteryData.get("2023-07-20", client=mock_client)
|
||||
# Should handle validation errors and continue processing
|
||||
assert isinstance(result, list)
|
||||
assert len(result) == 1 # Should create object with missing fields as None
|
||||
assert result[0].event is not None # Event should be created
|
||||
assert result[0].activity_name is None # Missing fields should be None
|
||||
|
||||
|
||||
def test_body_battery_data_get_mixed_valid_invalid():
|
||||
"""Test processing with mix of valid and invalid items."""
|
||||
mock_client = MagicMock()
|
||||
mock_client.connectapi.return_value = [
|
||||
{
|
||||
"event": {
|
||||
"eventType": "sleep",
|
||||
"eventStartTimeGmt": "2023-07-20T10:00:00.000Z",
|
||||
"timezoneOffset": -25200000,
|
||||
"durationInMilliseconds": 28800000,
|
||||
"bodyBatteryImpact": 35,
|
||||
"feedbackType": "good_sleep",
|
||||
"shortFeedback": "Good sleep",
|
||||
},
|
||||
"activityName": None,
|
||||
"activityType": None,
|
||||
"activityId": None,
|
||||
"averageStress": 15.5,
|
||||
"stressValuesArray": [[1689811800000, 12]],
|
||||
"bodyBatteryValuesArray": [[1689811800000, "charging", 45, 1.0]],
|
||||
},
|
||||
{
|
||||
# Invalid - missing eventStartTimeGmt
|
||||
"event": {"eventType": "sleep"},
|
||||
"activityName": "Test",
|
||||
},
|
||||
]
|
||||
|
||||
result = BodyBatteryData.get("2023-07-20", client=mock_client)
|
||||
# Should process valid items and skip invalid ones
|
||||
assert len(result) == 1 # Only the valid item should be processed
|
||||
assert result[0].event is not None
|
||||
|
||||
|
||||
def test_body_battery_data_get_unexpected_error():
|
||||
"""Test handling of unexpected errors during object creation."""
|
||||
mock_client = MagicMock()
|
||||
|
||||
# Create a special object that raises an exception when accessed
|
||||
class ExceptionRaisingDict(dict):
|
||||
def get(self, key, default=None):
|
||||
if key == "activityName":
|
||||
raise RuntimeError("Unexpected error during object creation")
|
||||
return super().get(key, default)
|
||||
|
||||
# Create mock data with problematic item
|
||||
mock_response_item = ExceptionRaisingDict(
|
||||
{
|
||||
"event": {
|
||||
"eventType": "sleep",
|
||||
"eventStartTimeGmt": "2023-07-20T10:00:00.000Z",
|
||||
"timezoneOffset": -25200000,
|
||||
"durationInMilliseconds": 28800000,
|
||||
"bodyBatteryImpact": 35,
|
||||
"feedbackType": "good_sleep",
|
||||
"shortFeedback": "Good sleep",
|
||||
},
|
||||
"activityName": None,
|
||||
"activityType": None,
|
||||
"activityId": None,
|
||||
"averageStress": 15.5,
|
||||
"stressValuesArray": [[1689811800000, 12]],
|
||||
"bodyBatteryValuesArray": [[1689811800000, "charging", 45, 1.0]],
|
||||
}
|
||||
)
|
||||
|
||||
mock_client.connectapi.return_value = [mock_response_item]
|
||||
|
||||
result = BodyBatteryData.get("2023-07-20", client=mock_client)
|
||||
# Should handle unexpected errors and return empty list
|
||||
assert result == []
|
||||
25
garth/tests/data/test_hrv_data.py
Normal file
25
garth/tests/data/test_hrv_data.py
Normal file
@@ -0,0 +1,25 @@
|
||||
from datetime import date
|
||||
|
||||
import pytest
|
||||
|
||||
from garth import HRVData
|
||||
from garth.http import Client
|
||||
|
||||
|
||||
@pytest.mark.vcr
|
||||
def test_hrv_data_get(authed_client: Client):
|
||||
hrv_data = HRVData.get("2023-07-20", client=authed_client)
|
||||
assert hrv_data
|
||||
assert hrv_data.user_profile_pk
|
||||
assert hrv_data.hrv_summary.calendar_date == date(2023, 7, 20)
|
||||
|
||||
assert HRVData.get("2021-07-20", client=authed_client) is None
|
||||
|
||||
|
||||
@pytest.mark.vcr
|
||||
def test_hrv_data_list(authed_client: Client):
|
||||
days = 2
|
||||
end = date(2023, 7, 20)
|
||||
hrv_data = HRVData.list(end, days, client=authed_client, max_workers=1)
|
||||
assert len(hrv_data) == days
|
||||
assert hrv_data[-1].hrv_summary.calendar_date == end
|
||||
24
garth/tests/data/test_sleep_data.py
Normal file
24
garth/tests/data/test_sleep_data.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from datetime import date
|
||||
|
||||
import pytest
|
||||
|
||||
from garth import SleepData
|
||||
from garth.http import Client
|
||||
|
||||
|
||||
@pytest.mark.vcr
|
||||
def test_sleep_data_get(authed_client: Client):
|
||||
sleep_data = SleepData.get("2021-07-20", client=authed_client)
|
||||
assert sleep_data
|
||||
assert sleep_data.daily_sleep_dto.calendar_date == date(2021, 7, 20)
|
||||
assert sleep_data.daily_sleep_dto.sleep_start
|
||||
assert sleep_data.daily_sleep_dto.sleep_end
|
||||
|
||||
|
||||
@pytest.mark.vcr
|
||||
def test_sleep_data_list(authed_client: Client):
|
||||
end = date(2021, 7, 20)
|
||||
days = 20
|
||||
sleep_data = SleepData.list(end, days, client=authed_client, max_workers=1)
|
||||
assert sleep_data[-1].daily_sleep_dto.calendar_date == end
|
||||
assert len(sleep_data) == days
|
||||
74
garth/tests/data/test_weight_data.py
Normal file
74
garth/tests/data/test_weight_data.py
Normal file
@@ -0,0 +1,74 @@
|
||||
from datetime import date, timedelta, timezone
|
||||
|
||||
import pytest
|
||||
|
||||
from garth.data import WeightData
|
||||
from garth.http import Client
|
||||
|
||||
|
||||
@pytest.mark.vcr
|
||||
def test_get_daily_weight_data(authed_client: Client):
|
||||
weight_data = WeightData.get(date(2025, 6, 15), client=authed_client)
|
||||
assert weight_data is not None
|
||||
assert weight_data.source_type == "INDEX_SCALE"
|
||||
assert weight_data.weight is not None
|
||||
assert weight_data.bmi is not None
|
||||
assert weight_data.body_fat is not None
|
||||
assert weight_data.body_water is not None
|
||||
assert weight_data.bone_mass is not None
|
||||
assert weight_data.muscle_mass is not None
|
||||
# Timezone should match your account settings, my case is -6
|
||||
assert weight_data.datetime_local.tzinfo == timezone(timedelta(hours=-6))
|
||||
assert weight_data.datetime_utc.tzinfo == timezone.utc
|
||||
|
||||
|
||||
@pytest.mark.vcr
|
||||
def test_get_manual_weight_data(authed_client: Client):
|
||||
weight_data = WeightData.get(date(2025, 6, 14), client=authed_client)
|
||||
assert weight_data is not None
|
||||
assert weight_data.source_type == "MANUAL"
|
||||
assert weight_data.weight is not None
|
||||
assert weight_data.bmi is None
|
||||
assert weight_data.body_fat is None
|
||||
assert weight_data.body_water is None
|
||||
assert weight_data.bone_mass is None
|
||||
assert weight_data.muscle_mass is None
|
||||
|
||||
|
||||
@pytest.mark.vcr
|
||||
def test_get_nonexistent_weight_data(authed_client: Client):
|
||||
weight_data = WeightData.get(date(2020, 1, 1), client=authed_client)
|
||||
assert weight_data is None
|
||||
|
||||
|
||||
@pytest.mark.vcr
|
||||
def test_weight_data_list(authed_client: Client):
|
||||
end = date(2025, 6, 15)
|
||||
days = 15
|
||||
weight_data = WeightData.list(end, days, client=authed_client)
|
||||
|
||||
# Only 4 weight entries recorded at time of test
|
||||
assert len(weight_data) == 4
|
||||
assert all(isinstance(data, WeightData) for data in weight_data)
|
||||
assert all(
|
||||
weight_data[i].datetime_utc <= weight_data[i + 1].datetime_utc
|
||||
for i in range(len(weight_data) - 1)
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.vcr
|
||||
def test_weight_data_list_single_day(authed_client: Client):
|
||||
end = date(2025, 6, 14)
|
||||
weight_data = WeightData.list(end, client=authed_client)
|
||||
assert len(weight_data) == 2
|
||||
assert all(isinstance(data, WeightData) for data in weight_data)
|
||||
assert weight_data[0].source_type == "INDEX_SCALE"
|
||||
assert weight_data[1].source_type == "MANUAL"
|
||||
|
||||
|
||||
@pytest.mark.vcr
|
||||
def test_weight_data_list_empty(authed_client: Client):
|
||||
end = date(2020, 1, 1)
|
||||
days = 15
|
||||
weight_data = WeightData.list(end, days, client=authed_client)
|
||||
assert len(weight_data) == 0
|
||||
128
garth/tests/stats/cassettes/test_daily_hrv.yaml
Normal file
128
garth/tests/stats/cassettes/test_daily_hrv.yaml
Normal file
@@ -0,0 +1,128 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/hrv-service/hrv/daily/2023-07-01/2023-07-20
|
||||
response:
|
||||
body:
|
||||
string: '{"hrvSummaries": [{"calendarDate": "2023-07-01", "weeklyAvg": 43, "lastNightAvg":
|
||||
43, "lastNight5MinHigh": 60, "baseline": {"lowUpper": 35, "balancedLow": 38,
|
||||
"balancedUpper": 52, "markerValue": 0.42855835}, "status": "BALANCED", "feedbackPhrase":
|
||||
"HRV_BALANCED_8", "createTimeStamp": "2023-07-01T12:27:14.85"}, {"calendarDate":
|
||||
"2023-07-02", "weeklyAvg": 43, "lastNightAvg": 44, "lastNight5MinHigh": 63,
|
||||
"baseline": {"lowUpper": 35, "balancedLow": 38, "balancedUpper": 51, "markerValue":
|
||||
0.44230652}, "status": "BALANCED", "feedbackPhrase": "HRV_BALANCED_5", "createTimeStamp":
|
||||
"2023-07-02T11:54:17.128"}, {"calendarDate": "2023-07-03", "weeklyAvg": 43,
|
||||
"lastNightAvg": 48, "lastNight5MinHigh": 82, "baseline": {"lowUpper": 35,
|
||||
"balancedLow": 38, "balancedUpper": 52, "markerValue": 0.42855835}, "status":
|
||||
"BALANCED", "feedbackPhrase": "HRV_BALANCED_6", "createTimeStamp": "2023-07-03T12:41:20.280"},
|
||||
{"calendarDate": "2023-07-04", "weeklyAvg": 43, "lastNightAvg": 40, "lastNight5MinHigh":
|
||||
80, "baseline": {"lowUpper": 35, "balancedLow": 38, "balancedUpper": 52, "markerValue":
|
||||
0.42855835}, "status": "BALANCED", "feedbackPhrase": "HRV_BALANCED_7", "createTimeStamp":
|
||||
"2023-07-04T11:41:59.456"}, {"calendarDate": "2023-07-05", "weeklyAvg": 43,
|
||||
"lastNightAvg": 40, "lastNight5MinHigh": 67, "baseline": {"lowUpper": 35,
|
||||
"balancedLow": 38, "balancedUpper": 52, "markerValue": 0.42855835}, "status":
|
||||
"BALANCED", "feedbackPhrase": "HRV_BALANCED_8", "createTimeStamp": "2023-07-05T12:46:25.805"},
|
||||
{"calendarDate": "2023-07-06", "weeklyAvg": 43, "lastNightAvg": 46, "lastNight5MinHigh":
|
||||
58, "baseline": {"lowUpper": 35, "balancedLow": 38, "balancedUpper": 52, "markerValue":
|
||||
0.42855835}, "status": "BALANCED", "feedbackPhrase": "HRV_BALANCED_5", "createTimeStamp":
|
||||
"2023-07-06T17:20:46.196"}, {"calendarDate": "2023-07-07", "weeklyAvg": 44,
|
||||
"lastNightAvg": 44, "lastNight5MinHigh": 85, "baseline": {"lowUpper": 35,
|
||||
"balancedLow": 38, "balancedUpper": 52, "markerValue": 0.46427917}, "status":
|
||||
"BALANCED", "feedbackPhrase": "HRV_BALANCED_6", "createTimeStamp": "2023-07-07T12:15:26.744"},
|
||||
{"calendarDate": "2023-07-08", "weeklyAvg": 43, "lastNightAvg": 40, "lastNight5MinHigh":
|
||||
63, "baseline": {"lowUpper": 35, "balancedLow": 38, "balancedUpper": 52, "markerValue":
|
||||
0.42855835}, "status": "BALANCED", "feedbackPhrase": "HRV_BALANCED_7", "createTimeStamp":
|
||||
"2023-07-09T01:57:41.693"}, {"calendarDate": "2023-07-09", "weeklyAvg": 43,
|
||||
"lastNightAvg": 43, "lastNight5MinHigh": 66, "baseline": {"lowUpper": 35,
|
||||
"balancedLow": 38, "balancedUpper": 52, "markerValue": 0.42855835}, "status":
|
||||
"BALANCED", "feedbackPhrase": "HRV_BALANCED_8", "createTimeStamp": "2023-07-09T14:15:53.403"},
|
||||
{"calendarDate": "2023-07-10", "weeklyAvg": 42, "lastNightAvg": 41, "lastNight5MinHigh":
|
||||
62, "baseline": {"lowUpper": 36, "balancedLow": 39, "balancedUpper": 52, "markerValue":
|
||||
0.3653717}, "status": "BALANCED", "feedbackPhrase": "HRV_BALANCED_5", "createTimeStamp":
|
||||
"2023-07-10T12:43:37.356"}, {"calendarDate": "2023-07-11", "weeklyAvg": 43,
|
||||
"lastNightAvg": 46, "lastNight5MinHigh": 67, "baseline": {"lowUpper": 36,
|
||||
"balancedLow": 39, "balancedUpper": 52, "markerValue": 0.4038391}, "status":
|
||||
"BALANCED", "feedbackPhrase": "HRV_BALANCED_6", "createTimeStamp": "2023-07-11T12:42:55.467"},
|
||||
{"calendarDate": "2023-07-12", "weeklyAvg": 42, "lastNightAvg": 38, "lastNight5MinHigh":
|
||||
56, "baseline": {"lowUpper": 36, "balancedLow": 39, "balancedUpper": 52, "markerValue":
|
||||
0.3653717}, "status": "BALANCED", "feedbackPhrase": "HRV_BALANCED_3", "createTimeStamp":
|
||||
"2023-07-12T10:08:55.474"}, {"calendarDate": "2023-07-13", "weeklyAvg": 42,
|
||||
"lastNightAvg": 41, "lastNight5MinHigh": 66, "baseline": {"lowUpper": 36,
|
||||
"balancedLow": 39, "balancedUpper": 52, "markerValue": 0.3653717}, "status":
|
||||
"BALANCED", "feedbackPhrase": "HRV_BALANCED_8", "createTimeStamp": "2023-07-13T12:59:44.753"},
|
||||
{"calendarDate": "2023-07-14", "weeklyAvg": 41, "lastNightAvg": 37, "lastNight5MinHigh":
|
||||
57, "baseline": {"lowUpper": 36, "balancedLow": 39, "balancedUpper": 52, "markerValue":
|
||||
0.32691956}, "status": "BALANCED", "feedbackPhrase": "HRV_BALANCED_3", "createTimeStamp":
|
||||
"2023-07-14T12:16:07.618"}, {"calendarDate": "2023-07-15", "weeklyAvg": 40,
|
||||
"lastNightAvg": 37, "lastNight5MinHigh": 54, "baseline": {"lowUpper": 36,
|
||||
"balancedLow": 39, "balancedUpper": 52, "markerValue": 0.28845215}, "status":
|
||||
"BALANCED", "feedbackPhrase": "HRV_BALANCED_3", "createTimeStamp": "2023-07-15T18:18:32.522"},
|
||||
{"calendarDate": "2023-07-16", "weeklyAvg": 39, "lastNightAvg": 37, "lastNight5MinHigh":
|
||||
64, "baseline": {"lowUpper": 36, "balancedLow": 39, "balancedUpper": 52, "markerValue":
|
||||
0.25}, "status": "BALANCED", "feedbackPhrase": "HRV_BALANCED_3", "createTimeStamp":
|
||||
"2023-07-17T03:13:54.585"}, {"calendarDate": "2023-07-17", "weeklyAvg": 39,
|
||||
"lastNightAvg": 41, "lastNight5MinHigh": 65, "baseline": {"lowUpper": 36,
|
||||
"balancedLow": 39, "balancedUpper": 52, "markerValue": 0.25}, "status": "BALANCED",
|
||||
"feedbackPhrase": "HRV_BALANCED_8", "createTimeStamp": "2023-07-17T11:58:26.731"},
|
||||
{"calendarDate": "2023-07-18", "weeklyAvg": 39, "lastNightAvg": 40, "lastNight5MinHigh":
|
||||
72, "baseline": {"lowUpper": 36, "balancedLow": 40, "balancedUpper": 52, "markerValue":
|
||||
0.22801208}, "status": "UNBALANCED", "feedbackPhrase": "HRV_UNBALANCED_12",
|
||||
"createTimeStamp": "2023-07-18T13:45:54.638"}, {"calendarDate": "2023-07-19",
|
||||
"weeklyAvg": 39, "lastNightAvg": 43, "lastNight5MinHigh": 60, "baseline":
|
||||
{"lowUpper": 36, "balancedLow": 39, "balancedUpper": 52, "markerValue": 0.25},
|
||||
"status": "BALANCED", "feedbackPhrase": "HRV_BALANCED_4", "createTimeStamp":
|
||||
"2023-07-19T14:43:16.394"}, {"calendarDate": "2023-07-20", "weeklyAvg": 39,
|
||||
"lastNightAvg": 42, "lastNight5MinHigh": 66, "baseline": {"lowUpper": 36,
|
||||
"balancedLow": 39, "balancedUpper": 52, "markerValue": 0.25}, "status": "BALANCED",
|
||||
"feedbackPhrase": "HRV_BALANCED_7", "createTimeStamp": "2023-07-20T12:14:11.898"}],
|
||||
"userProfilePk": 2591602}'
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 7f12cffb1bf04740-DFW
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Fri, 04 Aug 2023 00:51:31 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=FwXXbkT4TDPL0xwdyj0pemCK5IZtMhJ0cEJStvFh9rhUuwEgA9nPQyW5%2F78guNWU4c0CMB5arBD5aYFCFwrJ0S9cXV%2BHnxbblaMvHkHhr3XZNcHySwLfohsTZBtkWiUT4iZVWVGDgg%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cache-control:
|
||||
- no-cache, no-store, private
|
||||
pragma:
|
||||
- no-cache
|
||||
set-cookie:
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
54
garth/tests/stats/cassettes/test_daily_hrv_no_results.yaml
Normal file
54
garth/tests/stats/cassettes/test_daily_hrv_no_results.yaml
Normal file
@@ -0,0 +1,54 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/hrv-service/hrv/daily/1990-06-23/1990-07-20
|
||||
response:
|
||||
body:
|
||||
string: ''
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 7f12cfff895a46e6-DFW
|
||||
Connection:
|
||||
- keep-alive
|
||||
Date:
|
||||
- Fri, 04 Aug 2023 00:51:32 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=c5M1eFSWXRssBQjJffsEsd2fyEVImSUgt64bWpiVisHYL7YajNn2yfJIhcd6yueOyfUrDXvCDZJiZ9%2BVQfAAHMEaRxu%2B8ZZj7iPBiNff%2Fl9O9KJ6SIX1qHMttNFKtEcxoc5Vl0E5Gw%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cache-control:
|
||||
- no-cache, no-store, private
|
||||
pragma:
|
||||
- no-cache
|
||||
set-cookie:
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
status:
|
||||
code: 204
|
||||
message: No Content
|
||||
version: 1
|
||||
256
garth/tests/stats/cassettes/test_daily_hrv_paginate.yaml
Normal file
256
garth/tests/stats/cassettes/test_daily_hrv_paginate.yaml
Normal file
@@ -0,0 +1,256 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/hrv-service/hrv/daily/2023-06-23/2023-07-20
|
||||
response:
|
||||
body:
|
||||
string: '{"hrvSummaries": [{"calendarDate": "2023-06-23", "weeklyAvg": 40, "lastNightAvg":
|
||||
42, "lastNight5MinHigh": 54, "baseline": {"lowUpper": 35, "balancedLow": 38,
|
||||
"balancedUpper": 52, "markerValue": 0.3214264}, "status": "BALANCED", "feedbackPhrase":
|
||||
"HRV_BALANCED_8", "createTimeStamp": "2023-06-25T04:55:32.475"}, {"calendarDate":
|
||||
"2023-06-24", "weeklyAvg": 41, "lastNightAvg": 43, "lastNight5MinHigh": 69,
|
||||
"baseline": {"lowUpper": 35, "balancedLow": 38, "balancedUpper": 52, "markerValue":
|
||||
0.35713196}, "status": "BALANCED", "feedbackPhrase": "HRV_BALANCED_5", "createTimeStamp":
|
||||
"2023-06-25T04:56:02.128"}, {"calendarDate": "2023-06-25", "weeklyAvg": 41,
|
||||
"lastNightAvg": 41, "lastNight5MinHigh": 63, "baseline": {"lowUpper": 35,
|
||||
"balancedLow": 38, "balancedUpper": 51, "markerValue": 0.3653717}, "status":
|
||||
"BALANCED", "feedbackPhrase": "HRV_BALANCED_6", "createTimeStamp": "2023-06-25T12:30:49.823"},
|
||||
{"calendarDate": "2023-06-26", "weeklyAvg": 42, "lastNightAvg": 50, "lastNight5MinHigh":
|
||||
113, "baseline": {"lowUpper": 35, "balancedLow": 38, "balancedUpper": 52,
|
||||
"markerValue": 0.39285278}, "status": "BALANCED", "feedbackPhrase": "HRV_BALANCED_7",
|
||||
"createTimeStamp": "2023-06-26T15:16:53.873"}, {"calendarDate": "2023-06-27",
|
||||
"weeklyAvg": 41, "lastNightAvg": 39, "lastNight5MinHigh": 63, "baseline":
|
||||
{"lowUpper": 35, "balancedLow": 38, "balancedUpper": 52, "markerValue": 0.35713196},
|
||||
"status": "BALANCED", "feedbackPhrase": "HRV_BALANCED_8", "createTimeStamp":
|
||||
"2023-06-27T12:38:37.557"}, {"calendarDate": "2023-06-28", "weeklyAvg": 42,
|
||||
"lastNightAvg": 39, "lastNight5MinHigh": 63, "baseline": {"lowUpper": 35,
|
||||
"balancedLow": 38, "balancedUpper": 52, "markerValue": 0.39285278}, "status":
|
||||
"BALANCED", "feedbackPhrase": "HRV_BALANCED_5", "createTimeStamp": "2023-06-28T12:44:11.280"},
|
||||
{"calendarDate": "2023-06-29", "weeklyAvg": 43, "lastNightAvg": 47, "lastNight5MinHigh":
|
||||
78, "baseline": {"lowUpper": 35, "balancedLow": 38, "balancedUpper": 52, "markerValue":
|
||||
0.42855835}, "status": "BALANCED", "feedbackPhrase": "HRV_BALANCED_6", "createTimeStamp":
|
||||
"2023-06-29T13:30:15.112"}, {"calendarDate": "2023-06-30", "weeklyAvg": 43,
|
||||
"lastNightAvg": 40, "lastNight5MinHigh": 65, "baseline": {"lowUpper": 35,
|
||||
"balancedLow": 38, "balancedUpper": 52, "markerValue": 0.42855835}, "status":
|
||||
"BALANCED", "feedbackPhrase": "HRV_BALANCED_7", "createTimeStamp": "2023-06-30T14:19:24.203"},
|
||||
{"calendarDate": "2023-07-01", "weeklyAvg": 43, "lastNightAvg": 43, "lastNight5MinHigh":
|
||||
60, "baseline": {"lowUpper": 35, "balancedLow": 38, "balancedUpper": 52, "markerValue":
|
||||
0.42855835}, "status": "BALANCED", "feedbackPhrase": "HRV_BALANCED_8", "createTimeStamp":
|
||||
"2023-07-01T12:27:14.85"}, {"calendarDate": "2023-07-02", "weeklyAvg": 43,
|
||||
"lastNightAvg": 44, "lastNight5MinHigh": 63, "baseline": {"lowUpper": 35,
|
||||
"balancedLow": 38, "balancedUpper": 51, "markerValue": 0.44230652}, "status":
|
||||
"BALANCED", "feedbackPhrase": "HRV_BALANCED_5", "createTimeStamp": "2023-07-02T11:54:17.128"},
|
||||
{"calendarDate": "2023-07-03", "weeklyAvg": 43, "lastNightAvg": 48, "lastNight5MinHigh":
|
||||
82, "baseline": {"lowUpper": 35, "balancedLow": 38, "balancedUpper": 52, "markerValue":
|
||||
0.42855835}, "status": "BALANCED", "feedbackPhrase": "HRV_BALANCED_6", "createTimeStamp":
|
||||
"2023-07-03T12:41:20.280"}, {"calendarDate": "2023-07-04", "weeklyAvg": 43,
|
||||
"lastNightAvg": 40, "lastNight5MinHigh": 80, "baseline": {"lowUpper": 35,
|
||||
"balancedLow": 38, "balancedUpper": 52, "markerValue": 0.42855835}, "status":
|
||||
"BALANCED", "feedbackPhrase": "HRV_BALANCED_7", "createTimeStamp": "2023-07-04T11:41:59.456"},
|
||||
{"calendarDate": "2023-07-05", "weeklyAvg": 43, "lastNightAvg": 40, "lastNight5MinHigh":
|
||||
67, "baseline": {"lowUpper": 35, "balancedLow": 38, "balancedUpper": 52, "markerValue":
|
||||
0.42855835}, "status": "BALANCED", "feedbackPhrase": "HRV_BALANCED_8", "createTimeStamp":
|
||||
"2023-07-05T12:46:25.805"}, {"calendarDate": "2023-07-06", "weeklyAvg": 43,
|
||||
"lastNightAvg": 46, "lastNight5MinHigh": 58, "baseline": {"lowUpper": 35,
|
||||
"balancedLow": 38, "balancedUpper": 52, "markerValue": 0.42855835}, "status":
|
||||
"BALANCED", "feedbackPhrase": "HRV_BALANCED_5", "createTimeStamp": "2023-07-06T17:20:46.196"},
|
||||
{"calendarDate": "2023-07-07", "weeklyAvg": 44, "lastNightAvg": 44, "lastNight5MinHigh":
|
||||
85, "baseline": {"lowUpper": 35, "balancedLow": 38, "balancedUpper": 52, "markerValue":
|
||||
0.46427917}, "status": "BALANCED", "feedbackPhrase": "HRV_BALANCED_6", "createTimeStamp":
|
||||
"2023-07-07T12:15:26.744"}, {"calendarDate": "2023-07-08", "weeklyAvg": 43,
|
||||
"lastNightAvg": 40, "lastNight5MinHigh": 63, "baseline": {"lowUpper": 35,
|
||||
"balancedLow": 38, "balancedUpper": 52, "markerValue": 0.42855835}, "status":
|
||||
"BALANCED", "feedbackPhrase": "HRV_BALANCED_7", "createTimeStamp": "2023-07-09T01:57:41.693"},
|
||||
{"calendarDate": "2023-07-09", "weeklyAvg": 43, "lastNightAvg": 43, "lastNight5MinHigh":
|
||||
66, "baseline": {"lowUpper": 35, "balancedLow": 38, "balancedUpper": 52, "markerValue":
|
||||
0.42855835}, "status": "BALANCED", "feedbackPhrase": "HRV_BALANCED_8", "createTimeStamp":
|
||||
"2023-07-09T14:15:53.403"}, {"calendarDate": "2023-07-10", "weeklyAvg": 42,
|
||||
"lastNightAvg": 41, "lastNight5MinHigh": 62, "baseline": {"lowUpper": 36,
|
||||
"balancedLow": 39, "balancedUpper": 52, "markerValue": 0.3653717}, "status":
|
||||
"BALANCED", "feedbackPhrase": "HRV_BALANCED_5", "createTimeStamp": "2023-07-10T12:43:37.356"},
|
||||
{"calendarDate": "2023-07-11", "weeklyAvg": 43, "lastNightAvg": 46, "lastNight5MinHigh":
|
||||
67, "baseline": {"lowUpper": 36, "balancedLow": 39, "balancedUpper": 52, "markerValue":
|
||||
0.4038391}, "status": "BALANCED", "feedbackPhrase": "HRV_BALANCED_6", "createTimeStamp":
|
||||
"2023-07-11T12:42:55.467"}, {"calendarDate": "2023-07-12", "weeklyAvg": 42,
|
||||
"lastNightAvg": 38, "lastNight5MinHigh": 56, "baseline": {"lowUpper": 36,
|
||||
"balancedLow": 39, "balancedUpper": 52, "markerValue": 0.3653717}, "status":
|
||||
"BALANCED", "feedbackPhrase": "HRV_BALANCED_3", "createTimeStamp": "2023-07-12T10:08:55.474"},
|
||||
{"calendarDate": "2023-07-13", "weeklyAvg": 42, "lastNightAvg": 41, "lastNight5MinHigh":
|
||||
66, "baseline": {"lowUpper": 36, "balancedLow": 39, "balancedUpper": 52, "markerValue":
|
||||
0.3653717}, "status": "BALANCED", "feedbackPhrase": "HRV_BALANCED_8", "createTimeStamp":
|
||||
"2023-07-13T12:59:44.753"}, {"calendarDate": "2023-07-14", "weeklyAvg": 41,
|
||||
"lastNightAvg": 37, "lastNight5MinHigh": 57, "baseline": {"lowUpper": 36,
|
||||
"balancedLow": 39, "balancedUpper": 52, "markerValue": 0.32691956}, "status":
|
||||
"BALANCED", "feedbackPhrase": "HRV_BALANCED_3", "createTimeStamp": "2023-07-14T12:16:07.618"},
|
||||
{"calendarDate": "2023-07-15", "weeklyAvg": 40, "lastNightAvg": 37, "lastNight5MinHigh":
|
||||
54, "baseline": {"lowUpper": 36, "balancedLow": 39, "balancedUpper": 52, "markerValue":
|
||||
0.28845215}, "status": "BALANCED", "feedbackPhrase": "HRV_BALANCED_3", "createTimeStamp":
|
||||
"2023-07-15T18:18:32.522"}, {"calendarDate": "2023-07-16", "weeklyAvg": 39,
|
||||
"lastNightAvg": 37, "lastNight5MinHigh": 64, "baseline": {"lowUpper": 36,
|
||||
"balancedLow": 39, "balancedUpper": 52, "markerValue": 0.25}, "status": "BALANCED",
|
||||
"feedbackPhrase": "HRV_BALANCED_3", "createTimeStamp": "2023-07-17T03:13:54.585"},
|
||||
{"calendarDate": "2023-07-17", "weeklyAvg": 39, "lastNightAvg": 41, "lastNight5MinHigh":
|
||||
65, "baseline": {"lowUpper": 36, "balancedLow": 39, "balancedUpper": 52, "markerValue":
|
||||
0.25}, "status": "BALANCED", "feedbackPhrase": "HRV_BALANCED_8", "createTimeStamp":
|
||||
"2023-07-17T11:58:26.731"}, {"calendarDate": "2023-07-18", "weeklyAvg": 39,
|
||||
"lastNightAvg": 40, "lastNight5MinHigh": 72, "baseline": {"lowUpper": 36,
|
||||
"balancedLow": 40, "balancedUpper": 52, "markerValue": 0.22801208}, "status":
|
||||
"UNBALANCED", "feedbackPhrase": "HRV_UNBALANCED_12", "createTimeStamp": "2023-07-18T13:45:54.638"},
|
||||
{"calendarDate": "2023-07-19", "weeklyAvg": 39, "lastNightAvg": 43, "lastNight5MinHigh":
|
||||
60, "baseline": {"lowUpper": 36, "balancedLow": 39, "balancedUpper": 52, "markerValue":
|
||||
0.25}, "status": "BALANCED", "feedbackPhrase": "HRV_BALANCED_4", "createTimeStamp":
|
||||
"2023-07-19T14:43:16.394"}, {"calendarDate": "2023-07-20", "weeklyAvg": 39,
|
||||
"lastNightAvg": 42, "lastNight5MinHigh": 66, "baseline": {"lowUpper": 36,
|
||||
"balancedLow": 39, "balancedUpper": 52, "markerValue": 0.25}, "status": "BALANCED",
|
||||
"feedbackPhrase": "HRV_BALANCED_7", "createTimeStamp": "2023-07-20T12:14:11.898"}],
|
||||
"userProfilePk": 2591602}'
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 7f12cffc4ea3b6e1-QRO
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Fri, 04 Aug 2023 00:51:31 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=2qwUnXfm51mWjJY2ho0q0lhgcjKqNZIHMND%2F3pcFJs8BsJFIXzgvLZIVnVro%2Fl1%2BmXxg4txzEdRTUKUbedJs19kiYjjaUqbGOdl%2FA7w3NYWk3hlzD5bzjzJzovpCZi3rG9ckt3TzyQ%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cache-control:
|
||||
- no-cache, no-store, private
|
||||
pragma:
|
||||
- no-cache
|
||||
set-cookie:
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
Cookie:
|
||||
- _cfuvid=SANITIZED; ADRUM_BT1=SANITIZED; ADRUM_BTa=SANITIZED; SameSite=SANITIZED
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/hrv-service/hrv/daily/2023-06-11/2023-06-22
|
||||
response:
|
||||
body:
|
||||
string: '{"hrvSummaries": [{"calendarDate": "2023-06-11", "weeklyAvg": 43, "lastNightAvg":
|
||||
71, "lastNight5MinHigh": 115, "baseline": {"lowUpper": 35, "balancedLow":
|
||||
38, "balancedUpper": 51, "markerValue": 0.44230652}, "status": "BALANCED",
|
||||
"feedbackPhrase": "HRV_BALANCED_8", "createTimeStamp": "2023-06-11T14:17:59.319"},
|
||||
{"calendarDate": "2023-06-12", "weeklyAvg": 45, "lastNightAvg": 51, "lastNight5MinHigh":
|
||||
89, "baseline": {"lowUpper": 35, "balancedLow": 38, "balancedUpper": 51, "markerValue":
|
||||
0.5192261}, "status": "BALANCED", "feedbackPhrase": "HRV_BALANCED_5", "createTimeStamp":
|
||||
"2023-06-12T12:50:20.861"}, {"calendarDate": "2023-06-13", "weeklyAvg": 45,
|
||||
"lastNightAvg": 44, "lastNight5MinHigh": 68, "baseline": {"lowUpper": 35,
|
||||
"balancedLow": 38, "balancedUpper": 51, "markerValue": 0.5192261}, "status":
|
||||
"BALANCED", "feedbackPhrase": "HRV_BALANCED_6", "createTimeStamp": "2023-06-13T13:55:03.624"},
|
||||
{"calendarDate": "2023-06-14", "weeklyAvg": 47, "lastNightAvg": 49, "lastNight5MinHigh":
|
||||
83, "baseline": {"lowUpper": 35, "balancedLow": 38, "balancedUpper": 51, "markerValue":
|
||||
0.59614563}, "status": "BALANCED", "feedbackPhrase": "HRV_BALANCED_7", "createTimeStamp":
|
||||
"2023-06-14T13:36:05.184"}, {"calendarDate": "2023-06-15", "weeklyAvg": 46,
|
||||
"lastNightAvg": 39, "lastNight5MinHigh": 77, "baseline": {"lowUpper": 35,
|
||||
"balancedLow": 38, "balancedUpper": 51, "markerValue": 0.5576782}, "status":
|
||||
"BALANCED", "feedbackPhrase": "HRV_BALANCED_8", "createTimeStamp": "2023-06-15T11:50:18.949"},
|
||||
{"calendarDate": "2023-06-16", "weeklyAvg": 45, "lastNightAvg": 34, "lastNight5MinHigh":
|
||||
54, "baseline": {"lowUpper": 35, "balancedLow": 38, "balancedUpper": 51, "markerValue":
|
||||
0.5192261}, "status": "BALANCED", "feedbackPhrase": "HRV_BALANCED_3", "createTimeStamp":
|
||||
"2023-06-16T12:40:24.953"}, {"calendarDate": "2023-06-17", "weeklyAvg": 46,
|
||||
"lastNightAvg": 42, "lastNight5MinHigh": 97, "baseline": {"lowUpper": 35,
|
||||
"balancedLow": 38, "balancedUpper": 51, "markerValue": 0.5576782}, "status":
|
||||
"BALANCED", "feedbackPhrase": "HRV_BALANCED_6", "createTimeStamp": "2023-06-17T14:05:55.936"},
|
||||
{"calendarDate": "2023-06-18", "weeklyAvg": 42, "lastNightAvg": 39, "lastNight5MinHigh":
|
||||
71, "baseline": {"lowUpper": 35, "balancedLow": 38, "balancedUpper": 52, "markerValue":
|
||||
0.39285278}, "status": "BALANCED", "feedbackPhrase": "HRV_BALANCED_7", "createTimeStamp":
|
||||
"2023-06-18T13:32:40.883"}, {"calendarDate": "2023-06-19", "weeklyAvg": 41,
|
||||
"lastNightAvg": 40, "lastNight5MinHigh": 66, "baseline": {"lowUpper": 35,
|
||||
"balancedLow": 38, "balancedUpper": 52, "markerValue": 0.35713196}, "status":
|
||||
"BALANCED", "feedbackPhrase": "HRV_BALANCED_8", "createTimeStamp": "2023-06-19T14:15:45.918"},
|
||||
{"calendarDate": "2023-06-20", "weeklyAvg": 41, "lastNightAvg": 45, "lastNight5MinHigh":
|
||||
76, "baseline": {"lowUpper": 35, "balancedLow": 38, "balancedUpper": 52, "markerValue":
|
||||
0.35713196}, "status": "BALANCED", "feedbackPhrase": "HRV_BALANCED_5", "createTimeStamp":
|
||||
"2023-06-20T12:57:59.375"}, {"calendarDate": "2023-06-21", "weeklyAvg": 39,
|
||||
"lastNightAvg": 36, "lastNight5MinHigh": 55, "baseline": {"lowUpper": 35,
|
||||
"balancedLow": 38, "balancedUpper": 52, "markerValue": 0.28570557}, "status":
|
||||
"BALANCED", "feedbackPhrase": "HRV_BALANCED_3", "createTimeStamp": "2023-06-21T12:07:13.299"},
|
||||
{"calendarDate": "2023-06-22", "weeklyAvg": 39, "lastNightAvg": 40, "lastNight5MinHigh":
|
||||
66, "baseline": {"lowUpper": 35, "balancedLow": 38, "balancedUpper": 52, "markerValue":
|
||||
0.28570557}, "status": "BALANCED", "feedbackPhrase": "HRV_BALANCED_7", "createTimeStamp":
|
||||
"2023-06-22T15:07:52.527"}], "userProfilePk": 2591602}'
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 7f12cffd58bdb6e1-QRO
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Fri, 04 Aug 2023 00:51:32 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=0NCR775bJt1JRqheSkoCuFTz1b0iStwJxkS5KYP46xKe8WcWO56WI81lN9h62gaiKEiVtpV1Mrlkr4oRqMEm8XwiPMrJ8kF6OFxD8%2F6t%2B7DK0QOI6An3R2EzrBEfW40UaG6qbLitvg%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cache-control:
|
||||
- no-cache, no-store, private
|
||||
pragma:
|
||||
- no-cache
|
||||
set-cookie:
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
@@ -0,0 +1,54 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/hrv-service/hrv/daily/1990-06-23/1990-07-20
|
||||
response:
|
||||
body:
|
||||
string: ''
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 7f12d0009fb71556-QRO
|
||||
Connection:
|
||||
- keep-alive
|
||||
Date:
|
||||
- Fri, 04 Aug 2023 00:51:32 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=Rtks48zaXzNg0K9c8L5w7UaDVLx4fUwxanI2aMQtUuzIq7%2FEg%2F3tYRlfBLxbkZgQptFoiz3CS%2B8rKHWzqq7ayFvLe0k2kEynQKwSjY5I%2FDlesB7kjUKuXxwh%2B3qW7JMMoW6BK9ybRQ%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cache-control:
|
||||
- no-cache, no-store, private
|
||||
pragma:
|
||||
- no-cache
|
||||
set-cookie:
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
status:
|
||||
code: 204
|
||||
message: No Content
|
||||
version: 1
|
||||
50
garth/tests/stats/cassettes/test_daily_hydration.yaml
Normal file
50
garth/tests/stats/cassettes/test_daily_hydration.yaml
Normal file
@@ -0,0 +1,50 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- GCM-iOS-5.7.2.1
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/usersummary-service/stats/hydration/daily/2024-06-29/2024-06-29
|
||||
response:
|
||||
body:
|
||||
string: '[{"calendarDate": "2024-06-29", "valueInML": 1750.0, "goalInML": 2800.0}]'
|
||||
headers:
|
||||
CF-RAY:
|
||||
- 94b18a3ecfb934a7-QRO
|
||||
Cache-Control:
|
||||
- no-cache, no-store, private
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Thu, 05 Jun 2025 17:55:17 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v4?s=Fj%2FXgrWJs%2FS1mgUePurhEKyb3GueFKdLEDdkAVqNKT%2ByACTJn5%2Fi%2B2%2FsDOOh95cibWQYcDIAGM40K0XxrSBItFfY5ZW24CwXjHLIg%2FkvAB1JJwMiyAdhLIpXjUfVdyXoQ4tdKQcy0Q%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Set-Cookie:
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cf-cache-status:
|
||||
- DYNAMIC
|
||||
pragma:
|
||||
- no-cache
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
@@ -0,0 +1,82 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/usersummary-service/stats/im/daily/2023-07-01/2023-07-20
|
||||
response:
|
||||
body:
|
||||
string: '[{"calendarDate": "2023-07-01", "weeklyGoal": 150, "moderateValue":
|
||||
0, "vigorousValue": 0}, {"calendarDate": "2023-07-02", "weeklyGoal": 150,
|
||||
"moderateValue": 55, "vigorousValue": 6}, {"calendarDate": "2023-07-03", "weeklyGoal":
|
||||
150, "moderateValue": 13, "vigorousValue": 0}, {"calendarDate": "2023-07-04",
|
||||
"weeklyGoal": 150, "moderateValue": 9, "vigorousValue": 34}, {"calendarDate":
|
||||
"2023-07-05", "weeklyGoal": 150, "moderateValue": 23, "vigorousValue": 1},
|
||||
{"calendarDate": "2023-07-06", "weeklyGoal": 150, "moderateValue": 0, "vigorousValue":
|
||||
0}, {"calendarDate": "2023-07-07", "weeklyGoal": 150, "moderateValue": 88,
|
||||
"vigorousValue": 8}, {"calendarDate": "2023-07-08", "weeklyGoal": 150, "moderateValue":
|
||||
0, "vigorousValue": 0}, {"calendarDate": "2023-07-09", "weeklyGoal": 150,
|
||||
"moderateValue": 44, "vigorousValue": 5}, {"calendarDate": "2023-07-10", "weeklyGoal":
|
||||
150, "moderateValue": 9, "vigorousValue": 0}, {"calendarDate": "2023-07-11",
|
||||
"weeklyGoal": 150, "moderateValue": 31, "vigorousValue": 23}, {"calendarDate":
|
||||
"2023-07-12", "weeklyGoal": 150, "moderateValue": 43, "vigorousValue": 3},
|
||||
{"calendarDate": "2023-07-13", "weeklyGoal": 150, "moderateValue": 53, "vigorousValue":
|
||||
25}, {"calendarDate": "2023-07-14", "weeklyGoal": 150, "moderateValue": 49,
|
||||
"vigorousValue": 7}, {"calendarDate": "2023-07-15", "weeklyGoal": 150, "moderateValue":
|
||||
0, "vigorousValue": 0}, {"calendarDate": "2023-07-16", "weeklyGoal": 150,
|
||||
"moderateValue": 0, "vigorousValue": 0}, {"calendarDate": "2023-07-17", "weeklyGoal":
|
||||
150, "moderateValue": 61, "vigorousValue": 5}, {"calendarDate": "2023-07-18",
|
||||
"weeklyGoal": 150, "moderateValue": 0, "vigorousValue": 0}, {"calendarDate":
|
||||
"2023-07-19", "weeklyGoal": 150, "moderateValue": 1, "vigorousValue": 0},
|
||||
{"calendarDate": "2023-07-20", "weeklyGoal": 150, "moderateValue": 0, "vigorousValue":
|
||||
0}]'
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 7f12d0031b5247fd-DFW
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Fri, 04 Aug 2023 00:51:33 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=9im50IutY9DBaURoxea2zvLVuniHkDcSgeUppPhyVdmW%2FDSA3THWATEIui7XNQQxyQo08JOtLQ5MRRM1%2F6faiOqzehpRUM3EJ3eDtkXNQwLBUlFZ%2B3ldpbg%2FNfHDN%2BXb3rjJU1YjJg%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cache-control:
|
||||
- no-cache, no-store, private
|
||||
pragma:
|
||||
- no-cache
|
||||
set-cookie:
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
65
garth/tests/stats/cassettes/test_daily_sleep.yaml
Normal file
65
garth/tests/stats/cassettes/test_daily_sleep.yaml
Normal file
@@ -0,0 +1,65 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/wellness-service/stats/daily/sleep/score/2023-07-01/2023-07-20
|
||||
response:
|
||||
body:
|
||||
string: '[{"calendarDate": "2023-07-01", "value": 60}, {"calendarDate": "2023-07-02",
|
||||
"value": 70}, {"calendarDate": "2023-07-03", "value": 82}, {"calendarDate":
|
||||
"2023-07-04", "value": 75}, {"calendarDate": "2023-07-05", "value": 70}, {"calendarDate":
|
||||
"2023-07-06", "value": 28}, {"calendarDate": "2023-07-07", "value": 79}, {"calendarDate":
|
||||
"2023-07-08", "value": 70}, {"calendarDate": "2023-07-09", "value": 77}, {"calendarDate":
|
||||
"2023-07-10", "value": 87}, {"calendarDate": "2023-07-11", "value": 64}, {"calendarDate":
|
||||
"2023-07-12", "value": 58}, {"calendarDate": "2023-07-13", "value": 71}, {"calendarDate":
|
||||
"2023-07-14", "value": 80}, {"calendarDate": "2023-07-15", "value": 69}, {"calendarDate":
|
||||
"2023-07-16", "value": 71}, {"calendarDate": "2023-07-17", "value": 94}, {"calendarDate":
|
||||
"2023-07-18", "value": 77}, {"calendarDate": "2023-07-19", "value": 80}, {"calendarDate":
|
||||
"2023-07-20", "value": 68}]'
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 7f12d00638244600-DFW
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Fri, 04 Aug 2023 00:51:33 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=RyGEQnxnunkvjbgNO5BmWlIHgK45Gi5ICNxJrWUnFSVIYAOexuuvkkAPYzGQCZKluGJewZGrYLSriEsXOrcOcQ3heU9KlvGhE7UD2gY1xn7AECCOV5wWcGb5WLwgJ8%2FDzFs%2FdL3hGg%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cache-control:
|
||||
- no-cache, no-store, private
|
||||
pragma:
|
||||
- no-cache
|
||||
set-cookie:
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
84
garth/tests/stats/cassettes/test_daily_steps.yaml
Normal file
84
garth/tests/stats/cassettes/test_daily_steps.yaml
Normal file
@@ -0,0 +1,84 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/usersummary-service/stats/steps/daily/2023-07-01/2023-07-20
|
||||
response:
|
||||
body:
|
||||
string: '[{"calendarDate": "2023-07-01", "totalSteps": 12413, "totalDistance":
|
||||
10368, "stepGoal": 7950}, {"calendarDate": "2023-07-02", "totalSteps": 5719,
|
||||
"totalDistance": 5207, "stepGoal": 8400}, {"calendarDate": "2023-07-03", "totalSteps":
|
||||
3633, "totalDistance": 3152, "stepGoal": 8140}, {"calendarDate": "2023-07-04",
|
||||
"totalSteps": 9593, "totalDistance": 8745, "stepGoal": 8140}, {"calendarDate":
|
||||
"2023-07-05", "totalSteps": 5865, "totalDistance": 5344, "stepGoal": 8290},
|
||||
{"calendarDate": "2023-07-06", "totalSteps": 4331, "totalDistance": 3740,
|
||||
"stepGoal": 8050}, {"calendarDate": "2023-07-07", "totalSteps": 9263, "totalDistance":
|
||||
7597, "stepGoal": 7310}, {"calendarDate": "2023-07-08", "totalSteps": 6889,
|
||||
"totalDistance": 5800, "stepGoal": 7510}, {"calendarDate": "2023-07-09", "totalSteps":
|
||||
15403, "totalDistance": 11643, "stepGoal": 7450}, {"calendarDate": "2023-07-10",
|
||||
"totalSteps": 3409, "totalDistance": 2935, "stepGoal": 8250}, {"calendarDate":
|
||||
"2023-07-11", "totalSteps": 10746, "totalDistance": 9609, "stepGoal": 8250},
|
||||
{"calendarDate": "2023-07-12", "totalSteps": 9849, "totalDistance": 8508,
|
||||
"stepGoal": 8750}, {"calendarDate": "2023-07-13", "totalSteps": 17556, "totalDistance":
|
||||
31572, "stepGoal": 8970}, {"calendarDate": "2023-07-14", "totalSteps": 6919,
|
||||
"totalDistance": 5527, "stepGoal": 10690}, {"calendarDate": "2023-07-15",
|
||||
"totalSteps": 10886, "totalDistance": 9048, "stepGoal": 10320}, {"calendarDate":
|
||||
"2023-07-16", "totalSteps": 3965, "totalDistance": 3442, "stepGoal": 10380},
|
||||
{"calendarDate": "2023-07-17", "totalSteps": 6842, "totalDistance": 5439,
|
||||
"stepGoal": 9740}, {"calendarDate": "2023-07-18", "totalSteps": 3585, "totalDistance":
|
||||
3103, "stepGoal": 9160}, {"calendarDate": "2023-07-19", "totalSteps": 3998,
|
||||
"totalDistance": 3384, "stepGoal": 8050}, {"calendarDate": "2023-07-20", "totalSteps":
|
||||
7322, "totalDistance": 6148, "stepGoal": 7240}]'
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 7f12d0361f51b6ee-QRO
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Fri, 04 Aug 2023 00:51:41 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=bzh%2FUoGEfIajBdQR5MV3QW0RLYZzm5KVdFbwvpMqO9thm%2FwjZKTcc%2FouaGofuuNQmFoGl%2FTksvg%2BtFeTKq3Nt78N%2BaIJVDV3FIetTmEZlwD083NaVOvpEMEwaB80srEQbZuua1khiw%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cache-control:
|
||||
- no-cache, no-store, private
|
||||
pragma:
|
||||
- no-cache
|
||||
set-cookie:
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
107
garth/tests/stats/cassettes/test_daily_stress.yaml
Normal file
107
garth/tests/stats/cassettes/test_daily_stress.yaml
Normal file
@@ -0,0 +1,107 @@
|
||||
interactions:
|
||||
- request:
|
||||
body: null
|
||||
headers:
|
||||
Accept:
|
||||
- '*/*'
|
||||
Accept-Encoding:
|
||||
- gzip, deflate
|
||||
Authorization:
|
||||
- Bearer SANITIZED
|
||||
Connection:
|
||||
- keep-alive
|
||||
User-Agent:
|
||||
- Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15
|
||||
(KHTML, like Gecko) Mobile/15E148
|
||||
method: GET
|
||||
uri: https://connectapi.garmin.com/usersummary-service/stats/stress/daily/2023-07-01/2023-07-20
|
||||
response:
|
||||
body:
|
||||
string: '[{"calendarDate": "2023-07-01", "values": {"highStressDuration": 1680,
|
||||
"lowStressDuration": 21780, "overallStressLevel": 35, "restStressDuration":
|
||||
27780, "mediumStressDuration": 12660}}, {"calendarDate": "2023-07-02", "values":
|
||||
{"highStressDuration": 3600, "lowStressDuration": 11580, "overallStressLevel":
|
||||
29, "restStressDuration": 39840, "mediumStressDuration": 7740}}, {"calendarDate":
|
||||
"2023-07-03", "values": {"highStressDuration": 3060, "lowStressDuration":
|
||||
16680, "overallStressLevel": 32, "restStressDuration": 34080, "mediumStressDuration":
|
||||
11460}}, {"calendarDate": "2023-07-04", "values": {"highStressDuration": 2640,
|
||||
"lowStressDuration": 21420, "overallStressLevel": 34, "restStressDuration":
|
||||
27600, "mediumStressDuration": 10860}}, {"calendarDate": "2023-07-05", "values":
|
||||
{"highStressDuration": 2940, "lowStressDuration": 20040, "overallStressLevel":
|
||||
37, "restStressDuration": 25200, "mediumStressDuration": 15120}}, {"calendarDate":
|
||||
"2023-07-06", "values": {"highStressDuration": 6900, "lowStressDuration":
|
||||
11400, "overallStressLevel": 49, "restStressDuration": 10440, "mediumStressDuration":
|
||||
14520}}, {"calendarDate": "2023-07-07", "values": {"highStressDuration": 3600,
|
||||
"lowStressDuration": 15600, "overallStressLevel": 33, "restStressDuration":
|
||||
29280, "mediumStressDuration": 9000}}, {"calendarDate": "2023-07-08", "values":
|
||||
{"highStressDuration": 240, "lowStressDuration": 18540, "overallStressLevel":
|
||||
27, "restStressDuration": 41340, "mediumStressDuration": 5340}}, {"calendarDate":
|
||||
"2023-07-09", "values": {"highStressDuration": 3780, "lowStressDuration":
|
||||
6600, "overallStressLevel": 27, "restStressDuration": 36420, "mediumStressDuration":
|
||||
5820}}, {"calendarDate": "2023-07-10", "values": {"highStressDuration": 2220,
|
||||
"lowStressDuration": 20880, "overallStressLevel": 31, "restStressDuration":
|
||||
39000, "mediumStressDuration": 10080}}, {"calendarDate": "2023-07-11", "values":
|
||||
{"highStressDuration": 960, "lowStressDuration": 10560, "overallStressLevel":
|
||||
26, "restStressDuration": 25380, "mediumStressDuration": 3480}}, {"calendarDate":
|
||||
"2023-07-12", "values": {"highStressDuration": 2520, "lowStressDuration":
|
||||
13560, "overallStressLevel": 38, "restStressDuration": 17940, "mediumStressDuration":
|
||||
10920}}, {"calendarDate": "2023-07-13", "values": {"highStressDuration": 2880,
|
||||
"lowStressDuration": 10140, "overallStressLevel": 33, "restStressDuration":
|
||||
30960, "mediumStressDuration": 8760}}, {"calendarDate": "2023-07-14", "values":
|
||||
{"highStressDuration": 6000, "lowStressDuration": 15660, "overallStressLevel":
|
||||
38, "restStressDuration": 27360, "mediumStressDuration": 9480}}, {"calendarDate":
|
||||
"2023-07-15", "values": {"highStressDuration": 3660, "lowStressDuration":
|
||||
18480, "overallStressLevel": 35, "restStressDuration": 26760, "mediumStressDuration":
|
||||
8220}}, {"calendarDate": "2023-07-16", "values": {"highStressDuration": 300,
|
||||
"lowStressDuration": 29280, "overallStressLevel": 30, "restStressDuration":
|
||||
34980, "mediumStressDuration": 5760}}, {"calendarDate": "2023-07-17", "values":
|
||||
{"highStressDuration": 1500, "lowStressDuration": 18780, "overallStressLevel":
|
||||
32, "restStressDuration": 27420, "mediumStressDuration": 9300}}, {"calendarDate":
|
||||
"2023-07-18", "values": {"highStressDuration": 600, "lowStressDuration": 30660,
|
||||
"overallStressLevel": 31, "restStressDuration": 32820, "mediumStressDuration":
|
||||
7260}}, {"calendarDate": "2023-07-19", "values": {"highStressDuration": 3180,
|
||||
"lowStressDuration": 13680, "overallStressLevel": 34, "restStressDuration":
|
||||
32340, "mediumStressDuration": 14100}}, {"calendarDate": "2023-07-20", "values":
|
||||
{"highStressDuration": 2880, "lowStressDuration": 21780, "overallStressLevel":
|
||||
38, "restStressDuration": 26580, "mediumStressDuration": 18240}}]'
|
||||
headers:
|
||||
CF-Cache-Status:
|
||||
- DYNAMIC
|
||||
CF-RAY:
|
||||
- 7f12d0397ff347ab-DFW
|
||||
Connection:
|
||||
- keep-alive
|
||||
Content-Type:
|
||||
- application/json
|
||||
Date:
|
||||
- Fri, 04 Aug 2023 00:51:41 GMT
|
||||
NEL:
|
||||
- '{"success_fraction":0.01,"report_to":"cf-nel","max_age":604800}'
|
||||
Report-To:
|
||||
- '{"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=T9CAErSSY%2FFeBvVNVWoa%2FYNy0BI%2BhWzwaRSbOHzHpcYcA8u1JRck31Y044IFAjfPbLAdM1LUfY%2Fkt2eOoD4gMvuh%2B9KKGJ0VefqDsQE15iwlyQ%2FlI2YAHnt7eAhdme6nZHe8saWUvQ%3D%3D"}],"group":"cf-nel","max_age":604800}'
|
||||
Server:
|
||||
- cloudflare
|
||||
Transfer-Encoding:
|
||||
- chunked
|
||||
alt-svc:
|
||||
- h3=":443"; ma=86400
|
||||
cache-control:
|
||||
- no-cache, no-store, private
|
||||
pragma:
|
||||
- no-cache
|
||||
set-cookie:
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BTa=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- SameSite=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- ADRUM_BT1=SANITIZED; Max-Age=SANITIZED; Expires=SANITIZED; Path=SANITIZED;
|
||||
Secure
|
||||
- _cfuvid=SANITIZED; path=SANITIZED; domain=SANITIZED; HttpOnly; Secure; SameSite=SANITIZED
|
||||
status:
|
||||
code: 200
|
||||
message: OK
|
||||
version: 1
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user