assertion-cli 0.5.18__tar.gz → 0.5.19__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.
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/PKG-INFO +2 -3
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/README.md +1 -2
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/api.py +3 -3
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/assertion_cli.egg-info/PKG-INFO +2 -3
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/credentials.py +12 -51
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/main.py +2 -4
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/pyproject.toml +1 -1
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/tests/test_auth.py +3 -3
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/tests/test_credentials.py +26 -29
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/assertion_cli.egg-info/SOURCES.txt +0 -0
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/assertion_cli.egg-info/dependency_links.txt +0 -0
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/assertion_cli.egg-info/entry_points.txt +0 -0
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/assertion_cli.egg-info/requires.txt +0 -0
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/assertion_cli.egg-info/top_level.txt +0 -0
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/bundle.py +0 -0
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/git.py +0 -0
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/link.py +0 -0
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/models.py +0 -0
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/session.py +0 -0
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/setup.cfg +0 -0
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/templates/AGENTS.md +0 -0
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/templates/SKILL.md +0 -0
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/templates/__init__.py +0 -0
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/tests/test_api.py +0 -0
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/tests/test_bundle.py +0 -0
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/tests/test_decision.py +0 -0
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/tests/test_git.py +0 -0
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/tests/test_install.py +0 -0
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/tests/test_link.py +0 -0
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/tests/test_main.py +0 -0
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/tests/test_prompt.py +0 -0
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/tests/test_session.py +0 -0
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/tests/test_session_start.py +0 -0
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/tests/test_verify.py +0 -0
- {assertion_cli-0.5.18 → assertion_cli-0.5.19}/tests/test_verify_integration.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: assertion-cli
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.19
|
|
4
4
|
Summary: CLI for the Assertion API
|
|
5
5
|
Requires-Python: >=3.13
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -74,8 +74,7 @@ shows which token is in effect.
|
|
|
74
74
|
Token resolution at call time, in order:
|
|
75
75
|
|
|
76
76
|
1. the token in `repo_index` for the current repo's GitHub `owner/repo`;
|
|
77
|
-
2.
|
|
78
|
-
3. the `ASSERTION_API_TOKEN` environment variable (a backup for CI, or repos
|
|
77
|
+
2. the `ASSERTION_API_TOKEN` environment variable (a backup for CI, or repos
|
|
79
78
|
with no remote that `asrt login` won't store);
|
|
80
79
|
|
|
81
80
|
with a clear "run `asrt login`" error when neither is present. The API base URL
|
|
@@ -61,8 +61,7 @@ shows which token is in effect.
|
|
|
61
61
|
Token resolution at call time, in order:
|
|
62
62
|
|
|
63
63
|
1. the token in `repo_index` for the current repo's GitHub `owner/repo`;
|
|
64
|
-
2.
|
|
65
|
-
3. the `ASSERTION_API_TOKEN` environment variable (a backup for CI, or repos
|
|
64
|
+
2. the `ASSERTION_API_TOKEN` environment variable (a backup for CI, or repos
|
|
66
65
|
with no remote that `asrt login` won't store);
|
|
67
66
|
|
|
68
67
|
with a clear "run `asrt login`" error when neither is present. The API base URL
|
|
@@ -35,9 +35,9 @@ def _base_url() -> str:
|
|
|
35
35
|
def _resolve_token() -> str | None:
|
|
36
36
|
"""Token for the current repo, else the shell env-var backup, else None.
|
|
37
37
|
|
|
38
|
-
Stored credentials are
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
Stored credentials are keyed by the current repo's GitHub ``owner/repo``
|
|
39
|
+
identity. A real `export ASSERTION_API_TOKEN` only fills in when no stored
|
|
40
|
+
entry matches.
|
|
41
41
|
"""
|
|
42
42
|
repo_root = find_git_root_or_none(Path.cwd())
|
|
43
43
|
repo_full_name = (
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: assertion-cli
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.19
|
|
4
4
|
Summary: CLI for the Assertion API
|
|
5
5
|
Requires-Python: >=3.13
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -74,8 +74,7 @@ shows which token is in effect.
|
|
|
74
74
|
Token resolution at call time, in order:
|
|
75
75
|
|
|
76
76
|
1. the token in `repo_index` for the current repo's GitHub `owner/repo`;
|
|
77
|
-
2.
|
|
78
|
-
3. the `ASSERTION_API_TOKEN` environment variable (a backup for CI, or repos
|
|
77
|
+
2. the `ASSERTION_API_TOKEN` environment variable (a backup for CI, or repos
|
|
79
78
|
with no remote that `asrt login` won't store);
|
|
80
79
|
|
|
81
80
|
with a clear "run `asrt login`" error when neither is present. The API base URL
|
|
@@ -10,14 +10,10 @@ points at a connected repo:
|
|
|
10
10
|
"repo_index": {"acme/mvp": "alk_..."}
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
The legacy v1 ``repos`` path map is still read as a fallback:
|
|
14
|
-
|
|
15
|
-
{"version": 1, "repos": {"/abs/path/to/repo": "alk_..."}}
|
|
16
|
-
|
|
17
13
|
This module is deliberately git-free and pure stdlib — callers pass in the repo
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
full name (resolved via ``git.py``) and handle their own error reporting. Token
|
|
15
|
+
resolution precedence (repo entry first, then the env-var backup) lives in
|
|
16
|
+
``api.py``; this module only owns on-disk reads and writes.
|
|
21
17
|
"""
|
|
22
18
|
|
|
23
19
|
from __future__ import annotations
|
|
@@ -46,7 +42,6 @@ def _empty_store() -> dict:
|
|
|
46
42
|
"version": CREDENTIALS_VERSION,
|
|
47
43
|
"tokens": {},
|
|
48
44
|
"repo_index": {},
|
|
49
|
-
"repos": {},
|
|
50
45
|
}
|
|
51
46
|
|
|
52
47
|
|
|
@@ -70,16 +65,10 @@ def load_store() -> dict:
|
|
|
70
65
|
data["tokens"] = {}
|
|
71
66
|
if not isinstance(data.get("repo_index"), dict):
|
|
72
67
|
data["repo_index"] = _repo_index_from_tokens(data["tokens"])
|
|
73
|
-
|
|
74
|
-
data["repos"] = {}
|
|
68
|
+
data.pop("repos", None)
|
|
75
69
|
return data
|
|
76
70
|
|
|
77
71
|
|
|
78
|
-
def _repo_key(repo_root: Path) -> str:
|
|
79
|
-
"""Canonical store key for a repo root (absolute, symlinks resolved)."""
|
|
80
|
-
return str(repo_root.resolve())
|
|
81
|
-
|
|
82
|
-
|
|
83
72
|
def _repo_name_key(repo_full_name: str) -> str:
|
|
84
73
|
return repo_full_name.strip().lower()
|
|
85
74
|
|
|
@@ -124,14 +113,11 @@ def _write_store(store: dict) -> Path:
|
|
|
124
113
|
return path
|
|
125
114
|
|
|
126
115
|
|
|
127
|
-
def save_token(
|
|
128
|
-
token: str, *, repo_root: Path, repo_full_names: list[str] | None = None
|
|
129
|
-
) -> Path:
|
|
116
|
+
def save_token(token: str, *, repo_full_names: list[str] | None = None) -> Path:
|
|
130
117
|
"""Persist ``token`` and return the credentials file path.
|
|
131
118
|
|
|
132
119
|
``repo_full_names`` are canonical GitHub ``owner/repo`` names visible to the
|
|
133
|
-
token.
|
|
134
|
-
file during a rolling upgrade.
|
|
120
|
+
token.
|
|
135
121
|
"""
|
|
136
122
|
store = load_store()
|
|
137
123
|
if repo_full_names is not None:
|
|
@@ -159,20 +145,17 @@ def save_token(
|
|
|
159
145
|
store["tokens"][token] = normalized
|
|
160
146
|
for repo in normalized:
|
|
161
147
|
repo_index[repo] = token
|
|
162
|
-
store["repos"][_repo_key(repo_root)] = token
|
|
163
148
|
return _write_store(store)
|
|
164
149
|
|
|
165
150
|
|
|
166
|
-
def remove_token(*,
|
|
151
|
+
def remove_token(*, repo_full_name: str | None = None) -> bool:
|
|
167
152
|
"""Delete this repo's saved credential. Return ``True`` if one was removed."""
|
|
168
153
|
store = load_store()
|
|
169
|
-
|
|
170
|
-
token = store["repos"].pop(key, None)
|
|
171
|
-
removed = token is not None
|
|
154
|
+
removed = False
|
|
172
155
|
if repo_full_name is not None:
|
|
173
156
|
repo_key = _repo_name_key(repo_full_name)
|
|
174
157
|
indexed_token = store["repo_index"].pop(repo_key, None)
|
|
175
|
-
target_token =
|
|
158
|
+
target_token = indexed_token
|
|
176
159
|
for saved_token, repos in list(store["tokens"].items()):
|
|
177
160
|
if target_token is not None and saved_token != target_token:
|
|
178
161
|
continue
|
|
@@ -192,23 +175,6 @@ def remove_token(*, repo_root: Path, repo_full_name: str | None = None) -> bool:
|
|
|
192
175
|
if saved_token == target_token:
|
|
193
176
|
break
|
|
194
177
|
removed = removed or indexed_token is not None
|
|
195
|
-
if token:
|
|
196
|
-
repo_refs = 0
|
|
197
|
-
for saved in store["repos"].values():
|
|
198
|
-
if saved == token:
|
|
199
|
-
repo_refs += 1
|
|
200
|
-
indexed_refs = [
|
|
201
|
-
indexed_token
|
|
202
|
-
for indexed_token in store["repo_index"].values()
|
|
203
|
-
if indexed_token == token
|
|
204
|
-
]
|
|
205
|
-
if (
|
|
206
|
-
repo_refs == 0
|
|
207
|
-
and not indexed_refs
|
|
208
|
-
and token in store["tokens"]
|
|
209
|
-
and repo_full_name is None
|
|
210
|
-
):
|
|
211
|
-
del store["tokens"][token]
|
|
212
178
|
if removed:
|
|
213
179
|
_write_store(store)
|
|
214
180
|
return removed
|
|
@@ -219,8 +185,8 @@ def resolve_token(
|
|
|
219
185
|
) -> str | None:
|
|
220
186
|
"""Return the stored token for ``repo_full_name`` / ``repo_root``.
|
|
221
187
|
|
|
222
|
-
Repo-name matching
|
|
223
|
-
|
|
188
|
+
Repo-name matching survives reclones/moved folders because it uses the
|
|
189
|
+
checkout's GitHub ``owner/repo`` identity instead of the local path.
|
|
224
190
|
"""
|
|
225
191
|
store = load_store()
|
|
226
192
|
if repo_full_name is not None:
|
|
@@ -228,10 +194,7 @@ def resolve_token(
|
|
|
228
194
|
token = store["repo_index"].get(local)
|
|
229
195
|
if token:
|
|
230
196
|
return token
|
|
231
|
-
|
|
232
|
-
return None
|
|
233
|
-
token = store["repos"].get(_repo_key(repo_root))
|
|
234
|
-
return token or None
|
|
197
|
+
return None
|
|
235
198
|
|
|
236
199
|
|
|
237
200
|
def describe_source(
|
|
@@ -246,8 +209,6 @@ def describe_source(
|
|
|
246
209
|
and resolve_token(repo_root, repo_full_name) is not None
|
|
247
210
|
):
|
|
248
211
|
return f"the saved token for {repo_full_name.strip().lower()}"
|
|
249
|
-
if repo_root is not None and resolve_token(repo_root) is not None:
|
|
250
|
-
return f"the saved token for {_repo_key(repo_root)}"
|
|
251
212
|
if (os.environ.get(API_TOKEN_ENV) or "").strip():
|
|
252
213
|
return f"the {API_TOKEN_ENV} environment variable"
|
|
253
214
|
return None
|
|
@@ -174,9 +174,7 @@ def login(
|
|
|
174
174
|
"the dashboard, then re-run `asrt login`."
|
|
175
175
|
)
|
|
176
176
|
|
|
177
|
-
path = save_token(
|
|
178
|
-
token, repo_root=repo_root, repo_full_names=[s.repo for s in stack_list]
|
|
179
|
-
)
|
|
177
|
+
path = save_token(token, repo_full_names=[s.repo for s in stack_list])
|
|
180
178
|
names = ", ".join(f"'{s.name}'" for s in matched)
|
|
181
179
|
typer.echo(f"✓ {local} is connected to stack(s): {names}")
|
|
182
180
|
typer.echo(f"Saved token for {repo_root} to {path}")
|
|
@@ -190,7 +188,7 @@ def logout() -> None:
|
|
|
190
188
|
exit_with_error("Not inside a git repository.")
|
|
191
189
|
|
|
192
190
|
local = current_repo_full_name(repo_root)
|
|
193
|
-
if remove_token(
|
|
191
|
+
if remove_token(repo_full_name=local):
|
|
194
192
|
typer.echo(f"Removed saved token for {repo_root}.")
|
|
195
193
|
else:
|
|
196
194
|
typer.echo(f"No saved token for {repo_root}.")
|
|
@@ -52,7 +52,7 @@ def test_login_saves_when_repo_matches_stack(repo: Path) -> None:
|
|
|
52
52
|
assert result.exit_code == 0, result.output
|
|
53
53
|
assert "connected to stack" in result.output.lower()
|
|
54
54
|
assert route.called
|
|
55
|
-
assert credentials.resolve_token(repo) == "alk_new"
|
|
55
|
+
assert credentials.resolve_token(repo, "acme/mvp") == "alk_new"
|
|
56
56
|
assert credentials.resolve_token(None, "acme/mvp") == "alk_new"
|
|
57
57
|
assert credentials.resolve_token(None, "acme/docs") == "alk_new"
|
|
58
58
|
|
|
@@ -96,7 +96,7 @@ def test_login_no_remote_does_not_save_or_call_network(
|
|
|
96
96
|
|
|
97
97
|
|
|
98
98
|
def test_logout_removes_token(repo: Path) -> None:
|
|
99
|
-
credentials.save_token("alk_new",
|
|
99
|
+
credentials.save_token("alk_new", repo_full_names=["acme/mvp"])
|
|
100
100
|
|
|
101
101
|
result = runner.invoke(main.app, ["logout"])
|
|
102
102
|
|
|
@@ -114,7 +114,7 @@ def test_logout_when_nothing_saved(repo: Path) -> None:
|
|
|
114
114
|
|
|
115
115
|
@respx.mock
|
|
116
116
|
def test_whoami_reports_saved_token(repo: Path) -> None:
|
|
117
|
-
credentials.save_token("alk_new",
|
|
117
|
+
credentials.save_token("alk_new", repo_full_names=["acme/mvp"])
|
|
118
118
|
respx.get(STACKS_URL).mock(return_value=_stacks_response("acme/mvp", "acme/other"))
|
|
119
119
|
|
|
120
120
|
result = runner.invoke(main.app, ["whoami"])
|
|
@@ -26,14 +26,12 @@ def repo(tmp_path: Path) -> Path:
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
def test_save_and_resolve_round_trip(repo: Path) -> None:
|
|
29
|
-
credentials.save_token(
|
|
30
|
-
"alk_one", repo_root=repo, repo_full_names=["acme/mvp", "Acme/Docs"]
|
|
31
|
-
)
|
|
29
|
+
credentials.save_token("alk_one", repo_full_names=["acme/mvp", "Acme/Docs"])
|
|
32
30
|
assert credentials.resolve_token(repo, "ACME/MVP") == "alk_one"
|
|
33
31
|
|
|
34
32
|
|
|
35
33
|
def test_resolve_unknown_repo_returns_none(repo: Path, tmp_path: Path) -> None:
|
|
36
|
-
credentials.save_token("alk_one",
|
|
34
|
+
credentials.save_token("alk_one", repo_full_names=["acme/mvp"])
|
|
37
35
|
other = tmp_path / "other"
|
|
38
36
|
other.mkdir()
|
|
39
37
|
assert credentials.resolve_token(other) is None
|
|
@@ -44,28 +42,25 @@ def test_resolve_none_repo_root() -> None:
|
|
|
44
42
|
|
|
45
43
|
|
|
46
44
|
def test_save_writes_file_mode_0600(repo: Path) -> None:
|
|
47
|
-
path = credentials.save_token("alk_one"
|
|
45
|
+
path = credentials.save_token("alk_one")
|
|
48
46
|
mode = stat.S_IMODE(path.stat().st_mode)
|
|
49
47
|
assert mode == 0o600
|
|
50
48
|
|
|
51
49
|
|
|
52
50
|
def test_store_shape(repo: Path) -> None:
|
|
53
|
-
path = credentials.save_token(
|
|
54
|
-
"alk_one", repo_root=repo, repo_full_names=["Acme/MVP", "acme/mvp"]
|
|
55
|
-
)
|
|
51
|
+
path = credentials.save_token("alk_one", repo_full_names=["Acme/MVP", "acme/mvp"])
|
|
56
52
|
data = json.loads(path.read_text())
|
|
57
53
|
assert data["version"] == credentials.CREDENTIALS_VERSION
|
|
58
54
|
assert data["tokens"]["alk_one"] == ["acme/mvp"]
|
|
59
55
|
assert data["repo_index"]["acme/mvp"] == "alk_one"
|
|
60
|
-
assert
|
|
56
|
+
assert "repos" not in data
|
|
61
57
|
|
|
62
58
|
|
|
63
59
|
def test_save_is_idempotent_overwrite(repo: Path) -> None:
|
|
64
|
-
credentials.save_token("alk_one",
|
|
65
|
-
credentials.save_token("alk_two",
|
|
60
|
+
credentials.save_token("alk_one", repo_full_names=["acme/mvp"])
|
|
61
|
+
credentials.save_token("alk_two", repo_full_names=["acme/mvp"])
|
|
66
62
|
assert credentials.resolve_token(repo, "acme/mvp") == "alk_two"
|
|
67
63
|
store = credentials.load_store()
|
|
68
|
-
assert len(store["repos"]) == 1
|
|
69
64
|
assert len(store["tokens"]) == 1
|
|
70
65
|
assert store["repo_index"] == {"acme/mvp": "alk_two"}
|
|
71
66
|
|
|
@@ -74,8 +69,8 @@ def test_distinct_repos_get_distinct_entries(tmp_path: Path) -> None:
|
|
|
74
69
|
a, b = tmp_path / "a", tmp_path / "b"
|
|
75
70
|
a.mkdir()
|
|
76
71
|
b.mkdir()
|
|
77
|
-
credentials.save_token("alk_a",
|
|
78
|
-
credentials.save_token("alk_b",
|
|
72
|
+
credentials.save_token("alk_a", repo_full_names=["acme/a"])
|
|
73
|
+
credentials.save_token("alk_b", repo_full_names=["acme/b"])
|
|
79
74
|
assert credentials.resolve_token(a, "acme/a") == "alk_a"
|
|
80
75
|
assert credentials.resolve_token(b, "acme/b") == "alk_b"
|
|
81
76
|
|
|
@@ -85,7 +80,6 @@ def test_load_store_missing_file_is_empty() -> None:
|
|
|
85
80
|
"version": credentials.CREDENTIALS_VERSION,
|
|
86
81
|
"tokens": {},
|
|
87
82
|
"repo_index": {},
|
|
88
|
-
"repos": {},
|
|
89
83
|
}
|
|
90
84
|
|
|
91
85
|
|
|
@@ -97,20 +91,19 @@ def test_load_store_tolerates_corrupt_file(_isolate_home: Path) -> None:
|
|
|
97
91
|
"version": credentials.CREDENTIALS_VERSION,
|
|
98
92
|
"tokens": {},
|
|
99
93
|
"repo_index": {},
|
|
100
|
-
"repos": {},
|
|
101
94
|
}
|
|
102
95
|
|
|
103
96
|
|
|
104
97
|
def test_remove_token(repo: Path) -> None:
|
|
105
|
-
credentials.save_token("alk_one",
|
|
106
|
-
assert credentials.remove_token(
|
|
98
|
+
credentials.save_token("alk_one", repo_full_names=["acme/mvp"])
|
|
99
|
+
assert credentials.remove_token(repo_full_name="acme/mvp") is True
|
|
107
100
|
assert credentials.resolve_token(repo, "acme/mvp") is None
|
|
108
101
|
# Removing again is a no-op.
|
|
109
|
-
assert credentials.remove_token(
|
|
102
|
+
assert credentials.remove_token() is False
|
|
110
103
|
|
|
111
104
|
|
|
112
105
|
def test_describe_source_repo_entry(repo: Path) -> None:
|
|
113
|
-
credentials.save_token("alk_one",
|
|
106
|
+
credentials.save_token("alk_one", repo_full_names=["acme/mvp"])
|
|
114
107
|
assert credentials.describe_source(repo, "acme/mvp") == (
|
|
115
108
|
"the saved token for acme/mvp"
|
|
116
109
|
)
|
|
@@ -128,15 +121,17 @@ def test_describe_source_repo_entry_wins_over_env(
|
|
|
128
121
|
repo: Path, monkeypatch: pytest.MonkeyPatch
|
|
129
122
|
) -> None:
|
|
130
123
|
monkeypatch.setenv(credentials.API_TOKEN_ENV, "alk_env")
|
|
131
|
-
credentials.save_token("alk_one",
|
|
132
|
-
assert credentials.describe_source(repo) ==
|
|
124
|
+
credentials.save_token("alk_one", repo_full_names=["acme/mvp"])
|
|
125
|
+
assert credentials.describe_source(repo, "acme/mvp") == (
|
|
126
|
+
"the saved token for acme/mvp"
|
|
127
|
+
)
|
|
133
128
|
|
|
134
129
|
|
|
135
130
|
def test_describe_source_none(repo: Path) -> None:
|
|
136
131
|
assert credentials.describe_source(repo) is None
|
|
137
132
|
|
|
138
133
|
|
|
139
|
-
def
|
|
134
|
+
def test_load_store_drops_legacy_path_store(repo: Path) -> None:
|
|
140
135
|
path = credentials.credentials_path()
|
|
141
136
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
142
137
|
path.write_text(
|
|
@@ -144,7 +139,9 @@ def test_resolve_legacy_path_store(repo: Path) -> None:
|
|
|
144
139
|
encoding="utf-8",
|
|
145
140
|
)
|
|
146
141
|
|
|
147
|
-
|
|
142
|
+
store = credentials.load_store()
|
|
143
|
+
assert "repos" not in store
|
|
144
|
+
assert credentials.resolve_token(repo, "acme/mvp") is None
|
|
148
145
|
|
|
149
146
|
|
|
150
147
|
def test_load_store_builds_repo_index_from_token_only_v2_store() -> None:
|
|
@@ -162,26 +159,26 @@ def test_load_store_builds_repo_index_from_token_only_v2_store() -> None:
|
|
|
162
159
|
def test_resolve_repo_name_after_reclone(repo: Path, tmp_path: Path) -> None:
|
|
163
160
|
moved = tmp_path / "moved"
|
|
164
161
|
moved.mkdir()
|
|
165
|
-
credentials.save_token("alk_one",
|
|
162
|
+
credentials.save_token("alk_one", repo_full_names=["acme/mvp"])
|
|
166
163
|
|
|
167
164
|
assert credentials.resolve_token(moved, "acme/mvp") == "alk_one"
|
|
168
165
|
|
|
169
166
|
|
|
170
167
|
def test_remove_token_keeps_other_repo_names(repo: Path) -> None:
|
|
171
168
|
credentials.save_token(
|
|
172
|
-
"alk_one",
|
|
169
|
+
"alk_one", repo_full_names=["acme/mvp", "acme/docs"]
|
|
173
170
|
)
|
|
174
171
|
|
|
175
|
-
assert credentials.remove_token(
|
|
172
|
+
assert credentials.remove_token(repo_full_name="acme/mvp") is True
|
|
176
173
|
assert credentials.resolve_token(None, "acme/mvp") is None
|
|
177
174
|
assert credentials.resolve_token(None, "acme/docs") == "alk_one"
|
|
178
175
|
|
|
179
176
|
|
|
180
177
|
def test_resaving_same_token_removes_stale_repo_index_entries(repo: Path) -> None:
|
|
181
178
|
credentials.save_token(
|
|
182
|
-
"alk_one",
|
|
179
|
+
"alk_one", repo_full_names=["acme/mvp", "acme/docs"]
|
|
183
180
|
)
|
|
184
|
-
credentials.save_token("alk_one",
|
|
181
|
+
credentials.save_token("alk_one", repo_full_names=["acme/mvp"])
|
|
185
182
|
|
|
186
183
|
store = credentials.load_store()
|
|
187
184
|
assert store["tokens"]["alk_one"] == ["acme/mvp"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|