colacloud-cli 0.2.0__tar.gz → 0.3.1__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.
- {colacloud_cli-0.2.0 → colacloud_cli-0.3.1}/PKG-INFO +3 -3
- {colacloud_cli-0.2.0 → colacloud_cli-0.3.1}/README.md +1 -1
- {colacloud_cli-0.2.0 → colacloud_cli-0.3.1}/pyproject.toml +2 -2
- {colacloud_cli-0.2.0 → colacloud_cli-0.3.1}/src/colacloud_cli/api.py +157 -0
- colacloud_cli-0.3.1/src/colacloud_cli/commands/avas.py +91 -0
- colacloud_cli-0.3.1/src/colacloud_cli/commands/processing_times.py +153 -0
- colacloud_cli-0.3.1/src/colacloud_cli/commands/production_reports.py +89 -0
- {colacloud_cli-0.2.0 → colacloud_cli-0.3.1}/src/colacloud_cli/formatters.py +16 -14
- {colacloud_cli-0.2.0 → colacloud_cli-0.3.1}/src/colacloud_cli/main.py +10 -1
- {colacloud_cli-0.2.0 → colacloud_cli-0.3.1}/tests/test_api.py +214 -0
- {colacloud_cli-0.2.0 → colacloud_cli-0.3.1}/tests/test_cli.py +269 -0
- {colacloud_cli-0.2.0 → colacloud_cli-0.3.1}/.gitignore +0 -0
- {colacloud_cli-0.2.0 → colacloud_cli-0.3.1}/.mcp.json +0 -0
- {colacloud_cli-0.2.0 → colacloud_cli-0.3.1}/LICENSE +0 -0
- {colacloud_cli-0.2.0 → colacloud_cli-0.3.1}/scripts/smoke_test.py +0 -0
- {colacloud_cli-0.2.0 → colacloud_cli-0.3.1}/src/colacloud_cli/__init__.py +0 -0
- {colacloud_cli-0.2.0 → colacloud_cli-0.3.1}/src/colacloud_cli/commands/__init__.py +0 -0
- {colacloud_cli-0.2.0 → colacloud_cli-0.3.1}/src/colacloud_cli/commands/barcode.py +0 -0
- {colacloud_cli-0.2.0 → colacloud_cli-0.3.1}/src/colacloud_cli/commands/colas.py +0 -0
- {colacloud_cli-0.2.0 → colacloud_cli-0.3.1}/src/colacloud_cli/commands/config.py +0 -0
- {colacloud_cli-0.2.0 → colacloud_cli-0.3.1}/src/colacloud_cli/commands/permittees.py +0 -0
- {colacloud_cli-0.2.0 → colacloud_cli-0.3.1}/src/colacloud_cli/commands/usage.py +0 -0
- {colacloud_cli-0.2.0 → colacloud_cli-0.3.1}/src/colacloud_cli/commands/utils.py +0 -0
- {colacloud_cli-0.2.0 → colacloud_cli-0.3.1}/src/colacloud_cli/config.py +0 -0
- {colacloud_cli-0.2.0 → colacloud_cli-0.3.1}/tests/__init__.py +0 -0
- {colacloud_cli-0.2.0 → colacloud_cli-0.3.1}/tests/test_config.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: colacloud-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Command-line interface for the COLA Cloud API
|
|
5
5
|
Project-URL: Homepage, https://colacloud.us
|
|
6
|
-
Project-URL: Documentation, https://colacloud.us/
|
|
6
|
+
Project-URL: Documentation, https://docs.colacloud.us/api-reference
|
|
7
7
|
Project-URL: Repository, https://github.com/cola-cloud-us/colacloud-cli
|
|
8
8
|
Author-email: Jay Sobel <jay@colacloud.us>
|
|
9
9
|
License: MIT
|
|
@@ -300,5 +300,5 @@ MIT License - see [LICENSE](LICENSE) for details.
|
|
|
300
300
|
## Links
|
|
301
301
|
|
|
302
302
|
- [COLA Cloud Website](https://colacloud.us)
|
|
303
|
-
- [API Documentation](https://colacloud.us/
|
|
303
|
+
- [API Documentation](https://docs.colacloud.us/api-reference)
|
|
304
304
|
- [GitHub Repository](https://github.com/cola-cloud-us/colacloud-cli)
|
|
@@ -273,5 +273,5 @@ MIT License - see [LICENSE](LICENSE) for details.
|
|
|
273
273
|
## Links
|
|
274
274
|
|
|
275
275
|
- [COLA Cloud Website](https://colacloud.us)
|
|
276
|
-
- [API Documentation](https://colacloud.us/
|
|
276
|
+
- [API Documentation](https://docs.colacloud.us/api-reference)
|
|
277
277
|
- [GitHub Repository](https://github.com/cola-cloud-us/colacloud-cli)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "colacloud-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.1"
|
|
4
4
|
description = "Command-line interface for the COLA Cloud API"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = { text = "MIT" }
|
|
@@ -33,7 +33,7 @@ cola = "colacloud_cli.main:cli"
|
|
|
33
33
|
|
|
34
34
|
[project.urls]
|
|
35
35
|
Homepage = "https://colacloud.us"
|
|
36
|
-
Documentation = "https://colacloud.us/
|
|
36
|
+
Documentation = "https://docs.colacloud.us/api-reference"
|
|
37
37
|
Repository = "https://github.com/cola-cloud-us/colacloud-cli"
|
|
38
38
|
|
|
39
39
|
[build-system]
|
|
@@ -311,6 +311,163 @@ class ColaCloudClient:
|
|
|
311
311
|
response = self._client.get(f"/barcode/{barcode_value}")
|
|
312
312
|
return self._handle_response(response)
|
|
313
313
|
|
|
314
|
+
# Processing times endpoints
|
|
315
|
+
|
|
316
|
+
def list_processing_times(
|
|
317
|
+
self,
|
|
318
|
+
commodity: str | None = None,
|
|
319
|
+
) -> dict[str, Any]:
|
|
320
|
+
"""Get COLA processing times.
|
|
321
|
+
|
|
322
|
+
Args:
|
|
323
|
+
commodity: Filter by commodity type.
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
API response with processing times data.
|
|
327
|
+
"""
|
|
328
|
+
self._require_api_key()
|
|
329
|
+
|
|
330
|
+
params: dict[str, Any] = {}
|
|
331
|
+
if commodity:
|
|
332
|
+
params["commodity"] = commodity
|
|
333
|
+
|
|
334
|
+
response = self._client.get("/processing-times", params=params)
|
|
335
|
+
return self._handle_response(response)
|
|
336
|
+
|
|
337
|
+
def list_formula_processing_times(
|
|
338
|
+
self,
|
|
339
|
+
formula_type: str | None = None,
|
|
340
|
+
commodity: str | None = None,
|
|
341
|
+
) -> dict[str, Any]:
|
|
342
|
+
"""Get formula processing times.
|
|
343
|
+
|
|
344
|
+
Args:
|
|
345
|
+
formula_type: Filter by formula type.
|
|
346
|
+
commodity: Filter by commodity type.
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
API response with formula processing times data.
|
|
350
|
+
"""
|
|
351
|
+
self._require_api_key()
|
|
352
|
+
|
|
353
|
+
params: dict[str, Any] = {}
|
|
354
|
+
if formula_type:
|
|
355
|
+
params["formula_type"] = formula_type
|
|
356
|
+
if commodity:
|
|
357
|
+
params["commodity"] = commodity
|
|
358
|
+
|
|
359
|
+
response = self._client.get("/processing-times/formula", params=params)
|
|
360
|
+
return self._handle_response(response)
|
|
361
|
+
|
|
362
|
+
def list_registration_processing_times(
|
|
363
|
+
self,
|
|
364
|
+
category: str | None = None,
|
|
365
|
+
application_type: str | None = None,
|
|
366
|
+
) -> dict[str, Any]:
|
|
367
|
+
"""Get registration processing times.
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
category: Filter by category.
|
|
371
|
+
application_type: Filter by application type.
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
API response with registration processing times data.
|
|
375
|
+
"""
|
|
376
|
+
self._require_api_key()
|
|
377
|
+
|
|
378
|
+
params: dict[str, Any] = {}
|
|
379
|
+
if category:
|
|
380
|
+
params["category"] = category
|
|
381
|
+
if application_type:
|
|
382
|
+
params["application_type"] = application_type
|
|
383
|
+
|
|
384
|
+
response = self._client.get("/processing-times/registration", params=params)
|
|
385
|
+
return self._handle_response(response)
|
|
386
|
+
|
|
387
|
+
# Production reports endpoint
|
|
388
|
+
|
|
389
|
+
def list_production_reports(
|
|
390
|
+
self,
|
|
391
|
+
commodity: str | None = None,
|
|
392
|
+
year: int | None = None,
|
|
393
|
+
month: int | None = None,
|
|
394
|
+
report_type: str | None = None,
|
|
395
|
+
statistical_group: str | None = None,
|
|
396
|
+
page: int = 1,
|
|
397
|
+
per_page: int = 100,
|
|
398
|
+
) -> dict[str, Any]:
|
|
399
|
+
"""Get production reports.
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
commodity: Filter by commodity type.
|
|
403
|
+
year: Filter by year.
|
|
404
|
+
month: Filter by month.
|
|
405
|
+
report_type: Filter by report type.
|
|
406
|
+
statistical_group: Filter by statistical group.
|
|
407
|
+
page: Page number.
|
|
408
|
+
per_page: Results per page (max 100).
|
|
409
|
+
|
|
410
|
+
Returns:
|
|
411
|
+
API response with production reports data.
|
|
412
|
+
"""
|
|
413
|
+
self._require_api_key()
|
|
414
|
+
|
|
415
|
+
params: dict[str, Any] = {"page": page, "per_page": per_page}
|
|
416
|
+
if commodity:
|
|
417
|
+
params["commodity"] = commodity
|
|
418
|
+
if year is not None:
|
|
419
|
+
params["year"] = year
|
|
420
|
+
if month is not None:
|
|
421
|
+
params["month"] = month
|
|
422
|
+
if report_type:
|
|
423
|
+
params["report_type"] = report_type
|
|
424
|
+
if statistical_group:
|
|
425
|
+
params["statistical_group"] = statistical_group
|
|
426
|
+
|
|
427
|
+
response = self._client.get("/production-reports", params=params)
|
|
428
|
+
return self._handle_response(response)
|
|
429
|
+
|
|
430
|
+
# AVA endpoints
|
|
431
|
+
|
|
432
|
+
def list_avas(
|
|
433
|
+
self,
|
|
434
|
+
state: str | None = None,
|
|
435
|
+
query: str | None = None,
|
|
436
|
+
) -> dict[str, Any]:
|
|
437
|
+
"""List American Viticultural Areas (AVAs).
|
|
438
|
+
|
|
439
|
+
Args:
|
|
440
|
+
state: Filter by state.
|
|
441
|
+
query: Search by name.
|
|
442
|
+
|
|
443
|
+
Returns:
|
|
444
|
+
API response with AVA data.
|
|
445
|
+
"""
|
|
446
|
+
self._require_api_key()
|
|
447
|
+
|
|
448
|
+
params: dict[str, Any] = {}
|
|
449
|
+
if state:
|
|
450
|
+
params["state"] = state
|
|
451
|
+
if query:
|
|
452
|
+
params["q"] = query
|
|
453
|
+
|
|
454
|
+
response = self._client.get("/avas", params=params)
|
|
455
|
+
return self._handle_response(response)
|
|
456
|
+
|
|
457
|
+
def get_ava(self, ava_id: str) -> dict[str, Any]:
|
|
458
|
+
"""Get a single AVA by ID.
|
|
459
|
+
|
|
460
|
+
Args:
|
|
461
|
+
ava_id: The AVA identifier.
|
|
462
|
+
|
|
463
|
+
Returns:
|
|
464
|
+
API response with AVA details.
|
|
465
|
+
"""
|
|
466
|
+
self._require_api_key()
|
|
467
|
+
|
|
468
|
+
response = self._client.get(f"/avas/{ava_id}")
|
|
469
|
+
return self._handle_response(response)
|
|
470
|
+
|
|
314
471
|
# Usage endpoint
|
|
315
472
|
|
|
316
473
|
def get_usage(self) -> dict[str, Any]:
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""AVA commands for COLA Cloud CLI."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from colacloud_cli.api import APIError, get_client
|
|
8
|
+
from colacloud_cli.commands.utils import console, handle_api_error
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.group(name="avas")
|
|
12
|
+
def avas_group():
|
|
13
|
+
"""Browse American Viticultural Areas (AVAs)."""
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@avas_group.command(name="list")
|
|
18
|
+
@click.option("--state", help="Filter by state (e.g., CA, OR, WA).")
|
|
19
|
+
@click.option("-q", "--query", help="Search by AVA name.")
|
|
20
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON.")
|
|
21
|
+
def list_avas(state: str | None, query: str | None, as_json: bool):
|
|
22
|
+
"""List American Viticultural Areas.
|
|
23
|
+
|
|
24
|
+
Browse and search federally recognized wine grape-growing regions.
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
|
|
28
|
+
\b
|
|
29
|
+
# List all AVAs
|
|
30
|
+
cola avas list
|
|
31
|
+
|
|
32
|
+
\b
|
|
33
|
+
# Filter by state
|
|
34
|
+
cola avas list --state CA
|
|
35
|
+
|
|
36
|
+
\b
|
|
37
|
+
# Search by name
|
|
38
|
+
cola avas list -q "napa"
|
|
39
|
+
"""
|
|
40
|
+
try:
|
|
41
|
+
with get_client() as client:
|
|
42
|
+
result = client.list_avas(state=state, query=query)
|
|
43
|
+
|
|
44
|
+
if as_json:
|
|
45
|
+
click.echo(json.dumps(result, indent=2))
|
|
46
|
+
else:
|
|
47
|
+
data = result.get("data", [])
|
|
48
|
+
meta = result.get("meta", {})
|
|
49
|
+
|
|
50
|
+
if not data:
|
|
51
|
+
console.print("[yellow]No AVAs found matching your criteria.[/]")
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
console.print(json.dumps(data, indent=2))
|
|
55
|
+
total = meta.get("total", len(data))
|
|
56
|
+
console.print(f"\n[dim]{total} result(s)[/]")
|
|
57
|
+
|
|
58
|
+
except APIError as e:
|
|
59
|
+
handle_api_error(e)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@avas_group.command(name="get")
|
|
63
|
+
@click.argument("ava_id")
|
|
64
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON.")
|
|
65
|
+
def get_ava(ava_id: str, as_json: bool):
|
|
66
|
+
"""Get detailed information about a specific AVA.
|
|
67
|
+
|
|
68
|
+
AVA_ID is the unique identifier for the American Viticultural Area.
|
|
69
|
+
|
|
70
|
+
Examples:
|
|
71
|
+
|
|
72
|
+
\b
|
|
73
|
+
# Get AVA details
|
|
74
|
+
cola avas get napa-valley
|
|
75
|
+
|
|
76
|
+
\b
|
|
77
|
+
# Output as JSON
|
|
78
|
+
cola avas get napa-valley --json
|
|
79
|
+
"""
|
|
80
|
+
try:
|
|
81
|
+
with get_client() as client:
|
|
82
|
+
result = client.get_ava(ava_id)
|
|
83
|
+
|
|
84
|
+
if as_json:
|
|
85
|
+
click.echo(json.dumps(result, indent=2))
|
|
86
|
+
else:
|
|
87
|
+
data = result.get("data", {})
|
|
88
|
+
console.print(json.dumps(data, indent=2))
|
|
89
|
+
|
|
90
|
+
except APIError as e:
|
|
91
|
+
handle_api_error(e)
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""Processing times commands for COLA Cloud CLI."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from colacloud_cli.api import APIError, get_client
|
|
8
|
+
from colacloud_cli.commands.utils import console, handle_api_error
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.group(name="processing-times")
|
|
12
|
+
def processing_times_group():
|
|
13
|
+
"""View TTB processing times for COLAs, formulas, and registrations."""
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@processing_times_group.command(name="list")
|
|
18
|
+
@click.option("--commodity", help="Filter by commodity type.")
|
|
19
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON.")
|
|
20
|
+
def list_processing_times(commodity: str | None, as_json: bool):
|
|
21
|
+
"""List COLA processing times.
|
|
22
|
+
|
|
23
|
+
Shows how long it takes TTB to process COLA applications,
|
|
24
|
+
optionally filtered by commodity type.
|
|
25
|
+
|
|
26
|
+
Examples:
|
|
27
|
+
|
|
28
|
+
\b
|
|
29
|
+
# List all processing times
|
|
30
|
+
cola processing-times list
|
|
31
|
+
|
|
32
|
+
\b
|
|
33
|
+
# Filter by commodity
|
|
34
|
+
cola processing-times list --commodity wine
|
|
35
|
+
"""
|
|
36
|
+
try:
|
|
37
|
+
with get_client() as client:
|
|
38
|
+
result = client.list_processing_times(commodity=commodity)
|
|
39
|
+
|
|
40
|
+
if as_json:
|
|
41
|
+
click.echo(json.dumps(result, indent=2))
|
|
42
|
+
else:
|
|
43
|
+
data = result.get("data", [])
|
|
44
|
+
meta = result.get("meta", {})
|
|
45
|
+
|
|
46
|
+
if not data:
|
|
47
|
+
console.print(
|
|
48
|
+
"[yellow]No processing times found matching your criteria.[/]"
|
|
49
|
+
)
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
console.print(json.dumps(data, indent=2))
|
|
53
|
+
total = meta.get("total", len(data))
|
|
54
|
+
console.print(f"\n[dim]{total} result(s)[/]")
|
|
55
|
+
|
|
56
|
+
except APIError as e:
|
|
57
|
+
handle_api_error(e)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@processing_times_group.command(name="formula")
|
|
61
|
+
@click.option("--formula-type", help="Filter by formula type.")
|
|
62
|
+
@click.option("--commodity", help="Filter by commodity type.")
|
|
63
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON.")
|
|
64
|
+
def formula_processing_times(
|
|
65
|
+
formula_type: str | None, commodity: str | None, as_json: bool
|
|
66
|
+
):
|
|
67
|
+
"""List formula processing times.
|
|
68
|
+
|
|
69
|
+
Shows how long it takes TTB to process formula applications.
|
|
70
|
+
|
|
71
|
+
Examples:
|
|
72
|
+
|
|
73
|
+
\b
|
|
74
|
+
# List all formula processing times
|
|
75
|
+
cola processing-times formula
|
|
76
|
+
|
|
77
|
+
\b
|
|
78
|
+
# Filter by formula type and commodity
|
|
79
|
+
cola processing-times formula --formula-type domestic --commodity wine
|
|
80
|
+
"""
|
|
81
|
+
try:
|
|
82
|
+
with get_client() as client:
|
|
83
|
+
result = client.list_formula_processing_times(
|
|
84
|
+
formula_type=formula_type, commodity=commodity
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
if as_json:
|
|
88
|
+
click.echo(json.dumps(result, indent=2))
|
|
89
|
+
else:
|
|
90
|
+
data = result.get("data", [])
|
|
91
|
+
meta = result.get("meta", {})
|
|
92
|
+
|
|
93
|
+
if not data:
|
|
94
|
+
console.print(
|
|
95
|
+
"[yellow]No formula processing times found"
|
|
96
|
+
" matching your criteria.[/]"
|
|
97
|
+
)
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
console.print(json.dumps(data, indent=2))
|
|
101
|
+
total = meta.get("total", len(data))
|
|
102
|
+
console.print(f"\n[dim]{total} result(s)[/]")
|
|
103
|
+
|
|
104
|
+
except APIError as e:
|
|
105
|
+
handle_api_error(e)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@processing_times_group.command(name="registration")
|
|
109
|
+
@click.option("--category", help="Filter by category.")
|
|
110
|
+
@click.option("--application-type", help="Filter by application type.")
|
|
111
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON.")
|
|
112
|
+
def registration_processing_times(
|
|
113
|
+
category: str | None, application_type: str | None, as_json: bool
|
|
114
|
+
):
|
|
115
|
+
"""List registration processing times.
|
|
116
|
+
|
|
117
|
+
Shows how long it takes TTB to process registration applications.
|
|
118
|
+
|
|
119
|
+
Examples:
|
|
120
|
+
|
|
121
|
+
\b
|
|
122
|
+
# List all registration processing times
|
|
123
|
+
cola processing-times registration
|
|
124
|
+
|
|
125
|
+
\b
|
|
126
|
+
# Filter by category
|
|
127
|
+
cola processing-times registration --category beverage
|
|
128
|
+
"""
|
|
129
|
+
try:
|
|
130
|
+
with get_client() as client:
|
|
131
|
+
result = client.list_registration_processing_times(
|
|
132
|
+
category=category, application_type=application_type
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
if as_json:
|
|
136
|
+
click.echo(json.dumps(result, indent=2))
|
|
137
|
+
else:
|
|
138
|
+
data = result.get("data", [])
|
|
139
|
+
meta = result.get("meta", {})
|
|
140
|
+
|
|
141
|
+
if not data:
|
|
142
|
+
console.print(
|
|
143
|
+
"[yellow]No registration processing times found"
|
|
144
|
+
" matching your criteria.[/]"
|
|
145
|
+
)
|
|
146
|
+
return
|
|
147
|
+
|
|
148
|
+
console.print(json.dumps(data, indent=2))
|
|
149
|
+
total = meta.get("total", len(data))
|
|
150
|
+
console.print(f"\n[dim]{total} result(s)[/]")
|
|
151
|
+
|
|
152
|
+
except APIError as e:
|
|
153
|
+
handle_api_error(e)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""Production reports commands for COLA Cloud CLI."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
|
|
5
|
+
import click
|
|
6
|
+
|
|
7
|
+
from colacloud_cli.api import APIError, get_client
|
|
8
|
+
from colacloud_cli.commands.utils import console, handle_api_error
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@click.group(name="production-reports")
|
|
12
|
+
def production_reports_group():
|
|
13
|
+
"""View TTB production reports and statistics."""
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@production_reports_group.command(name="list")
|
|
18
|
+
@click.option("--commodity", help="Filter by commodity type.")
|
|
19
|
+
@click.option("--year", type=int, help="Filter by year.")
|
|
20
|
+
@click.option("--month", type=int, help="Filter by month (1-12).")
|
|
21
|
+
@click.option("--report-type", help="Filter by report type.")
|
|
22
|
+
@click.option("--statistical-group", help="Filter by statistical group.")
|
|
23
|
+
@click.option(
|
|
24
|
+
"--limit", "per_page", default=100, type=int, help="Results per page (max 100)."
|
|
25
|
+
)
|
|
26
|
+
@click.option("--page", default=1, type=int, help="Page number.")
|
|
27
|
+
@click.option("--json", "as_json", is_flag=True, help="Output as JSON.")
|
|
28
|
+
def list_production_reports(
|
|
29
|
+
commodity: str | None,
|
|
30
|
+
year: int | None,
|
|
31
|
+
month: int | None,
|
|
32
|
+
report_type: str | None,
|
|
33
|
+
statistical_group: str | None,
|
|
34
|
+
per_page: int,
|
|
35
|
+
page: int,
|
|
36
|
+
as_json: bool,
|
|
37
|
+
):
|
|
38
|
+
"""List production reports.
|
|
39
|
+
|
|
40
|
+
Browse TTB production report data with optional filters.
|
|
41
|
+
|
|
42
|
+
Examples:
|
|
43
|
+
|
|
44
|
+
\b
|
|
45
|
+
# List all production reports
|
|
46
|
+
cola production-reports list
|
|
47
|
+
|
|
48
|
+
\b
|
|
49
|
+
# Filter by commodity and year
|
|
50
|
+
cola production-reports list --commodity wine --year 2024
|
|
51
|
+
|
|
52
|
+
\b
|
|
53
|
+
# Filter by month
|
|
54
|
+
cola production-reports list --year 2024 --month 6
|
|
55
|
+
"""
|
|
56
|
+
try:
|
|
57
|
+
with get_client() as client:
|
|
58
|
+
result = client.list_production_reports(
|
|
59
|
+
commodity=commodity,
|
|
60
|
+
year=year,
|
|
61
|
+
month=month,
|
|
62
|
+
report_type=report_type,
|
|
63
|
+
statistical_group=statistical_group,
|
|
64
|
+
page=page,
|
|
65
|
+
per_page=min(per_page, 100),
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
if as_json:
|
|
69
|
+
click.echo(json.dumps(result, indent=2))
|
|
70
|
+
else:
|
|
71
|
+
data = result.get("data", [])
|
|
72
|
+
meta = result.get("meta", {})
|
|
73
|
+
|
|
74
|
+
if not data:
|
|
75
|
+
console.print(
|
|
76
|
+
"[yellow]No production reports found matching your criteria.[/]"
|
|
77
|
+
)
|
|
78
|
+
return
|
|
79
|
+
|
|
80
|
+
console.print(json.dumps(data, indent=2))
|
|
81
|
+
total = meta.get("total", len(data))
|
|
82
|
+
page_num = meta.get("page", page)
|
|
83
|
+
has_more = meta.get("has_more", False)
|
|
84
|
+
console.print(f"\n[dim]{total} total result(s), page {page_num}[/]")
|
|
85
|
+
if has_more:
|
|
86
|
+
console.print("[dim]More results available. Use --page to paginate.[/]")
|
|
87
|
+
|
|
88
|
+
except APIError as e:
|
|
89
|
+
handle_api_error(e)
|
|
@@ -383,23 +383,25 @@ def format_permittee_detail(permittee: dict[str, Any], console: Console) -> None
|
|
|
383
383
|
console.print("[bold]Company Information[/]")
|
|
384
384
|
console.print(info_table)
|
|
385
385
|
|
|
386
|
-
# COLA stats
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
"Approved COLAs", format_number(permittee.get("colas_approved"))
|
|
394
|
-
)
|
|
395
|
-
if permittee.get("last_cola_application_date"):
|
|
386
|
+
# COLA stats (paid plans only)
|
|
387
|
+
if permittee.get("colas") is not None:
|
|
388
|
+
stats_table = Table(show_header=False, box=None, padding=(0, 2))
|
|
389
|
+
stats_table.add_column("Field", style="dim")
|
|
390
|
+
stats_table.add_column("Value")
|
|
391
|
+
|
|
392
|
+
stats_table.add_row("Total COLAs", format_number(permittee.get("colas")))
|
|
396
393
|
stats_table.add_row(
|
|
397
|
-
"
|
|
394
|
+
"Approved COLAs", format_number(permittee.get("colas_approved"))
|
|
398
395
|
)
|
|
396
|
+
if permittee.get("last_cola_application_date"):
|
|
397
|
+
stats_table.add_row(
|
|
398
|
+
"Last Application",
|
|
399
|
+
format_date(permittee.get("last_cola_application_date")),
|
|
400
|
+
)
|
|
399
401
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
402
|
+
console.print()
|
|
403
|
+
console.print("[bold]COLA Statistics[/]")
|
|
404
|
+
console.print(stats_table)
|
|
403
405
|
|
|
404
406
|
# Recent COLAs
|
|
405
407
|
recent_colas = permittee.get("recent_colas", [])
|
|
@@ -4,10 +4,13 @@ import click
|
|
|
4
4
|
from rich.console import Console
|
|
5
5
|
|
|
6
6
|
from colacloud_cli import __version__
|
|
7
|
+
from colacloud_cli.commands.avas import avas_group
|
|
7
8
|
from colacloud_cli.commands.barcode import barcode_command
|
|
8
9
|
from colacloud_cli.commands.colas import colas_group
|
|
9
10
|
from colacloud_cli.commands.config import config_group
|
|
10
11
|
from colacloud_cli.commands.permittees import permittees_group
|
|
12
|
+
from colacloud_cli.commands.processing_times import processing_times_group
|
|
13
|
+
from colacloud_cli.commands.production_reports import production_reports_group
|
|
11
14
|
from colacloud_cli.commands.usage import usage_command
|
|
12
15
|
|
|
13
16
|
console = Console()
|
|
@@ -29,6 +32,9 @@ class AliasedGroup(click.Group):
|
|
|
29
32
|
"c": "config",
|
|
30
33
|
"b": "barcode",
|
|
31
34
|
"u": "usage",
|
|
35
|
+
"pt": "processing-times",
|
|
36
|
+
"pr": "production-reports",
|
|
37
|
+
"a": "avas",
|
|
32
38
|
}
|
|
33
39
|
|
|
34
40
|
if cmd_name in aliases:
|
|
@@ -76,7 +82,7 @@ def cli(ctx: click.Context) -> None:
|
|
|
76
82
|
cola barcode 012345678901
|
|
77
83
|
cola usage
|
|
78
84
|
|
|
79
|
-
For more information, visit https://colacloud.us
|
|
85
|
+
For more information, visit https://docs.colacloud.us
|
|
80
86
|
"""
|
|
81
87
|
ctx.ensure_object(dict)
|
|
82
88
|
|
|
@@ -87,6 +93,9 @@ cli.add_command(colas_group)
|
|
|
87
93
|
cli.add_command(permittees_group)
|
|
88
94
|
cli.add_command(barcode_command)
|
|
89
95
|
cli.add_command(usage_command)
|
|
96
|
+
cli.add_command(processing_times_group)
|
|
97
|
+
cli.add_command(production_reports_group)
|
|
98
|
+
cli.add_command(avas_group)
|
|
90
99
|
|
|
91
100
|
|
|
92
101
|
def main() -> None:
|
|
@@ -195,6 +195,220 @@ class TestUsage:
|
|
|
195
195
|
assert result["data"]["detail_views"]["used"] == 100
|
|
196
196
|
|
|
197
197
|
|
|
198
|
+
class TestProcessingTimes:
|
|
199
|
+
@respx.mock
|
|
200
|
+
def test_list_processing_times(self, client):
|
|
201
|
+
"""list_processing_times returns results."""
|
|
202
|
+
respx.get("https://test.colacloud.us/api/v1/processing-times").mock(
|
|
203
|
+
return_value=httpx.Response(
|
|
204
|
+
200,
|
|
205
|
+
json={
|
|
206
|
+
"data": [{"commodity": "wine", "avg_days": 30}],
|
|
207
|
+
"meta": {"total": 1},
|
|
208
|
+
},
|
|
209
|
+
)
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
result = client.list_processing_times()
|
|
213
|
+
assert len(result["data"]) == 1
|
|
214
|
+
assert result["data"][0]["commodity"] == "wine"
|
|
215
|
+
|
|
216
|
+
@respx.mock
|
|
217
|
+
def test_list_processing_times_with_commodity(self, client):
|
|
218
|
+
"""list_processing_times passes commodity filter."""
|
|
219
|
+
route = respx.get("https://test.colacloud.us/api/v1/processing-times").mock(
|
|
220
|
+
return_value=httpx.Response(
|
|
221
|
+
200, json={"data": [], "meta": {"total": 0}}
|
|
222
|
+
)
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
client.list_processing_times(commodity="wine")
|
|
226
|
+
request = route.calls[0].request
|
|
227
|
+
assert "commodity=wine" in str(request.url)
|
|
228
|
+
|
|
229
|
+
@respx.mock
|
|
230
|
+
def test_list_formula_processing_times(self, client):
|
|
231
|
+
"""list_formula_processing_times returns results."""
|
|
232
|
+
respx.get(
|
|
233
|
+
"https://test.colacloud.us/api/v1/processing-times/formula"
|
|
234
|
+
).mock(
|
|
235
|
+
return_value=httpx.Response(
|
|
236
|
+
200,
|
|
237
|
+
json={
|
|
238
|
+
"data": [{"formula_type": "domestic", "avg_days": 20}],
|
|
239
|
+
"meta": {"total": 1},
|
|
240
|
+
},
|
|
241
|
+
)
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
result = client.list_formula_processing_times()
|
|
245
|
+
assert len(result["data"]) == 1
|
|
246
|
+
|
|
247
|
+
@respx.mock
|
|
248
|
+
def test_list_formula_processing_times_with_filters(self, client):
|
|
249
|
+
"""list_formula_processing_times passes filters."""
|
|
250
|
+
route = respx.get(
|
|
251
|
+
"https://test.colacloud.us/api/v1/processing-times/formula"
|
|
252
|
+
).mock(
|
|
253
|
+
return_value=httpx.Response(
|
|
254
|
+
200, json={"data": [], "meta": {"total": 0}}
|
|
255
|
+
)
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
client.list_formula_processing_times(
|
|
259
|
+
formula_type="domestic", commodity="wine"
|
|
260
|
+
)
|
|
261
|
+
request = route.calls[0].request
|
|
262
|
+
assert "formula_type=domestic" in str(request.url)
|
|
263
|
+
assert "commodity=wine" in str(request.url)
|
|
264
|
+
|
|
265
|
+
@respx.mock
|
|
266
|
+
def test_list_registration_processing_times(self, client):
|
|
267
|
+
"""list_registration_processing_times returns results."""
|
|
268
|
+
respx.get(
|
|
269
|
+
"https://test.colacloud.us/api/v1/processing-times/registration"
|
|
270
|
+
).mock(
|
|
271
|
+
return_value=httpx.Response(
|
|
272
|
+
200,
|
|
273
|
+
json={
|
|
274
|
+
"data": [{"category": "beverage", "avg_days": 15}],
|
|
275
|
+
"meta": {"total": 1},
|
|
276
|
+
},
|
|
277
|
+
)
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
result = client.list_registration_processing_times()
|
|
281
|
+
assert len(result["data"]) == 1
|
|
282
|
+
|
|
283
|
+
@respx.mock
|
|
284
|
+
def test_list_registration_processing_times_with_filters(self, client):
|
|
285
|
+
"""list_registration_processing_times passes filters."""
|
|
286
|
+
route = respx.get(
|
|
287
|
+
"https://test.colacloud.us/api/v1/processing-times/registration"
|
|
288
|
+
).mock(
|
|
289
|
+
return_value=httpx.Response(
|
|
290
|
+
200, json={"data": [], "meta": {"total": 0}}
|
|
291
|
+
)
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
client.list_registration_processing_times(
|
|
295
|
+
category="beverage", application_type="original"
|
|
296
|
+
)
|
|
297
|
+
request = route.calls[0].request
|
|
298
|
+
assert "category=beverage" in str(request.url)
|
|
299
|
+
assert "application_type=original" in str(request.url)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
class TestProductionReports:
|
|
303
|
+
@respx.mock
|
|
304
|
+
def test_list_production_reports(self, client):
|
|
305
|
+
"""list_production_reports returns results."""
|
|
306
|
+
respx.get("https://test.colacloud.us/api/v1/production-reports").mock(
|
|
307
|
+
return_value=httpx.Response(
|
|
308
|
+
200,
|
|
309
|
+
json={
|
|
310
|
+
"data": [{"commodity": "wine", "year": 2024}],
|
|
311
|
+
"meta": {"total": 1, "page": 1, "per_page": 100, "has_more": False},
|
|
312
|
+
},
|
|
313
|
+
)
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
result = client.list_production_reports()
|
|
317
|
+
assert len(result["data"]) == 1
|
|
318
|
+
assert result["data"][0]["commodity"] == "wine"
|
|
319
|
+
|
|
320
|
+
@respx.mock
|
|
321
|
+
def test_list_production_reports_with_filters(self, client):
|
|
322
|
+
"""list_production_reports passes filter parameters."""
|
|
323
|
+
route = respx.get(
|
|
324
|
+
"https://test.colacloud.us/api/v1/production-reports"
|
|
325
|
+
).mock(
|
|
326
|
+
return_value=httpx.Response(
|
|
327
|
+
200,
|
|
328
|
+
json={
|
|
329
|
+
"data": [],
|
|
330
|
+
"meta": {"total": 0, "page": 1, "per_page": 100, "has_more": False},
|
|
331
|
+
},
|
|
332
|
+
)
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
client.list_production_reports(
|
|
336
|
+
commodity="wine", year=2024, month=6, report_type="monthly"
|
|
337
|
+
)
|
|
338
|
+
request = route.calls[0].request
|
|
339
|
+
assert "commodity=wine" in str(request.url)
|
|
340
|
+
assert "year=2024" in str(request.url)
|
|
341
|
+
assert "month=6" in str(request.url)
|
|
342
|
+
assert "report_type=monthly" in str(request.url)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
class TestAVAs:
|
|
346
|
+
@respx.mock
|
|
347
|
+
def test_list_avas(self, client):
|
|
348
|
+
"""list_avas returns results."""
|
|
349
|
+
respx.get("https://test.colacloud.us/api/v1/avas").mock(
|
|
350
|
+
return_value=httpx.Response(
|
|
351
|
+
200,
|
|
352
|
+
json={
|
|
353
|
+
"data": [{"id": "napa-valley", "name": "Napa Valley"}],
|
|
354
|
+
"meta": {"total": 1},
|
|
355
|
+
},
|
|
356
|
+
)
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
result = client.list_avas()
|
|
360
|
+
assert len(result["data"]) == 1
|
|
361
|
+
assert result["data"][0]["name"] == "Napa Valley"
|
|
362
|
+
|
|
363
|
+
@respx.mock
|
|
364
|
+
def test_list_avas_with_filters(self, client):
|
|
365
|
+
"""list_avas passes filter parameters."""
|
|
366
|
+
route = respx.get("https://test.colacloud.us/api/v1/avas").mock(
|
|
367
|
+
return_value=httpx.Response(
|
|
368
|
+
200, json={"data": [], "meta": {"total": 0}}
|
|
369
|
+
)
|
|
370
|
+
)
|
|
371
|
+
|
|
372
|
+
client.list_avas(state="CA", query="napa")
|
|
373
|
+
request = route.calls[0].request
|
|
374
|
+
assert "state=CA" in str(request.url)
|
|
375
|
+
assert "q=napa" in str(request.url)
|
|
376
|
+
|
|
377
|
+
@respx.mock
|
|
378
|
+
def test_get_ava(self, client):
|
|
379
|
+
"""get_ava returns AVA details."""
|
|
380
|
+
respx.get("https://test.colacloud.us/api/v1/avas/napa-valley").mock(
|
|
381
|
+
return_value=httpx.Response(
|
|
382
|
+
200,
|
|
383
|
+
json={
|
|
384
|
+
"data": {
|
|
385
|
+
"id": "napa-valley",
|
|
386
|
+
"name": "Napa Valley",
|
|
387
|
+
"state": "CA",
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
)
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
result = client.get_ava("napa-valley")
|
|
394
|
+
assert result["data"]["name"] == "Napa Valley"
|
|
395
|
+
|
|
396
|
+
@respx.mock
|
|
397
|
+
def test_get_ava_not_found(self, client):
|
|
398
|
+
"""get_ava raises APIError for 404."""
|
|
399
|
+
respx.get("https://test.colacloud.us/api/v1/avas/nonexistent").mock(
|
|
400
|
+
return_value=httpx.Response(
|
|
401
|
+
404,
|
|
402
|
+
json={"error": {"code": "not_found", "message": "AVA not found"}},
|
|
403
|
+
)
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
with pytest.raises(APIError) as exc_info:
|
|
407
|
+
client.get_ava("nonexistent")
|
|
408
|
+
|
|
409
|
+
assert exc_info.value.status_code == 404
|
|
410
|
+
|
|
411
|
+
|
|
198
412
|
class TestErrorHandling:
|
|
199
413
|
@respx.mock
|
|
200
414
|
def test_authentication_error(self, client):
|
|
@@ -238,6 +238,275 @@ class TestConfigCommands:
|
|
|
238
238
|
assert "config" in result.output.lower() or "api" in result.output.lower()
|
|
239
239
|
|
|
240
240
|
|
|
241
|
+
class TestProcessingTimesCommands:
|
|
242
|
+
@respx.mock
|
|
243
|
+
def test_processing_times_list(self, runner, api_key):
|
|
244
|
+
"""processing-times list returns results."""
|
|
245
|
+
respx.get("https://app.colacloud.us/api/v1/processing-times").mock(
|
|
246
|
+
return_value=httpx.Response(
|
|
247
|
+
200,
|
|
248
|
+
json={
|
|
249
|
+
"data": [{"commodity": "wine", "avg_days": 30}],
|
|
250
|
+
"meta": {"total": 1},
|
|
251
|
+
},
|
|
252
|
+
)
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
result = runner.invoke(cli, ["processing-times", "list"])
|
|
256
|
+
assert result.exit_code == 0
|
|
257
|
+
assert "wine" in result.output
|
|
258
|
+
|
|
259
|
+
@respx.mock
|
|
260
|
+
def test_processing_times_list_json(self, runner, api_key):
|
|
261
|
+
"""processing-times list --json outputs JSON."""
|
|
262
|
+
respx.get("https://app.colacloud.us/api/v1/processing-times").mock(
|
|
263
|
+
return_value=httpx.Response(
|
|
264
|
+
200,
|
|
265
|
+
json={
|
|
266
|
+
"data": [{"commodity": "wine"}],
|
|
267
|
+
"meta": {"total": 1},
|
|
268
|
+
},
|
|
269
|
+
)
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
result = runner.invoke(cli, ["processing-times", "list", "--json"])
|
|
273
|
+
assert result.exit_code == 0
|
|
274
|
+
data = json.loads(result.output)
|
|
275
|
+
assert "data" in data
|
|
276
|
+
|
|
277
|
+
@respx.mock
|
|
278
|
+
def test_processing_times_list_empty(self, runner, api_key):
|
|
279
|
+
"""processing-times list shows message when no results."""
|
|
280
|
+
respx.get("https://app.colacloud.us/api/v1/processing-times").mock(
|
|
281
|
+
return_value=httpx.Response(
|
|
282
|
+
200, json={"data": [], "meta": {"total": 0}}
|
|
283
|
+
)
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
result = runner.invoke(cli, ["processing-times", "list"])
|
|
287
|
+
assert result.exit_code == 0
|
|
288
|
+
assert "No processing times found" in result.output
|
|
289
|
+
|
|
290
|
+
@respx.mock
|
|
291
|
+
def test_processing_times_formula(self, runner, api_key):
|
|
292
|
+
"""processing-times formula returns results."""
|
|
293
|
+
respx.get(
|
|
294
|
+
"https://app.colacloud.us/api/v1/processing-times/formula"
|
|
295
|
+
).mock(
|
|
296
|
+
return_value=httpx.Response(
|
|
297
|
+
200,
|
|
298
|
+
json={
|
|
299
|
+
"data": [{"formula_type": "domestic", "avg_days": 20}],
|
|
300
|
+
"meta": {"total": 1},
|
|
301
|
+
},
|
|
302
|
+
)
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
result = runner.invoke(cli, ["processing-times", "formula"])
|
|
306
|
+
assert result.exit_code == 0
|
|
307
|
+
assert "domestic" in result.output
|
|
308
|
+
|
|
309
|
+
@respx.mock
|
|
310
|
+
def test_processing_times_registration(self, runner, api_key):
|
|
311
|
+
"""processing-times registration returns results."""
|
|
312
|
+
respx.get(
|
|
313
|
+
"https://app.colacloud.us/api/v1/processing-times/registration"
|
|
314
|
+
).mock(
|
|
315
|
+
return_value=httpx.Response(
|
|
316
|
+
200,
|
|
317
|
+
json={
|
|
318
|
+
"data": [{"category": "beverage", "avg_days": 15}],
|
|
319
|
+
"meta": {"total": 1},
|
|
320
|
+
},
|
|
321
|
+
)
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
result = runner.invoke(cli, ["processing-times", "registration"])
|
|
325
|
+
assert result.exit_code == 0
|
|
326
|
+
assert "beverage" in result.output
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
class TestProductionReportsCommands:
|
|
330
|
+
@respx.mock
|
|
331
|
+
def test_production_reports_list(self, runner, api_key):
|
|
332
|
+
"""production-reports list returns results."""
|
|
333
|
+
respx.get("https://app.colacloud.us/api/v1/production-reports").mock(
|
|
334
|
+
return_value=httpx.Response(
|
|
335
|
+
200,
|
|
336
|
+
json={
|
|
337
|
+
"data": [{"commodity": "wine", "year": 2024, "month": 6}],
|
|
338
|
+
"meta": {
|
|
339
|
+
"total": 1,
|
|
340
|
+
"page": 1,
|
|
341
|
+
"per_page": 100,
|
|
342
|
+
"has_more": False,
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
)
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
result = runner.invoke(cli, ["production-reports", "list"])
|
|
349
|
+
assert result.exit_code == 0
|
|
350
|
+
assert "wine" in result.output
|
|
351
|
+
|
|
352
|
+
@respx.mock
|
|
353
|
+
def test_production_reports_list_json(self, runner, api_key):
|
|
354
|
+
"""production-reports list --json outputs JSON."""
|
|
355
|
+
respx.get("https://app.colacloud.us/api/v1/production-reports").mock(
|
|
356
|
+
return_value=httpx.Response(
|
|
357
|
+
200,
|
|
358
|
+
json={
|
|
359
|
+
"data": [{"commodity": "wine"}],
|
|
360
|
+
"meta": {
|
|
361
|
+
"total": 1,
|
|
362
|
+
"page": 1,
|
|
363
|
+
"per_page": 100,
|
|
364
|
+
"has_more": False,
|
|
365
|
+
},
|
|
366
|
+
},
|
|
367
|
+
)
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
result = runner.invoke(cli, ["production-reports", "list", "--json"])
|
|
371
|
+
assert result.exit_code == 0
|
|
372
|
+
data = json.loads(result.output)
|
|
373
|
+
assert "data" in data
|
|
374
|
+
|
|
375
|
+
@respx.mock
|
|
376
|
+
def test_production_reports_list_empty(self, runner, api_key):
|
|
377
|
+
"""production-reports list shows message when no results."""
|
|
378
|
+
respx.get("https://app.colacloud.us/api/v1/production-reports").mock(
|
|
379
|
+
return_value=httpx.Response(
|
|
380
|
+
200,
|
|
381
|
+
json={
|
|
382
|
+
"data": [],
|
|
383
|
+
"meta": {
|
|
384
|
+
"total": 0,
|
|
385
|
+
"page": 1,
|
|
386
|
+
"per_page": 100,
|
|
387
|
+
"has_more": False,
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
)
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
result = runner.invoke(cli, ["production-reports", "list"])
|
|
394
|
+
assert result.exit_code == 0
|
|
395
|
+
assert "No production reports found" in result.output
|
|
396
|
+
|
|
397
|
+
@respx.mock
|
|
398
|
+
def test_production_reports_list_with_pagination(self, runner, api_key):
|
|
399
|
+
"""production-reports list shows pagination hint when has_more."""
|
|
400
|
+
respx.get("https://app.colacloud.us/api/v1/production-reports").mock(
|
|
401
|
+
return_value=httpx.Response(
|
|
402
|
+
200,
|
|
403
|
+
json={
|
|
404
|
+
"data": [{"commodity": "wine"}],
|
|
405
|
+
"meta": {
|
|
406
|
+
"total": 200,
|
|
407
|
+
"page": 1,
|
|
408
|
+
"per_page": 100,
|
|
409
|
+
"has_more": True,
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
)
|
|
413
|
+
)
|
|
414
|
+
|
|
415
|
+
result = runner.invoke(cli, ["production-reports", "list"])
|
|
416
|
+
assert result.exit_code == 0
|
|
417
|
+
assert "More results available" in result.output
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
class TestAVAsCommands:
|
|
421
|
+
@respx.mock
|
|
422
|
+
def test_avas_list(self, runner, api_key):
|
|
423
|
+
"""avas list returns results."""
|
|
424
|
+
respx.get("https://app.colacloud.us/api/v1/avas").mock(
|
|
425
|
+
return_value=httpx.Response(
|
|
426
|
+
200,
|
|
427
|
+
json={
|
|
428
|
+
"data": [{"id": "napa-valley", "name": "Napa Valley"}],
|
|
429
|
+
"meta": {"total": 1},
|
|
430
|
+
},
|
|
431
|
+
)
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
result = runner.invoke(cli, ["avas", "list"])
|
|
435
|
+
assert result.exit_code == 0
|
|
436
|
+
assert "Napa Valley" in result.output
|
|
437
|
+
|
|
438
|
+
@respx.mock
|
|
439
|
+
def test_avas_list_json(self, runner, api_key):
|
|
440
|
+
"""avas list --json outputs JSON."""
|
|
441
|
+
respx.get("https://app.colacloud.us/api/v1/avas").mock(
|
|
442
|
+
return_value=httpx.Response(
|
|
443
|
+
200,
|
|
444
|
+
json={
|
|
445
|
+
"data": [{"id": "napa-valley", "name": "Napa Valley"}],
|
|
446
|
+
"meta": {"total": 1},
|
|
447
|
+
},
|
|
448
|
+
)
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
result = runner.invoke(cli, ["avas", "list", "--json"])
|
|
452
|
+
assert result.exit_code == 0
|
|
453
|
+
data = json.loads(result.output)
|
|
454
|
+
assert "data" in data
|
|
455
|
+
|
|
456
|
+
@respx.mock
|
|
457
|
+
def test_avas_list_empty(self, runner, api_key):
|
|
458
|
+
"""avas list shows message when no results."""
|
|
459
|
+
respx.get("https://app.colacloud.us/api/v1/avas").mock(
|
|
460
|
+
return_value=httpx.Response(
|
|
461
|
+
200, json={"data": [], "meta": {"total": 0}}
|
|
462
|
+
)
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
result = runner.invoke(cli, ["avas", "list"])
|
|
466
|
+
assert result.exit_code == 0
|
|
467
|
+
assert "No AVAs found" in result.output
|
|
468
|
+
|
|
469
|
+
@respx.mock
|
|
470
|
+
def test_avas_get(self, runner, api_key):
|
|
471
|
+
"""avas get returns AVA details."""
|
|
472
|
+
respx.get("https://app.colacloud.us/api/v1/avas/napa-valley").mock(
|
|
473
|
+
return_value=httpx.Response(
|
|
474
|
+
200,
|
|
475
|
+
json={
|
|
476
|
+
"data": {
|
|
477
|
+
"id": "napa-valley",
|
|
478
|
+
"name": "Napa Valley",
|
|
479
|
+
"state": "CA",
|
|
480
|
+
}
|
|
481
|
+
},
|
|
482
|
+
)
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
result = runner.invoke(cli, ["avas", "get", "napa-valley"])
|
|
486
|
+
assert result.exit_code == 0
|
|
487
|
+
assert "Napa Valley" in result.output
|
|
488
|
+
|
|
489
|
+
@respx.mock
|
|
490
|
+
def test_avas_get_json(self, runner, api_key):
|
|
491
|
+
"""avas get --json outputs JSON."""
|
|
492
|
+
respx.get("https://app.colacloud.us/api/v1/avas/napa-valley").mock(
|
|
493
|
+
return_value=httpx.Response(
|
|
494
|
+
200,
|
|
495
|
+
json={
|
|
496
|
+
"data": {
|
|
497
|
+
"id": "napa-valley",
|
|
498
|
+
"name": "Napa Valley",
|
|
499
|
+
}
|
|
500
|
+
},
|
|
501
|
+
)
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
result = runner.invoke(cli, ["avas", "get", "napa-valley", "--json"])
|
|
505
|
+
assert result.exit_code == 0
|
|
506
|
+
data = json.loads(result.output)
|
|
507
|
+
assert "data" in data
|
|
508
|
+
|
|
509
|
+
|
|
241
510
|
class TestCommandAliases:
|
|
242
511
|
@respx.mock
|
|
243
512
|
def test_shortcut_s_for_colas(self, runner, api_key):
|
|
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
|