Tests and docs for v2 introspection commands and alias features

Add test_commands_v2.py with 28 tests covering databases, tables,
plugins, schema, default_query, upsert, auth status, actor, and
get commands.

Add test_alias_v2.py with 5 tests for alias default, alias default-db,
and removing the default alias.

Update aliases.md and authentication.md docs for v2 API.

refs #29

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Simon Willison 2026-02-24 15:21:58 -08:00
commit 92c0d2a5ba
4 changed files with 812 additions and 36 deletions

View file

@ -1,17 +1,40 @@
# Aliases
You can assign an alias to a Datasette database using the `dclient alias` command:
You can assign an alias to a Datasette instance using the `dclient alias add` command:
dclient alias add content https://datasette.io/content
dclient alias add latest https://latest.datasette.io
You can list aliases with `dclient alias list`:
$ dclient alias list
content = https://datasette.io/content
latest = https://latest.datasette.io
Once registered, you can pass an alias to commands such as `dclient query`:
Once registered, you can pass an alias to commands using the `-i` flag:
dclient query content "select * from news limit 1"
dclient query fixtures "select * from news limit 1" -i latest
## Default instance
Set a default instance so you don't need `-i` every time:
dclient alias default latest
Now commands will use `latest` automatically:
dclient databases
dclient tables -d fixtures
## Default database
Set a default database for an alias:
dclient alias default-db latest fixtures
Now you can run bare SQL queries directly:
dclient "select * from facetable limit 5"
This uses the default instance and default database.
## dclient alias --help
<!-- [[[cog
@ -34,9 +57,11 @@ Options:
--help Show this message and exit.
Commands:
add Add an alias
list List aliases
remove Remove an alias
add Add an alias for a Datasette instance
default Set or show the default instance
default-db Set or show the default database for an alias
list List aliases
remove Remove an alias
```
<!-- [[[end]]] -->
@ -56,10 +81,6 @@ Usage: dclient alias list [OPTIONS]
List aliases
Example usage:
dclient aliases list
Options:
--json Output raw JSON
--help Show this message and exit.
@ -80,15 +101,11 @@ cog.out(
```
Usage: dclient alias add [OPTIONS] NAME URL
Add an alias
Add an alias for a Datasette instance
Example usage:
dclient alias add content https://datasette.io/content
Then:
dclient query content 'select * from news limit 3'
dclient alias add prod https://myapp.datasette.cloud
Options:
--help Show this message and exit.
@ -113,10 +130,66 @@ Usage: dclient alias remove [OPTIONS] NAME
Example usage:
dclient alias remove content
dclient alias remove prod
Options:
--help Show this message and exit.
```
<!-- [[[end]]] -->
## dclient alias default --help
<!-- [[[cog
import cog
result = runner.invoke(cli.cli, ["alias", "default", "--help"])
help = result.output.replace("Usage: cli", "Usage: dclient")
cog.out(
"```\n{}\n```".format(help)
)
]]] -->
```
Usage: dclient alias default [OPTIONS] [NAME]
Set or show the default instance
Example usage:
dclient alias default prod
dclient alias default
dclient alias default --clear
Options:
--clear Clear default instance
--help Show this message and exit.
```
<!-- [[[end]]] -->
## dclient alias default-db --help
<!-- [[[cog
import cog
result = runner.invoke(cli.cli, ["alias", "default-db", "--help"])
help = result.output.replace("Usage: cli", "Usage: dclient")
cog.out(
"```\n{}\n```".format(help)
)
]]] -->
```
Usage: dclient alias default-db [OPTIONS] ALIAS_NAME [DB]
Set or show the default database for an alias
Example usage:
dclient alias default-db prod main
dclient alias default-db prod
dclient alias default-db prod --clear
Options:
--clear Clear default database for this alias
--help Show this message and exit.
```
<!-- [[[end]]] -->

View file

@ -4,35 +4,57 @@
`dclient` can handle API tokens for Datasette instances that require authentication.
You can pass an API token to `query` using `-t/--token` like this:
You can pass an API token to any command using `--token` like this:
```bash
dclient query https://latest.datasette.io/fixtures "select * from facetable" -t dstok_mytoken
dclient query fixtures "select * from facetable" --token dstok_mytoken -i latest
```
A more convenient way to handle this is to store tokens to be used with different URL prefixes.
A more convenient way to handle this is to store tokens for your aliases.
## Using stored tokens
To always use `dstok_mytoken` for any URL on the `https://latest.datasette.io/` instance you can run this:
To store a token for an alias:
```bash
dclient auth add https://latest.datasette.io/
dclient auth add latest
```
Then paste in the token and hit enter when prompted to do so.
To list which URLs you have set tokens for, run the `auth list` command:
Tokens can also be stored for direct URLs:
```bash
dclient auth add https://latest.datasette.io
```
To list which aliases/URLs you have set tokens for, run the `auth list` command:
```bash
dclient auth list
```
To delete the token for a specific URL, run `auth remove`:
To delete the token for a specific alias or URL, run `auth remove`:
```bash
dclient auth remove https://latest.datasette.io/
dclient auth remove latest
```
## Token resolution order
When making a request, dclient resolves the token in this order:
1. `--token` CLI flag (highest priority)
2. Token stored by alias name in `auth.json`
3. Token stored by URL prefix in `auth.json`
4. `DATASETTE_TOKEN` environment variable (lowest priority)
## Testing a token
The `dclient actor` command can be used to test a token, retrieving the actor that the token represents.
The `dclient auth status` command can be used to verify authentication by calling `/-/actor.json`:
```bash
dclient actor https://latest.datasette.io/content
dclient auth status
dclient auth status -i prod
```
The `dclient actor` command also shows the actor:
```bash
dclient actor
dclient actor -i prod
```
The output looks like this:
```json
@ -68,6 +90,7 @@ Commands:
add Add an authentication token for an alias or URL
list List stored API tokens
remove Remove the API token for an alias or URL
status Verify authentication by calling /-/actor.json
```
<!-- [[[end]]] -->
@ -89,7 +112,8 @@ Usage: dclient auth add [OPTIONS] ALIAS_OR_URL
Example usage:
dclient auth add https://datasette.io/content
dclient auth add prod
dclient auth add https://datasette.io
Paste in the token when prompted.
@ -142,7 +166,7 @@ Usage: dclient auth remove [OPTIONS] ALIAS_OR_URL
Example usage:
dclient auth remove https://datasette.io/content
dclient auth remove prod
Options:
--help Show this message and exit.
@ -150,6 +174,34 @@ Options:
```
<!-- [[[end]]] -->
## dclient auth status --help
<!-- [[[cog
import cog
result = runner.invoke(cli.cli, ["auth", "status", "--help"])
help = result.output.replace("Usage: cli", "Usage: dclient")
cog.out(
"```\n{}\n```".format(help)
)
]]] -->
```
Usage: dclient auth status [OPTIONS]
Verify authentication by calling /-/actor.json
Example usage:
dclient auth status
dclient auth status -i prod
Options:
-i, --instance TEXT Datasette instance URL or alias
--token TEXT API token
--help Show this message and exit.
```
<!-- [[[end]]] -->
## dclient actor --help
<!-- [[[cog
@ -161,17 +213,19 @@ cog.out(
)
]]] -->
```
Usage: dclient actor [OPTIONS] [URL_OR_ALIAS]
Usage: dclient actor [OPTIONS]
Show the actor represented by an API token
Example usage:
dclient actor https://latest.datasette.io/fixtures
dclient actor
dclient actor -i prod
Options:
--token TEXT API token
--help Show this message and exit.
-i, --instance TEXT Datasette instance URL or alias
--token TEXT API token
--help Show this message and exit.
```
<!-- [[[end]]] -->
<!-- [[[end]]] -->

111
tests/test_alias_v2.py Normal file
View file

@ -0,0 +1,111 @@
"""Tests for v2 alias command: default, default-db subcommands."""
from click.testing import CliRunner
from dclient.cli import cli
import json
import pathlib
def test_alias_default_workflow(mocker, tmpdir):
mocker.patch("dclient.cli.get_config_dir", return_value=pathlib.Path(tmpdir))
runner = CliRunner()
# Add an alias
result = runner.invoke(cli, ["alias", "add", "prod", "https://prod.example.com"])
assert result.exit_code == 0
# No default yet
result = runner.invoke(cli, ["alias", "default"])
assert result.exit_code == 0
assert "No default instance set" in result.output
# Set default
result = runner.invoke(cli, ["alias", "default", "prod"])
assert result.exit_code == 0
# Show default
result = runner.invoke(cli, ["alias", "default"])
assert result.exit_code == 0
assert result.output.strip() == "prod"
# List should show * marker
result = runner.invoke(cli, ["alias", "list"])
assert result.exit_code == 0
assert "* prod" in result.output
# Clear default
result = runner.invoke(cli, ["alias", "default", "--clear"])
assert result.exit_code == 0
result = runner.invoke(cli, ["alias", "default"])
assert "No default instance set" in result.output
def test_alias_default_unknown_alias(mocker, tmpdir):
mocker.patch("dclient.cli.get_config_dir", return_value=pathlib.Path(tmpdir))
runner = CliRunner()
result = runner.invoke(cli, ["alias", "default", "nonexistent"])
assert result.exit_code == 1
assert "No such alias" in result.output
def test_alias_default_db_workflow(mocker, tmpdir):
mocker.patch("dclient.cli.get_config_dir", return_value=pathlib.Path(tmpdir))
runner = CliRunner()
# Add an alias
result = runner.invoke(cli, ["alias", "add", "prod", "https://prod.example.com"])
assert result.exit_code == 0
# No default database yet
result = runner.invoke(cli, ["alias", "default-db", "prod"])
assert result.exit_code == 0
assert "No default database set" in result.output
# Set default database
result = runner.invoke(cli, ["alias", "default-db", "prod", "main"])
assert result.exit_code == 0
# Show default database
result = runner.invoke(cli, ["alias", "default-db", "prod"])
assert result.exit_code == 0
assert result.output.strip() == "main"
# List should show db info
result = runner.invoke(cli, ["alias", "list"])
assert "(db: main)" in result.output
# Clear default database
result = runner.invoke(cli, ["alias", "default-db", "prod", "--clear"])
assert result.exit_code == 0
result = runner.invoke(cli, ["alias", "default-db", "prod"])
assert "No default database set" in result.output
def test_alias_default_db_unknown_alias(mocker, tmpdir):
mocker.patch("dclient.cli.get_config_dir", return_value=pathlib.Path(tmpdir))
runner = CliRunner()
result = runner.invoke(cli, ["alias", "default-db", "nonexistent", "main"])
assert result.exit_code == 1
assert "No such alias" in result.output
def test_alias_remove_clears_default(mocker, tmpdir):
"""Removing the default alias also clears default_instance."""
mocker.patch("dclient.cli.get_config_dir", return_value=pathlib.Path(tmpdir))
runner = CliRunner()
runner.invoke(cli, ["alias", "add", "prod", "https://prod.example.com"])
runner.invoke(cli, ["alias", "default", "prod"])
# Verify it's set
config = json.loads((pathlib.Path(tmpdir) / "config.json").read_text())
assert config["default_instance"] == "prod"
# Remove alias
runner.invoke(cli, ["alias", "remove", "prod"])
config = json.loads((pathlib.Path(tmpdir) / "config.json").read_text())
assert config["default_instance"] is None
assert "prod" not in config["instances"]

538
tests/test_commands_v2.py Normal file
View file

@ -0,0 +1,538 @@
"""Tests for v2 commands: databases, tables, plugins, schema, default_query, upsert."""
from click.testing import CliRunner
from dclient.cli import cli
import json
import pathlib
import pytest
# -- databases command --
def test_databases_json(httpx_mock, mocker, tmpdir):
mocker.patch("dclient.cli.get_config_dir", return_value=pathlib.Path(tmpdir))
httpx_mock.add_response(
json={
"databases": [
{"name": "main", "tables_count": 12},
{"name": "extra", "tables_count": 3},
]
},
status_code=200,
)
runner = CliRunner()
result = runner.invoke(
cli, ["databases", "-i", "https://example.com", "--json"]
)
assert result.exit_code == 0
data = json.loads(result.output)
assert len(data) == 2
assert data[0]["name"] == "main"
def test_databases_plain(httpx_mock, mocker, tmpdir):
mocker.patch("dclient.cli.get_config_dir", return_value=pathlib.Path(tmpdir))
httpx_mock.add_response(
json={
"databases": [
{"name": "main", "tables_count": 12},
{"name": "extra", "tables_count": 3},
]
},
status_code=200,
)
runner = CliRunner()
result = runner.invoke(cli, ["databases", "-i", "https://example.com"])
assert result.exit_code == 0
assert "main\n" in result.output
assert "extra\n" in result.output
def test_databases_url(httpx_mock, mocker, tmpdir):
"""databases command hits /.json on the instance."""
mocker.patch("dclient.cli.get_config_dir", return_value=pathlib.Path(tmpdir))
httpx_mock.add_response(
json={"databases": [{"name": "db1"}]},
status_code=200,
)
runner = CliRunner()
result = runner.invoke(cli, ["databases", "-i", "https://example.com"])
assert result.exit_code == 0
request = httpx_mock.get_request()
assert request.url.path == "/.json"
# -- tables command --
def test_tables_json(httpx_mock, mocker, tmpdir):
mocker.patch("dclient.cli.get_config_dir", return_value=pathlib.Path(tmpdir))
httpx_mock.add_response(
json={
"tables": [
{"name": "facetable", "count": 15, "hidden": False},
{"name": "facet_cities", "count": 4, "hidden": False},
],
"views": [],
},
status_code=200,
)
runner = CliRunner()
result = runner.invoke(
cli,
["tables", "-i", "https://example.com", "-d", "fixtures", "--json"],
)
assert result.exit_code == 0
data = json.loads(result.output)
assert len(data) == 2
def test_tables_plain(httpx_mock, mocker, tmpdir):
mocker.patch("dclient.cli.get_config_dir", return_value=pathlib.Path(tmpdir))
httpx_mock.add_response(
json={
"tables": [
{"name": "facetable", "count": 15, "hidden": False},
],
"views": [],
},
status_code=200,
)
runner = CliRunner()
result = runner.invoke(
cli, ["tables", "-i", "https://example.com", "-d", "fixtures"]
)
assert result.exit_code == 0
assert "facetable" in result.output
assert "15 rows" in result.output
def test_tables_url(httpx_mock, mocker, tmpdir):
"""tables command hits /<database>.json."""
mocker.patch("dclient.cli.get_config_dir", return_value=pathlib.Path(tmpdir))
httpx_mock.add_response(
json={"tables": [], "views": []},
status_code=200,
)
runner = CliRunner()
result = runner.invoke(
cli, ["tables", "-i", "https://example.com", "-d", "fixtures"]
)
assert result.exit_code == 0
request = httpx_mock.get_request()
assert request.url.path == "/fixtures.json"
def test_tables_with_views(httpx_mock, mocker, tmpdir):
mocker.patch("dclient.cli.get_config_dir", return_value=pathlib.Path(tmpdir))
httpx_mock.add_response(
json={
"tables": [{"name": "t1", "count": 5, "hidden": False}],
"views": [{"name": "v1"}],
},
status_code=200,
)
runner = CliRunner()
result = runner.invoke(
cli, ["tables", "-i", "https://example.com", "-d", "db", "--views"]
)
assert result.exit_code == 0
assert "t1" in result.output
assert "v1" in result.output
def test_tables_views_only(httpx_mock, mocker, tmpdir):
mocker.patch("dclient.cli.get_config_dir", return_value=pathlib.Path(tmpdir))
httpx_mock.add_response(
json={
"tables": [{"name": "t1", "count": 5, "hidden": False}],
"views": [{"name": "v1"}],
},
status_code=200,
)
runner = CliRunner()
result = runner.invoke(
cli,
["tables", "-i", "https://example.com", "-d", "db", "--views-only"],
)
assert result.exit_code == 0
assert "t1" not in result.output
assert "v1" in result.output
def test_tables_hidden(httpx_mock, mocker, tmpdir):
mocker.patch("dclient.cli.get_config_dir", return_value=pathlib.Path(tmpdir))
httpx_mock.add_response(
json={
"tables": [
{"name": "visible", "count": 5, "hidden": False},
{"name": "hidden_t", "count": 2, "hidden": True},
],
"views": [],
},
status_code=200,
)
runner = CliRunner()
# Without --hidden
result = runner.invoke(
cli, ["tables", "-i", "https://example.com", "-d", "db"]
)
assert "visible" in result.output
assert "hidden_t" not in result.output
# With --hidden
httpx_mock.add_response(
json={
"tables": [
{"name": "visible", "count": 5, "hidden": False},
{"name": "hidden_t", "count": 2, "hidden": True},
],
"views": [],
},
status_code=200,
)
result = runner.invoke(
cli,
["tables", "-i", "https://example.com", "-d", "db", "--hidden"],
)
assert "visible" in result.output
assert "hidden_t" in result.output
# -- plugins command --
def test_plugins_json(httpx_mock, mocker, tmpdir):
mocker.patch("dclient.cli.get_config_dir", return_value=pathlib.Path(tmpdir))
httpx_mock.add_response(
json=[
{"name": "datasette-files", "version": "0.3.1"},
{"name": "datasette-auth-tokens", "version": "0.4"},
],
status_code=200,
)
runner = CliRunner()
result = runner.invoke(
cli, ["plugins", "-i", "https://example.com", "--json"]
)
assert result.exit_code == 0
data = json.loads(result.output)
assert len(data) == 2
def test_plugins_plain(httpx_mock, mocker, tmpdir):
mocker.patch("dclient.cli.get_config_dir", return_value=pathlib.Path(tmpdir))
httpx_mock.add_response(
json=[
{"name": "datasette-files", "version": "0.3.1"},
{"name": "datasette-auth-tokens", "version": "0.4"},
],
status_code=200,
)
runner = CliRunner()
result = runner.invoke(cli, ["plugins", "-i", "https://example.com"])
assert result.exit_code == 0
assert "datasette-files\n" in result.output
assert "datasette-auth-tokens\n" in result.output
def test_plugins_url(httpx_mock, mocker, tmpdir):
"""plugins command hits /-/plugins.json."""
mocker.patch("dclient.cli.get_config_dir", return_value=pathlib.Path(tmpdir))
httpx_mock.add_response(json=[], status_code=200)
runner = CliRunner()
result = runner.invoke(cli, ["plugins", "-i", "https://example.com"])
assert result.exit_code == 0
request = httpx_mock.get_request()
assert request.url.path == "/-/plugins.json"
# -- schema command --
def test_schema_all_tables(httpx_mock, mocker, tmpdir):
mocker.patch("dclient.cli.get_config_dir", return_value=pathlib.Path(tmpdir))
full_schema = (
"CREATE TABLE users (id integer primary key, name text);\n"
"CREATE VIEW user_count AS SELECT count(*) FROM users;"
)
httpx_mock.add_response(
json={"database": "main", "schema": full_schema},
status_code=200,
)
runner = CliRunner()
result = runner.invoke(
cli, ["schema", "-i", "https://example.com", "-d", "main"]
)
assert result.exit_code == 0
assert "CREATE TABLE users" in result.output
assert "CREATE VIEW user_count" in result.output
request = httpx_mock.get_request()
assert request.url.path == "/main/-/schema.json"
def test_schema_specific_table(httpx_mock, mocker, tmpdir):
mocker.patch("dclient.cli.get_config_dir", return_value=pathlib.Path(tmpdir))
httpx_mock.add_response(
json={
"database": "main",
"table": "users",
"schema": "CREATE TABLE users (id integer primary key, name text)",
},
status_code=200,
)
runner = CliRunner()
result = runner.invoke(
cli, ["schema", "users", "-i", "https://example.com", "-d", "main"]
)
assert result.exit_code == 0
assert "CREATE TABLE users" in result.output
request = httpx_mock.get_request()
assert request.url.path == "/main/users/-/schema.json"
def test_schema_json(httpx_mock, mocker, tmpdir):
mocker.patch("dclient.cli.get_config_dir", return_value=pathlib.Path(tmpdir))
full_schema = "CREATE TABLE users (id integer primary key);"
httpx_mock.add_response(
json={"database": "main", "schema": full_schema},
status_code=200,
)
runner = CliRunner()
result = runner.invoke(
cli,
["schema", "-i", "https://example.com", "-d", "main", "--json"],
)
assert result.exit_code == 0
data = json.loads(result.output)
assert "schema" in data
# -- default_query (bare SQL shortcut) --
def test_default_query_with_defaults(httpx_mock, mocker, tmpdir):
"""Bare SQL uses default instance + default database."""
mocker.patch("dclient.cli.get_config_dir", return_value=pathlib.Path(tmpdir))
config_file = pathlib.Path(tmpdir) / "config.json"
config_file.write_text(
json.dumps(
{
"default_instance": "prod",
"instances": {
"prod": {
"url": "https://prod.example.com",
"default_database": "main",
}
},
}
)
)
httpx_mock.add_response(
json={
"ok": True,
"rows": [{"count(*)": 42}],
"columns": ["count(*)"],
},
status_code=200,
)
runner = CliRunner()
result = runner.invoke(cli, ["select count(*) from users"])
assert result.exit_code == 0
assert json.loads(result.output) == [{"count(*)": 42}]
request = httpx_mock.get_request()
assert request.url.host == "prod.example.com"
assert request.url.path == "/main.json"
def test_default_query_with_database_override(httpx_mock, mocker, tmpdir):
"""Bare SQL with -d flag overrides the database."""
mocker.patch("dclient.cli.get_config_dir", return_value=pathlib.Path(tmpdir))
config_file = pathlib.Path(tmpdir) / "config.json"
config_file.write_text(
json.dumps(
{
"default_instance": "prod",
"instances": {
"prod": {
"url": "https://prod.example.com",
"default_database": "main",
}
},
}
)
)
httpx_mock.add_response(
json={
"ok": True,
"rows": [{"count(*)": 100}],
"columns": ["count(*)"],
},
status_code=200,
)
runner = CliRunner()
result = runner.invoke(
cli, ["select count(*) from events", "-d", "analytics"]
)
assert result.exit_code == 0
request = httpx_mock.get_request()
assert request.url.path == "/analytics.json"
def test_default_query_with_instance_override(httpx_mock, mocker, tmpdir):
"""Bare SQL with -i flag overrides the instance."""
mocker.patch("dclient.cli.get_config_dir", return_value=pathlib.Path(tmpdir))
config_file = pathlib.Path(tmpdir) / "config.json"
config_file.write_text(
json.dumps(
{
"default_instance": "prod",
"instances": {
"prod": {
"url": "https://prod.example.com",
"default_database": "main",
},
"staging": {
"url": "https://staging.example.com",
"default_database": "main",
},
},
}
)
)
httpx_mock.add_response(
json={
"ok": True,
"rows": [{"count(*)": 5}],
"columns": ["count(*)"],
},
status_code=200,
)
runner = CliRunner()
result = runner.invoke(
cli, ["select count(*) from users", "-i", "staging"]
)
assert result.exit_code == 0
request = httpx_mock.get_request()
assert request.url.host == "staging.example.com"
def test_default_query_no_database_error(mocker, tmpdir):
"""Bare SQL without a default database gives an error."""
mocker.patch("dclient.cli.get_config_dir", return_value=pathlib.Path(tmpdir))
config_file = pathlib.Path(tmpdir) / "config.json"
config_file.write_text(
json.dumps(
{
"default_instance": "prod",
"instances": {
"prod": {
"url": "https://prod.example.com",
"default_database": None,
}
},
}
)
)
runner = CliRunner()
result = runner.invoke(cli, ["select 1"])
assert result.exit_code == 1
assert "No database specified" in result.output
# -- upsert command --
def test_upsert_mocked(httpx_mock, tmpdir, mocker):
mocker.patch("dclient.cli.get_config_dir", return_value=pathlib.Path(tmpdir))
httpx_mock.add_response(
json={
"ok": True,
}
)
path = pathlib.Path(tmpdir) / "data.csv"
path.write_text("a,b,c\n1,2,3\n")
runner = CliRunner()
result = runner.invoke(
cli,
[
"upsert",
"data",
"table1",
str(path),
"--csv",
"--token",
"x",
"-i",
"https://datasette.example.com",
],
catch_exceptions=False,
)
assert result.exit_code == 0
request = httpx_mock.get_request()
assert request.headers["authorization"] == "Bearer x"
# Should hit /-/upsert endpoint
assert "/table1/-/upsert" in str(request.url)
assert json.loads(request.read()) == {"rows": [{"a": 1, "b": 2, "c": 3}]}
# -- auth status command --
def test_auth_status(httpx_mock, mocker, tmpdir):
mocker.patch("dclient.cli.get_config_dir", return_value=pathlib.Path(tmpdir))
httpx_mock.add_response(
json={"actor": {"id": "root"}},
status_code=200,
)
runner = CliRunner()
result = runner.invoke(
cli, ["auth", "status", "-i", "https://example.com", "--token", "tok"]
)
assert result.exit_code == 0
data = json.loads(result.output)
assert data["actor"]["id"] == "root"
request = httpx_mock.get_request()
assert request.url.path == "/-/actor.json"
# -- actor command --
def test_actor_with_instance_flag(httpx_mock, mocker, tmpdir):
mocker.patch("dclient.cli.get_config_dir", return_value=pathlib.Path(tmpdir))
httpx_mock.add_response(
json={"actor": {"id": "root"}},
status_code=200,
)
runner = CliRunner()
result = runner.invoke(
cli, ["actor", "-i", "https://example.com", "--token", "tok"]
)
assert result.exit_code == 0
data = json.loads(result.output)
assert data["actor"]["id"] == "root"
request = httpx_mock.get_request()
assert request.url.path == "/-/actor.json"
assert request.headers["authorization"] == "Bearer tok"
# -- get command --
def test_get_command(httpx_mock, mocker, tmpdir):
mocker.patch("dclient.cli.get_config_dir", return_value=pathlib.Path(tmpdir))
httpx_mock.add_response(
json={"hello": "world"},
status_code=200,
)
runner = CliRunner()
result = runner.invoke(
cli, ["get", "/-/plugins.json", "-i", "https://example.com"]
)
assert result.exit_code == 0
data = json.loads(result.output)
assert data == {"hello": "world"}
request = httpx_mock.get_request()
assert request.url.path == "/-/plugins.json"