fauxbus 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fauxbus-0.1.2/.dockerignore +13 -0
- fauxbus-0.1.2/.gitignore +17 -0
- fauxbus-0.1.2/.woodpecker.yml +170 -0
- fauxbus-0.1.2/Containerfile +58 -0
- fauxbus-0.1.2/LICENSE +21 -0
- fauxbus-0.1.2/PKG-INFO +163 -0
- fauxbus-0.1.2/README.md +137 -0
- fauxbus-0.1.2/RELEASING.md +91 -0
- fauxbus-0.1.2/SPEC.md +503 -0
- fauxbus-0.1.2/container/entrypoint.sh +25 -0
- fauxbus-0.1.2/examples/compose.yaml +52 -0
- fauxbus-0.1.2/examples/seed.json +53 -0
- fauxbus-0.1.2/pyproject.toml +81 -0
- fauxbus-0.1.2/src/fauxbus/__init__.py +27 -0
- fauxbus-0.1.2/src/fauxbus/auth.py +75 -0
- fauxbus-0.1.2/src/fauxbus/cli.py +86 -0
- fauxbus-0.1.2/src/fauxbus/control_api.py +134 -0
- fauxbus-0.1.2/src/fauxbus/errors.py +59 -0
- fauxbus-0.1.2/src/fauxbus/groups_api.py +158 -0
- fauxbus-0.1.2/src/fauxbus/ids.py +46 -0
- fauxbus-0.1.2/src/fauxbus/server.py +494 -0
- fauxbus-0.1.2/src/fauxbus/world.py +774 -0
- fauxbus-0.1.2/tests/conftest.py +101 -0
- fauxbus-0.1.2/tests/test_control_plane.py +246 -0
- fauxbus-0.1.2/tests/test_examples.py +65 -0
- fauxbus-0.1.2/tests/test_groups_http.py +426 -0
- fauxbus-0.1.2/tests/test_hardening.py +282 -0
- fauxbus-0.1.2/tests/test_sdk_conformance.py +221 -0
fauxbus-0.1.2/.gitignore
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*.egg-info/
|
|
4
|
+
dist/
|
|
5
|
+
build/
|
|
6
|
+
.venv/
|
|
7
|
+
venv/
|
|
8
|
+
.pytest_cache/
|
|
9
|
+
.ruff_cache/
|
|
10
|
+
.coverage
|
|
11
|
+
|
|
12
|
+
# Internal review artifacts — findings land here for triage, not for
|
|
13
|
+
# publication. A security review is a working document: it names
|
|
14
|
+
# weaknesses before they are fixed, which is precisely the thing not to
|
|
15
|
+
# hand a stranger. What survives triage becomes a commit, an issue, or
|
|
16
|
+
# a SPEC entry — those are the public record.
|
|
17
|
+
.reviews/
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# Fauxbus CI — the conformance tether, mechanized.
|
|
2
|
+
#
|
|
3
|
+
# SPEC.md promises "one test suite, two backends: Fauxbus on every CI run,
|
|
4
|
+
# the real Globus service env-gated before each release." This file is the
|
|
5
|
+
# first half of that promise. The second half arrives with the recording
|
|
6
|
+
# session (see RELEASING.md for the honest posture until then).
|
|
7
|
+
#
|
|
8
|
+
# Fauxbus is a library, not a service — no deploy step. What ships is a
|
|
9
|
+
# wheel and an image, so CI builds and boots both.
|
|
10
|
+
#
|
|
11
|
+
# THE TRUST ARC (a lesson worth keeping): build #1 failed because
|
|
12
|
+
# untrusted repos may not mount `volumes` — correctly, since this config
|
|
13
|
+
# lives in the repo and a config that could mount /var/run/docker.sock
|
|
14
|
+
# would hand the host to anyone who can push a branch. The refusal was
|
|
15
|
+
# the security model working. An admin has since marked the repo
|
|
16
|
+
# trusted, which is a statement about who can push here, not about
|
|
17
|
+
# Woodpecker relaxing: the socket mount below is exactly as dangerous as
|
|
18
|
+
# it always was, and belongs only in repos where every committer could
|
|
19
|
+
# already root the host by other means.
|
|
20
|
+
#
|
|
21
|
+
# Pip cache mounted at /opt/cache/pip, per house convention.
|
|
22
|
+
|
|
23
|
+
# Tags get their own pipeline too: a tag re-verifies the exact tagged
|
|
24
|
+
# commit, and `event: tag` is what the parked publish-pypi step at the
|
|
25
|
+
# bottom will hang on once restored.
|
|
26
|
+
when:
|
|
27
|
+
- event: push
|
|
28
|
+
- event: tag
|
|
29
|
+
|
|
30
|
+
labels:
|
|
31
|
+
host: xdocker01
|
|
32
|
+
|
|
33
|
+
matrix:
|
|
34
|
+
PYTHON:
|
|
35
|
+
- "3.11"
|
|
36
|
+
- "3.12"
|
|
37
|
+
- "3.13"
|
|
38
|
+
|
|
39
|
+
steps:
|
|
40
|
+
# `ruff check` only — deliberately no `ruff format --check`. pyproject
|
|
41
|
+
# ignores E501 at line-length 100, which is the author saying line breaks
|
|
42
|
+
# are a human call in this codebase; a formatter gate would overrule that.
|
|
43
|
+
- name: lint
|
|
44
|
+
image: python:${PYTHON}-slim
|
|
45
|
+
commands:
|
|
46
|
+
- pip install --cache-dir /cache/pip -e ".[dev]"
|
|
47
|
+
- ruff check src/ tests/
|
|
48
|
+
volumes:
|
|
49
|
+
- /opt/cache/pip:/cache/pip
|
|
50
|
+
|
|
51
|
+
# FAUXBUS_REQUIRE_CONFORMANCE is the point of this step, not decoration.
|
|
52
|
+
# tests/test_sdk_conformance.py guards its import with `importorskip` so a
|
|
53
|
+
# bare checkout still runs; that same guard means a CI image missing
|
|
54
|
+
# globus-sdk would drop every SDK test and still go green. The env var
|
|
55
|
+
# turns that skip into a collection error, so "the SDK is the contract"
|
|
56
|
+
# stays a fact about this pipeline rather than a claim in a README.
|
|
57
|
+
- name: test
|
|
58
|
+
image: python:${PYTHON}-slim
|
|
59
|
+
environment:
|
|
60
|
+
FAUXBUS_REQUIRE_CONFORMANCE: "1"
|
|
61
|
+
commands:
|
|
62
|
+
- pip install --cache-dir /cache/pip -e ".[dev,conformance]"
|
|
63
|
+
- python -c "import globus_sdk; print('SDK under test:', globus_sdk.__version__)"
|
|
64
|
+
- pytest -v --tb=short
|
|
65
|
+
volumes:
|
|
66
|
+
- /opt/cache/pip:/cache/pip
|
|
67
|
+
|
|
68
|
+
# An editable install never exercises the wheel path, and the wheel is
|
|
69
|
+
# what the container image and every consumer actually installs. Build
|
|
70
|
+
# it here so a packaging break fails CI instead of surfacing at
|
|
71
|
+
# someone's `pipx install`.
|
|
72
|
+
- name: build-wheel
|
|
73
|
+
image: python:${PYTHON}-slim
|
|
74
|
+
commands:
|
|
75
|
+
- pip install --cache-dir /cache/pip build
|
|
76
|
+
- python -m build --wheel
|
|
77
|
+
- pip install --cache-dir /cache/pip dist/*.whl
|
|
78
|
+
- fauxbus --help
|
|
79
|
+
volumes:
|
|
80
|
+
- /opt/cache/pip:/cache/pip
|
|
81
|
+
depends_on:
|
|
82
|
+
- test
|
|
83
|
+
|
|
84
|
+
# The image is shipped documentation: examples/compose.yaml tells
|
|
85
|
+
# consumers to build it, and root-cellar's deploy module actually does.
|
|
86
|
+
# This step proves the Containerfile builds, boots, and answers the
|
|
87
|
+
# same unauthenticated /_fauxbus/ probe the HEALTHCHECK uses — the
|
|
88
|
+
# thing compose's `depends_on: service_healthy` hangs on. Gated to one
|
|
89
|
+
# matrix cell: the image pins its own Python, so building it three
|
|
90
|
+
# times would test Woodpecker, not Fauxbus.
|
|
91
|
+
#
|
|
92
|
+
# Names key on CI_PIPELINE_NUMBER, not the commit SHA: a push to main
|
|
93
|
+
# and a fleet branch can carry the SAME sha and run concurrently, and
|
|
94
|
+
# two `docker run --name fauxbus-ci-<sha>` would collide. The
|
|
95
|
+
# pipeline number is unique by construction.
|
|
96
|
+
- name: build-image
|
|
97
|
+
image: docker:cli
|
|
98
|
+
when:
|
|
99
|
+
matrix:
|
|
100
|
+
PYTHON: "3.13"
|
|
101
|
+
environment:
|
|
102
|
+
# The Containerfile uses RUN --mount, a BuildKit feature; be
|
|
103
|
+
# explicit rather than trusting the daemon's default.
|
|
104
|
+
DOCKER_BUILDKIT: "1"
|
|
105
|
+
commands:
|
|
106
|
+
- docker build -f Containerfile -t fauxbus:ci-${CI_PIPELINE_NUMBER} .
|
|
107
|
+
- docker run -d --name fauxbus-ci-${CI_PIPELINE_NUMBER} fauxbus:ci-${CI_PIPELINE_NUMBER}
|
|
108
|
+
- |
|
|
109
|
+
for i in $(seq 1 30); do
|
|
110
|
+
docker exec fauxbus-ci-${CI_PIPELINE_NUMBER} python -c \
|
|
111
|
+
"import urllib.request; urllib.request.urlopen('http://127.0.0.1:9800/_fauxbus/', timeout=1)" \
|
|
112
|
+
&& break
|
|
113
|
+
[ "$i" = "30" ] && { docker logs fauxbus-ci-${CI_PIPELINE_NUMBER}; exit 1; }
|
|
114
|
+
sleep 1
|
|
115
|
+
done
|
|
116
|
+
# The SBOM must ride in the image and name what it claims to name
|
|
117
|
+
# — an image that lost its bill of materials fails the build, not
|
|
118
|
+
# a compliance review three months later.
|
|
119
|
+
- |
|
|
120
|
+
docker exec fauxbus-ci-${CI_PIPELINE_NUMBER} python -c \
|
|
121
|
+
"import json; d = json.load(open('/usr/share/fauxbus/sbom.json')); names = sorted(c['name'] for c in d['components']); assert 'fauxbus' in names, names; print('SBOM components:', names)"
|
|
122
|
+
- docker rm -f fauxbus-ci-${CI_PIPELINE_NUMBER}
|
|
123
|
+
- docker rmi fauxbus:ci-${CI_PIPELINE_NUMBER} || true
|
|
124
|
+
volumes:
|
|
125
|
+
- /var/run/docker.sock:/var/run/docker.sock
|
|
126
|
+
depends_on:
|
|
127
|
+
- test
|
|
128
|
+
|
|
129
|
+
# ---------------------------------------------------------------------------
|
|
130
|
+
# PARKED PENDING PUBLICATION — the PyPI publish step, written 2026-08-05
|
|
131
|
+
# while we were thinking of it, so tag time never has to improvise.
|
|
132
|
+
# The name is free (checked: fauxbus 404s on PyPI) and the v0.1.0
|
|
133
|
+
# artifacts pass `twine check`; this step is the automation for v0.2
|
|
134
|
+
# onward. ONE condition left — was two:
|
|
135
|
+
#
|
|
136
|
+
# 1. A project-scoped PyPI token exists as the Woodpecker secret
|
|
137
|
+
# `pypi_token`. (PyPI's OIDC trusted publishing doesn't cover
|
|
138
|
+
# Woodpecker, so token it is.) Project-scoped implies the first,
|
|
139
|
+
# manual upload already happened — RELEASING.md has the sequence.
|
|
140
|
+
# 2. DONE 2026-08-07 — pyproject's homepage/issues URLs now point at
|
|
141
|
+
# github.com/atmarx/fauxbus, which is public with issues enabled.
|
|
142
|
+
# (Was: a package that tells strangers to file issues behind the
|
|
143
|
+
# VPN is a bad look.)
|
|
144
|
+
#
|
|
145
|
+
# The `event: tag` this hangs on is already live in the top-level when.
|
|
146
|
+
#
|
|
147
|
+
# - name: publish-pypi
|
|
148
|
+
# image: python:${PYTHON}-slim
|
|
149
|
+
# when:
|
|
150
|
+
# event: tag
|
|
151
|
+
# matrix:
|
|
152
|
+
# PYTHON: "3.13"
|
|
153
|
+
# environment:
|
|
154
|
+
# TWINE_USERNAME: __token__
|
|
155
|
+
# TWINE_PASSWORD:
|
|
156
|
+
# from_secret: pypi_token
|
|
157
|
+
# commands:
|
|
158
|
+
# - pip install --cache-dir /cache/pip build twine
|
|
159
|
+
# - python -m build
|
|
160
|
+
# # The tag names the version; the build had better agree. A tag
|
|
161
|
+
# # pointing at a .dev0 commit must die here, not on PyPI —
|
|
162
|
+
# # uploads are burn-once.
|
|
163
|
+
# - test -f "dist/fauxbus-${CI_COMMIT_TAG#v}.tar.gz" || { echo "tag ${CI_COMMIT_TAG} does not match built version:"; ls dist/; exit 1; }
|
|
164
|
+
# - twine check dist/*
|
|
165
|
+
# - twine upload --non-interactive dist/*
|
|
166
|
+
# volumes:
|
|
167
|
+
# - /opt/cache/pip:/cache/pip
|
|
168
|
+
# depends_on:
|
|
169
|
+
# - test
|
|
170
|
+
# ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Fauxbus — a wire-level fake of the Globus API.
|
|
2
|
+
#
|
|
3
|
+
# Two stages: build the wheel, install only the wheel. Fauxbus has zero
|
|
4
|
+
# runtime dependencies, so the final image is the Python base plus one
|
|
5
|
+
# pure-stdlib package — no supply chain rides along into your dev stack.
|
|
6
|
+
#
|
|
7
|
+
# Named Containerfile (the OCI-neutral spelling). Docker wants
|
|
8
|
+
# `-f Containerfile`, or in compose:
|
|
9
|
+
# build: { context: ., dockerfile: Containerfile }
|
|
10
|
+
|
|
11
|
+
FROM python:3.13-slim AS build
|
|
12
|
+
WORKDIR /src
|
|
13
|
+
COPY pyproject.toml README.md LICENSE ./
|
|
14
|
+
COPY src ./src
|
|
15
|
+
RUN pip wheel --no-deps --no-cache-dir --wheel-dir /dist .
|
|
16
|
+
|
|
17
|
+
# The SBOM is generated from a CLEAN venv holding only the wheel we just
|
|
18
|
+
# built: pointing the generator at its own environment would list the
|
|
19
|
+
# generator and its dependencies, polluting the receipt. For a
|
|
20
|
+
# zero-dependency package the bill of materials is short on purpose —
|
|
21
|
+
# it is the machine-readable proof of the "no supply chain rides along"
|
|
22
|
+
# claim, license metadata included (CycloneDX reads MIT straight out of
|
|
23
|
+
# the wheel).
|
|
24
|
+
RUN pip install --no-cache-dir cyclonedx-bom \
|
|
25
|
+
&& python -m venv /sbomenv \
|
|
26
|
+
&& /sbomenv/bin/pip install --no-cache-dir /dist/*.whl \
|
|
27
|
+
&& cyclonedx-py environment /sbomenv/bin/python --of json -o /dist/sbom.json
|
|
28
|
+
|
|
29
|
+
FROM python:3.13-slim
|
|
30
|
+
|
|
31
|
+
LABEL org.opencontainers.image.title="fauxbus" \
|
|
32
|
+
org.opencontainers.image.description="A wire-level fake of the Globus API for E2E testing. Independent test tool, not affiliated with Globus." \
|
|
33
|
+
org.opencontainers.image.source="https://github.com/atmarx/fauxbus" \
|
|
34
|
+
org.opencontainers.image.licenses="MIT"
|
|
35
|
+
|
|
36
|
+
ENV PYTHONUNBUFFERED=1
|
|
37
|
+
|
|
38
|
+
RUN --mount=type=bind,from=build,source=/dist,target=/dist \
|
|
39
|
+
pip install --no-cache-dir /dist/*.whl
|
|
40
|
+
|
|
41
|
+
# The image carries its own bill of materials — transparency travels
|
|
42
|
+
# with the artifact, not with a wiki page about the artifact.
|
|
43
|
+
COPY --from=build /dist/sbom.json /usr/share/fauxbus/sbom.json
|
|
44
|
+
|
|
45
|
+
# Unprivileged, uid matching the port — memorable, and stable for anyone
|
|
46
|
+
# who needs to reason about mounted-file ownership.
|
|
47
|
+
RUN useradd --system --uid 9800 --no-create-home fauxbus
|
|
48
|
+
COPY --chmod=755 container/entrypoint.sh /usr/local/bin/fauxbus-entrypoint
|
|
49
|
+
USER fauxbus
|
|
50
|
+
|
|
51
|
+
EXPOSE 9800
|
|
52
|
+
|
|
53
|
+
# The control-plane index answers without auth — the natural liveness
|
|
54
|
+
# probe, and what makes `depends_on: service_healthy` work in compose.
|
|
55
|
+
HEALTHCHECK --interval=5s --timeout=2s --start-period=2s --retries=3 \
|
|
56
|
+
CMD ["python", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:9800/_fauxbus/', timeout=1)"]
|
|
57
|
+
|
|
58
|
+
ENTRYPOINT ["fauxbus-entrypoint"]
|
fauxbus-0.1.2/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Andrew Marx
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
fauxbus-0.1.2/PKG-INFO
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fauxbus
|
|
3
|
+
Version: 0.1.2
|
|
4
|
+
Summary: A wire-level fake of the Globus API for E2E testing
|
|
5
|
+
Project-URL: Homepage, https://github.com/atmarx/fauxbus
|
|
6
|
+
Project-URL: Issues, https://github.com/atmarx/fauxbus/issues
|
|
7
|
+
Author: Andrew Marx
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: fake,globus,http,integration-tests,testing
|
|
11
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
12
|
+
Classifier: Environment :: Console
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Software Development :: Testing
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
|
+
Provides-Extra: conformance
|
|
21
|
+
Requires-Dist: globus-sdk<5,>=4.8.1; extra == 'conformance'
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest<9,>=8; extra == 'dev'
|
|
24
|
+
Requires-Dist: ruff<1,>=0.5; extra == 'dev'
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
|
|
27
|
+
# Fauxbus
|
|
28
|
+
|
|
29
|
+
A wire-level fake of the Globus API for E2E testing.
|
|
30
|
+
|
|
31
|
+
## The problem
|
|
32
|
+
|
|
33
|
+
Globus is the data fabric of research computing: it moves datasets
|
|
34
|
+
between universities, national labs, and HPC centers, and its API is how
|
|
35
|
+
institutions automate that world — group rosters that gate who can reach
|
|
36
|
+
which collection, permissions, transfer pipelines. If you run research
|
|
37
|
+
infrastructure, sooner or later you have code calling this API, and that
|
|
38
|
+
code matters more than most: it decides who can touch the data.
|
|
39
|
+
|
|
40
|
+
Testing that code is where it gets uncomfortable. Globus has no
|
|
41
|
+
sandbox, no emulator, no local mode. The SDK ships a testing module,
|
|
42
|
+
but it is in-process canned responses — by its own docs "a best
|
|
43
|
+
approximation of API responses" — which serves unit tests well and
|
|
44
|
+
cannot back a socket. So an integration test has two options: a real
|
|
45
|
+
tenancy (real credentials in CI, real groups with real people in them,
|
|
46
|
+
rate limits, shared mutable state, network weather), or nothing. A lot
|
|
47
|
+
of shops choose nothing. The layer that gates access to research data
|
|
48
|
+
ends up the least-tested code in the pipeline, precisely because it is
|
|
49
|
+
the riskiest thing to point a test at.
|
|
50
|
+
|
|
51
|
+
Fauxbus is a third option: a standalone HTTP server that imitates the
|
|
52
|
+
Globus API at the wire level. Point your `globus-sdk` client at
|
|
53
|
+
`http://localhost:9800` instead of the real service and run your
|
|
54
|
+
existing suite — same requests, same response shapes, same status
|
|
55
|
+
codes. Your code never learns it isn't talking to Globus. And because
|
|
56
|
+
the whole point of a fake is control, failure is a feature you schedule:
|
|
57
|
+
a 429 on exactly the request where your retry logic needs exercise, an
|
|
58
|
+
identity that is already in the group, a batch action that partially
|
|
59
|
+
fails the way the real service partially fails.
|
|
60
|
+
|
|
61
|
+
**Status: early but real.** The complete `GroupsClient` surface of
|
|
62
|
+
globus-sdk 4.8.1 is implemented — all fourteen methods, all eleven
|
|
63
|
+
batch membership verbs — and the conformance suite drives every one of
|
|
64
|
+
the fourteen through the actual globus-sdk, unmodified, including the
|
|
65
|
+
SDK's retry layer recovering through an injected 429. Response shapes
|
|
66
|
+
not yet backed by a recording are marked `PROVISIONAL` in code and
|
|
67
|
+
tracked as conformance obligations. Nothing is stable yet — see
|
|
68
|
+
[SPEC.md](SPEC.md) for the full design.
|
|
69
|
+
|
|
70
|
+
## Fake, not mock
|
|
71
|
+
|
|
72
|
+
Your code doesn't know Fauxbus is there. No monkeypatching, no fixture
|
|
73
|
+
dicts shaped like your guess about what Globus returns. The client
|
|
74
|
+
library you ship is the client library you test against — Fauxbus
|
|
75
|
+
imitates what `globus-sdk` actually sends and expects, pinned to a
|
|
76
|
+
stated SDK version and derived from real recorded responses, never
|
|
77
|
+
guesswork.
|
|
78
|
+
|
|
79
|
+
If canned responses inside a Python unit test are all you need, use the
|
|
80
|
+
SDK's own `globus_sdk.testing` — that's the in-process layer, and its
|
|
81
|
+
fixtures are Fauxbus's ground truth. Fauxbus is for the layer it
|
|
82
|
+
architecturally can't reach: a real socket your whole stack can talk
|
|
83
|
+
to, with state behind it (think moto next to botocore's `Stubber`).
|
|
84
|
+
|
|
85
|
+
## Quick start
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
# not on PyPI yet — install straight from the repo
|
|
89
|
+
pipx install git+https://github.com/atmarx/fauxbus
|
|
90
|
+
fauxbus serve --port 9800
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Point `globus_sdk.GroupsClient(...)` at that port instead of the real
|
|
94
|
+
service host and run your existing test suite. Other Globus clients
|
|
95
|
+
will meet honest `501`s until their surfaces exist — see below.
|
|
96
|
+
|
|
97
|
+
## In a compose stack
|
|
98
|
+
|
|
99
|
+
The repo ships a `Containerfile`. Zero runtime dependencies means the
|
|
100
|
+
image is the Python base plus one stdlib-only wheel — nothing else rides
|
|
101
|
+
along. Mount a state document at `/seed.json` and the world exists
|
|
102
|
+
before the first test connects:
|
|
103
|
+
|
|
104
|
+
```yaml
|
|
105
|
+
services:
|
|
106
|
+
fauxbus:
|
|
107
|
+
build: { context: ., dockerfile: Containerfile }
|
|
108
|
+
ports: ["9800:9800"]
|
|
109
|
+
volumes: ["./seed.json:/seed.json:ro"]
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
The image ships a healthcheck on the control-plane index, so
|
|
113
|
+
`depends_on: { fauxbus: { condition: service_healthy } }` does the
|
|
114
|
+
waiting for you. A runnable example — sibling-service pattern, seed
|
|
115
|
+
file included, both round-trip tested in CI — is in
|
|
116
|
+
[`examples/`](examples/).
|
|
117
|
+
|
|
118
|
+
## The control plane
|
|
119
|
+
|
|
120
|
+
Everything test-harness-y lives under `/_fauxbus/`, a prefix no Globus
|
|
121
|
+
API will ever claim — the imitated surface stays pure. `GET
|
|
122
|
+
/_fauxbus/state` dumps everything in the world for assertions; `POST
|
|
123
|
+
/_fauxbus/reset` returns the world to its canonical seed (or to empty)
|
|
124
|
+
between tests — mutate freely, reset comes home. Full control-plane
|
|
125
|
+
surface is in [SPEC.md](SPEC.md).
|
|
126
|
+
|
|
127
|
+
It is unauthenticated by design — it imitates a service that answers
|
|
128
|
+
the network, and harnesses are often sibling containers. Which means
|
|
129
|
+
anyone who can reach the port can rewrite the world and arm forged
|
|
130
|
+
responses; for a fake, the sharpest risk isn't a crash but a lie, since
|
|
131
|
+
tests that trust it would go green when they should go red. Run it
|
|
132
|
+
where you'd run any test fixture. On a shared host,
|
|
133
|
+
`--control-loopback-only` refuses `/_fauxbus/` from non-loopback peers
|
|
134
|
+
without touching the imitated surface. The reasoning, and the two
|
|
135
|
+
things that flag deliberately doesn't solve, are in
|
|
136
|
+
[SPEC.md](SPEC.md#control-plane-reachability).
|
|
137
|
+
|
|
138
|
+
## Loud 501s, no fiction
|
|
139
|
+
|
|
140
|
+
Fauxbus only implements endpoints a real consumer needs. Anything it
|
|
141
|
+
doesn't imitate yet returns a loud `501` with a pointer to file an
|
|
142
|
+
issue — never a silent wrong answer pretending to be Globus. If you
|
|
143
|
+
hit one, that's the fastest way to get it prioritized.
|
|
144
|
+
|
|
145
|
+
## The source is meant to be read
|
|
146
|
+
|
|
147
|
+
Comments narrate the design decisions where they live — why uuid5 and
|
|
148
|
+
never uuid4, why one lock, why 404 instead of 403, why failure
|
|
149
|
+
injection can't touch the control plane — at a depth meant for someone
|
|
150
|
+
learning how to *build* a wire-level fake, not just use one.
|
|
151
|
+
|
|
152
|
+
## Not affiliated with Globus
|
|
153
|
+
|
|
154
|
+
Fauxbus is an independent test tool, not affiliated with or endorsed by
|
|
155
|
+
Globus or the University of Chicago. "Globus" is their trademark; this
|
|
156
|
+
project imitates the API's behavior for local testing only — the name
|
|
157
|
+
is a confession, not an infringement. It's *faux*.
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
MIT. Fauxbus contains no Globus code: it imitates publicly documented
|
|
162
|
+
wire behavior, grounded against the Apache-2.0 `globus-sdk` as a
|
|
163
|
+
test-time dependency only.
|
fauxbus-0.1.2/README.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Fauxbus
|
|
2
|
+
|
|
3
|
+
A wire-level fake of the Globus API for E2E testing.
|
|
4
|
+
|
|
5
|
+
## The problem
|
|
6
|
+
|
|
7
|
+
Globus is the data fabric of research computing: it moves datasets
|
|
8
|
+
between universities, national labs, and HPC centers, and its API is how
|
|
9
|
+
institutions automate that world — group rosters that gate who can reach
|
|
10
|
+
which collection, permissions, transfer pipelines. If you run research
|
|
11
|
+
infrastructure, sooner or later you have code calling this API, and that
|
|
12
|
+
code matters more than most: it decides who can touch the data.
|
|
13
|
+
|
|
14
|
+
Testing that code is where it gets uncomfortable. Globus has no
|
|
15
|
+
sandbox, no emulator, no local mode. The SDK ships a testing module,
|
|
16
|
+
but it is in-process canned responses — by its own docs "a best
|
|
17
|
+
approximation of API responses" — which serves unit tests well and
|
|
18
|
+
cannot back a socket. So an integration test has two options: a real
|
|
19
|
+
tenancy (real credentials in CI, real groups with real people in them,
|
|
20
|
+
rate limits, shared mutable state, network weather), or nothing. A lot
|
|
21
|
+
of shops choose nothing. The layer that gates access to research data
|
|
22
|
+
ends up the least-tested code in the pipeline, precisely because it is
|
|
23
|
+
the riskiest thing to point a test at.
|
|
24
|
+
|
|
25
|
+
Fauxbus is a third option: a standalone HTTP server that imitates the
|
|
26
|
+
Globus API at the wire level. Point your `globus-sdk` client at
|
|
27
|
+
`http://localhost:9800` instead of the real service and run your
|
|
28
|
+
existing suite — same requests, same response shapes, same status
|
|
29
|
+
codes. Your code never learns it isn't talking to Globus. And because
|
|
30
|
+
the whole point of a fake is control, failure is a feature you schedule:
|
|
31
|
+
a 429 on exactly the request where your retry logic needs exercise, an
|
|
32
|
+
identity that is already in the group, a batch action that partially
|
|
33
|
+
fails the way the real service partially fails.
|
|
34
|
+
|
|
35
|
+
**Status: early but real.** The complete `GroupsClient` surface of
|
|
36
|
+
globus-sdk 4.8.1 is implemented — all fourteen methods, all eleven
|
|
37
|
+
batch membership verbs — and the conformance suite drives every one of
|
|
38
|
+
the fourteen through the actual globus-sdk, unmodified, including the
|
|
39
|
+
SDK's retry layer recovering through an injected 429. Response shapes
|
|
40
|
+
not yet backed by a recording are marked `PROVISIONAL` in code and
|
|
41
|
+
tracked as conformance obligations. Nothing is stable yet — see
|
|
42
|
+
[SPEC.md](SPEC.md) for the full design.
|
|
43
|
+
|
|
44
|
+
## Fake, not mock
|
|
45
|
+
|
|
46
|
+
Your code doesn't know Fauxbus is there. No monkeypatching, no fixture
|
|
47
|
+
dicts shaped like your guess about what Globus returns. The client
|
|
48
|
+
library you ship is the client library you test against — Fauxbus
|
|
49
|
+
imitates what `globus-sdk` actually sends and expects, pinned to a
|
|
50
|
+
stated SDK version and derived from real recorded responses, never
|
|
51
|
+
guesswork.
|
|
52
|
+
|
|
53
|
+
If canned responses inside a Python unit test are all you need, use the
|
|
54
|
+
SDK's own `globus_sdk.testing` — that's the in-process layer, and its
|
|
55
|
+
fixtures are Fauxbus's ground truth. Fauxbus is for the layer it
|
|
56
|
+
architecturally can't reach: a real socket your whole stack can talk
|
|
57
|
+
to, with state behind it (think moto next to botocore's `Stubber`).
|
|
58
|
+
|
|
59
|
+
## Quick start
|
|
60
|
+
|
|
61
|
+
```sh
|
|
62
|
+
# not on PyPI yet — install straight from the repo
|
|
63
|
+
pipx install git+https://github.com/atmarx/fauxbus
|
|
64
|
+
fauxbus serve --port 9800
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Point `globus_sdk.GroupsClient(...)` at that port instead of the real
|
|
68
|
+
service host and run your existing test suite. Other Globus clients
|
|
69
|
+
will meet honest `501`s until their surfaces exist — see below.
|
|
70
|
+
|
|
71
|
+
## In a compose stack
|
|
72
|
+
|
|
73
|
+
The repo ships a `Containerfile`. Zero runtime dependencies means the
|
|
74
|
+
image is the Python base plus one stdlib-only wheel — nothing else rides
|
|
75
|
+
along. Mount a state document at `/seed.json` and the world exists
|
|
76
|
+
before the first test connects:
|
|
77
|
+
|
|
78
|
+
```yaml
|
|
79
|
+
services:
|
|
80
|
+
fauxbus:
|
|
81
|
+
build: { context: ., dockerfile: Containerfile }
|
|
82
|
+
ports: ["9800:9800"]
|
|
83
|
+
volumes: ["./seed.json:/seed.json:ro"]
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
The image ships a healthcheck on the control-plane index, so
|
|
87
|
+
`depends_on: { fauxbus: { condition: service_healthy } }` does the
|
|
88
|
+
waiting for you. A runnable example — sibling-service pattern, seed
|
|
89
|
+
file included, both round-trip tested in CI — is in
|
|
90
|
+
[`examples/`](examples/).
|
|
91
|
+
|
|
92
|
+
## The control plane
|
|
93
|
+
|
|
94
|
+
Everything test-harness-y lives under `/_fauxbus/`, a prefix no Globus
|
|
95
|
+
API will ever claim — the imitated surface stays pure. `GET
|
|
96
|
+
/_fauxbus/state` dumps everything in the world for assertions; `POST
|
|
97
|
+
/_fauxbus/reset` returns the world to its canonical seed (or to empty)
|
|
98
|
+
between tests — mutate freely, reset comes home. Full control-plane
|
|
99
|
+
surface is in [SPEC.md](SPEC.md).
|
|
100
|
+
|
|
101
|
+
It is unauthenticated by design — it imitates a service that answers
|
|
102
|
+
the network, and harnesses are often sibling containers. Which means
|
|
103
|
+
anyone who can reach the port can rewrite the world and arm forged
|
|
104
|
+
responses; for a fake, the sharpest risk isn't a crash but a lie, since
|
|
105
|
+
tests that trust it would go green when they should go red. Run it
|
|
106
|
+
where you'd run any test fixture. On a shared host,
|
|
107
|
+
`--control-loopback-only` refuses `/_fauxbus/` from non-loopback peers
|
|
108
|
+
without touching the imitated surface. The reasoning, and the two
|
|
109
|
+
things that flag deliberately doesn't solve, are in
|
|
110
|
+
[SPEC.md](SPEC.md#control-plane-reachability).
|
|
111
|
+
|
|
112
|
+
## Loud 501s, no fiction
|
|
113
|
+
|
|
114
|
+
Fauxbus only implements endpoints a real consumer needs. Anything it
|
|
115
|
+
doesn't imitate yet returns a loud `501` with a pointer to file an
|
|
116
|
+
issue — never a silent wrong answer pretending to be Globus. If you
|
|
117
|
+
hit one, that's the fastest way to get it prioritized.
|
|
118
|
+
|
|
119
|
+
## The source is meant to be read
|
|
120
|
+
|
|
121
|
+
Comments narrate the design decisions where they live — why uuid5 and
|
|
122
|
+
never uuid4, why one lock, why 404 instead of 403, why failure
|
|
123
|
+
injection can't touch the control plane — at a depth meant for someone
|
|
124
|
+
learning how to *build* a wire-level fake, not just use one.
|
|
125
|
+
|
|
126
|
+
## Not affiliated with Globus
|
|
127
|
+
|
|
128
|
+
Fauxbus is an independent test tool, not affiliated with or endorsed by
|
|
129
|
+
Globus or the University of Chicago. "Globus" is their trademark; this
|
|
130
|
+
project imitates the API's behavior for local testing only — the name
|
|
131
|
+
is a confession, not an infringement. It's *faux*.
|
|
132
|
+
|
|
133
|
+
## License
|
|
134
|
+
|
|
135
|
+
MIT. Fauxbus contains no Globus code: it imitates publicly documented
|
|
136
|
+
wire behavior, grounded against the Apache-2.0 `globus-sdk` as a
|
|
137
|
+
test-time dependency only.
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Releasing fauxbus
|
|
2
|
+
|
|
3
|
+
The ritual, written down. The point of a checklist is that a release
|
|
4
|
+
cut under time pressure walks the same path as one cut with all the
|
|
5
|
+
time in the world.
|
|
6
|
+
|
|
7
|
+
## 0. The tether posture, stated honestly
|
|
8
|
+
|
|
9
|
+
SPEC's conformance tether has two legs: Fauxbus on every CI run (live
|
|
10
|
+
since v0.2.5), and the real Globus service, env-gated, before each
|
|
11
|
+
release. The second leg needs a sacrificial tenancy, and **none
|
|
12
|
+
exists anywhere yet** — dev deliberately has no Globus tenancy (that
|
|
13
|
+
absence is why fauxbus is load-bearing for its first consumer).
|
|
14
|
+
|
|
15
|
+
So, until the first recording session is possible:
|
|
16
|
+
|
|
17
|
+
- Every release ships with the **PROVISIONAL inventory** in its release
|
|
18
|
+
notes. Generate it: `grep -rn PROVISIONAL src/`. The batch
|
|
19
|
+
membership response document is the standing #1.
|
|
20
|
+
- The moment a sacrificial tenancy exists, the recording session
|
|
21
|
+
becomes **release-blocking for every release after it becomes
|
|
22
|
+
possible**. A gate nobody can run is worse than an honest gap —
|
|
23
|
+
but a gate somebody could run and didn't is just a gap.
|
|
24
|
+
|
|
25
|
+
## 1. Green, current, coordinated
|
|
26
|
+
|
|
27
|
+
- [ ] CI green on `main` — lint, conformance-enforced suite, and wheel
|
|
28
|
+
across 3.11/3.12/3.13, plus the image build-and-boot step.
|
|
29
|
+
- [ ] SPEC.md changelog carries an entry for this version.
|
|
30
|
+
- [ ] **The re-pin handshake.** Downstream consumers may pin fauxbus
|
|
31
|
+
in more than one place (the first consumer pins it twice: once
|
|
32
|
+
as an application dependency, once in its deploy stack). Hand
|
|
33
|
+
consumers the new tag so every pin moves **in one commit** —
|
|
34
|
+
never let pins drift, because a half-repinned consumer tests two
|
|
35
|
+
different fakes and believes it tested one.
|
|
36
|
+
|
|
37
|
+
## 2. Version and tag
|
|
38
|
+
|
|
39
|
+
- [ ] `src/fauxbus/__init__.py`: drop the `.dev0` from `__version__`
|
|
40
|
+
(e.g. `0.1.0.dev0` → `0.1.0`). **That one line is the whole
|
|
41
|
+
bump** — pyproject reads it at build time. It did not use to be:
|
|
42
|
+
the version lived in two files, this checklist named only one of
|
|
43
|
+
them, and v0.1.0 and v0.1.1 both shipped reporting `0.1.0.dev0`
|
|
44
|
+
to `--version`, the banner, and `GET /_fauxbus/`. A checklist
|
|
45
|
+
that names one of two sources of truth is a checklist that walks
|
|
46
|
+
you confidently past the bug.
|
|
47
|
+
- [ ] Confirm the artifact agrees before you tag: `python -m build`
|
|
48
|
+
then check `dist/` is named for the version you meant. (CI's
|
|
49
|
+
tagged pipeline enforces this too, but after the tag exists.)
|
|
50
|
+
- [ ] Commit `Release vX.Y.Z`, tag `vX.Y.Z`, push commit and tag.
|
|
51
|
+
- [ ] Immediately follow with a bump to the next `.dev0` so `main` is
|
|
52
|
+
never ambiguous about whether it is a release.
|
|
53
|
+
|
|
54
|
+
## 3. Artifacts
|
|
55
|
+
|
|
56
|
+
- The wheel: CI builds it on every push; the tagged pipeline's wheel is
|
|
57
|
+
the release artifact.
|
|
58
|
+
- The image: built from the tag via the shipped `Containerfile`. It
|
|
59
|
+
carries its own CycloneDX SBOM at `/usr/share/fauxbus/sbom.json`,
|
|
60
|
+
generated at image build from a clean install of the wheel and
|
|
61
|
+
asserted by CI — for a zero-dependency package, the SBOM is the
|
|
62
|
+
receipt for the packaging claim. A bare wheel install can mint the
|
|
63
|
+
same receipt: `cyclonedx-py environment <venv-python>`.
|
|
64
|
+
- Publication: **PyPI — decided 2026-08-05, not yet armed.** The name
|
|
65
|
+
is free and the v0.1.0 artifacts pass `twine check`. The sequence,
|
|
66
|
+
in order, none of it improvised at tag time:
|
|
67
|
+
1. **First upload is manual**, from a clean tag build:
|
|
68
|
+
`git archive vX.Y.Z | tar -x -C /tmp/rel`, then `python -m build`
|
|
69
|
+
and `twine upload dist/*` from there. Manual because the first
|
|
70
|
+
upload is what creates the PyPI project a token can be scoped to.
|
|
71
|
+
2. **Scope a token** to the project; store it as the Woodpecker
|
|
72
|
+
secret `pypi_token`.
|
|
73
|
+
3. **Restore the parked `publish-pypi` step** at the bottom of
|
|
74
|
+
`.woodpecker.yml` (restore conditions annotated there). Tags
|
|
75
|
+
publish automatically from then on, with a guard that dies if the
|
|
76
|
+
tag's version disagrees with the built artifacts.
|
|
77
|
+
4. **Then, and only then, flip the docs**: README's install line
|
|
78
|
+
becomes the real `pipx install fauxbus`, and the blog draft's
|
|
79
|
+
"not on PyPI yet" caveat goes. Docs never promise a package
|
|
80
|
+
that isn't live.
|
|
81
|
+
|
|
82
|
+
Prerequisite for all of it: public homepage/issues URLs in
|
|
83
|
+
pyproject (the mirror decision). **Satisfied 2026-08-07** — they
|
|
84
|
+
point at `github.com/atmarx/fauxbus`, which is public and has issues
|
|
85
|
+
enabled. A container registry remains a decision not yet taken.
|
|
86
|
+
|
|
87
|
+
## 4. Tell the people
|
|
88
|
+
|
|
89
|
+
- Announce the release where consumers actually look, with the
|
|
90
|
+
PROVISIONAL inventory and the re-pin pointer. Consumers learn about
|
|
91
|
+
releases from the announcement, not from watching the repo.
|