planar 0.9.3__py3-none-any.whl → 0.11.0__py3-none-any.whl
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.
- planar/ai/agent.py +2 -1
- planar/ai/agent_base.py +24 -5
- planar/ai/state.py +17 -0
- planar/app.py +18 -1
- planar/data/connection.py +108 -0
- planar/data/dataset.py +11 -104
- planar/data/utils.py +89 -0
- planar/db/alembic/env.py +25 -1
- planar/files/storage/azure_blob.py +1 -1
- planar/registry_items.py +2 -0
- planar/routers/dataset_router.py +213 -0
- planar/routers/info.py +79 -36
- planar/routers/models.py +1 -0
- planar/routers/workflow.py +2 -0
- planar/scaffold_templates/pyproject.toml.j2 +1 -1
- planar/security/authorization.py +31 -3
- planar/security/default_policies.cedar +25 -0
- planar/testing/fixtures.py +34 -1
- planar/testing/planar_test_client.py +1 -1
- planar/workflows/decorators.py +2 -1
- planar/workflows/wrappers.py +1 -0
- {planar-0.9.3.dist-info → planar-0.11.0.dist-info}/METADATA +9 -1
- {planar-0.9.3.dist-info → planar-0.11.0.dist-info}/RECORD +25 -72
- {planar-0.9.3.dist-info → planar-0.11.0.dist-info}/WHEEL +1 -1
- planar/ai/test_agent_serialization.py +0 -229
- planar/ai/test_agent_tool_step_display.py +0 -78
- planar/data/test_dataset.py +0 -354
- planar/files/storage/test_azure_blob.py +0 -435
- planar/files/storage/test_local_directory.py +0 -162
- planar/files/storage/test_s3.py +0 -299
- planar/files/test_files.py +0 -282
- planar/human/test_human.py +0 -385
- planar/logging/test_formatter.py +0 -327
- planar/modeling/mixins/test_auditable.py +0 -97
- planar/modeling/mixins/test_timestamp.py +0 -134
- planar/modeling/mixins/test_uuid_primary_key.py +0 -52
- planar/routers/test_agents_router.py +0 -174
- planar/routers/test_files_router.py +0 -49
- planar/routers/test_object_config_router.py +0 -367
- planar/routers/test_routes_security.py +0 -168
- planar/routers/test_rule_router.py +0 -470
- planar/routers/test_workflow_router.py +0 -539
- planar/rules/test_data/account_dormancy_management.json +0 -223
- planar/rules/test_data/airline_loyalty_points_calculator.json +0 -262
- planar/rules/test_data/applicant_risk_assessment.json +0 -435
- planar/rules/test_data/booking_fraud_detection.json +0 -407
- planar/rules/test_data/cellular_data_rollover_system.json +0 -258
- planar/rules/test_data/clinical_trial_eligibility_screener.json +0 -437
- planar/rules/test_data/customer_lifetime_value.json +0 -143
- planar/rules/test_data/import_duties_calculator.json +0 -289
- planar/rules/test_data/insurance_prior_authorization.json +0 -443
- planar/rules/test_data/online_check_in_eligibility_system.json +0 -254
- planar/rules/test_data/order_consolidation_system.json +0 -375
- planar/rules/test_data/portfolio_risk_monitor.json +0 -471
- planar/rules/test_data/supply_chain_risk.json +0 -253
- planar/rules/test_data/warehouse_cross_docking.json +0 -237
- planar/rules/test_rules.py +0 -1494
- planar/security/tests/test_auth_middleware.py +0 -162
- planar/security/tests/test_authorization_context.py +0 -78
- planar/security/tests/test_cedar_basics.py +0 -41
- planar/security/tests/test_cedar_policies.py +0 -158
- planar/security/tests/test_jwt_principal_context.py +0 -179
- planar/test_app.py +0 -142
- planar/test_cli.py +0 -394
- planar/test_config.py +0 -515
- planar/test_object_config.py +0 -527
- planar/test_object_registry.py +0 -14
- planar/test_sqlalchemy.py +0 -193
- planar/test_utils.py +0 -105
- planar/testing/test_memory_storage.py +0 -143
- planar/workflows/test_concurrency_detection.py +0 -120
- planar/workflows/test_lock_timeout.py +0 -140
- planar/workflows/test_serialization.py +0 -1203
- planar/workflows/test_suspend_deserialization.py +0 -231
- planar/workflows/test_workflow.py +0 -2005
- {planar-0.9.3.dist-info → planar-0.11.0.dist-info}/entry_points.txt +0 -0
@@ -1,174 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Tests for agent router endpoints.
|
3
|
-
|
4
|
-
This module tests the agent router endpoints to ensure they work correctly
|
5
|
-
with the new serialization changes.
|
6
|
-
"""
|
7
|
-
|
8
|
-
import pytest
|
9
|
-
from sqlmodel.ext.asyncio.session import AsyncSession
|
10
|
-
|
11
|
-
from planar.ai.agent import Agent
|
12
|
-
from planar.app import PlanarApp
|
13
|
-
from planar.config import sqlite_config
|
14
|
-
from planar.testing.planar_test_client import PlanarTestClient
|
15
|
-
|
16
|
-
|
17
|
-
@pytest.fixture(name="app")
|
18
|
-
def app_fixture(tmp_db_path: str):
|
19
|
-
"""Create a test app with agents."""
|
20
|
-
app = PlanarApp(
|
21
|
-
config=sqlite_config(tmp_db_path),
|
22
|
-
title="Test app for agent router",
|
23
|
-
description="Testing agent endpoints",
|
24
|
-
)
|
25
|
-
|
26
|
-
# Register a simple agent
|
27
|
-
simple_agent = Agent(
|
28
|
-
name="simple_test_agent",
|
29
|
-
system_prompt="Simple system prompt",
|
30
|
-
user_prompt="Simple user prompt: {input}",
|
31
|
-
model="openai:gpt-4o",
|
32
|
-
max_turns=2,
|
33
|
-
)
|
34
|
-
app.register_agent(simple_agent)
|
35
|
-
|
36
|
-
# Register an agent with tools
|
37
|
-
async def test_tool(param: str) -> str:
|
38
|
-
"""A test tool."""
|
39
|
-
return f"Processed: {param}"
|
40
|
-
|
41
|
-
agent_with_tools = Agent(
|
42
|
-
name="agent_with_tools",
|
43
|
-
system_prompt="System with tools",
|
44
|
-
user_prompt="User: {input}",
|
45
|
-
model="anthropic:claude-3-5-sonnet-latest",
|
46
|
-
max_turns=5,
|
47
|
-
tools=[test_tool],
|
48
|
-
)
|
49
|
-
app.register_agent(agent_with_tools)
|
50
|
-
|
51
|
-
return app
|
52
|
-
|
53
|
-
|
54
|
-
async def test_get_agents_endpoint(
|
55
|
-
client: PlanarTestClient, app: PlanarApp, session: AsyncSession
|
56
|
-
):
|
57
|
-
"""Test the GET /agents endpoint returns agents with configs field."""
|
58
|
-
response = await client.get("/planar/v1/agents/")
|
59
|
-
assert response.status_code == 200
|
60
|
-
|
61
|
-
agents = response.json()
|
62
|
-
assert len(agents) == 2
|
63
|
-
|
64
|
-
# Check first agent
|
65
|
-
simple_agent = next(a for a in agents if a["name"] == "simple_test_agent")
|
66
|
-
assert simple_agent["name"] == "simple_test_agent"
|
67
|
-
assert "configs" in simple_agent
|
68
|
-
assert isinstance(simple_agent["configs"], list)
|
69
|
-
assert len(simple_agent["configs"]) == 1 # Default config always present
|
70
|
-
|
71
|
-
# Verify the default config is present and correct
|
72
|
-
default_config = simple_agent["configs"][-1]
|
73
|
-
assert default_config["version"] == 0
|
74
|
-
assert default_config["data"]["system_prompt"] == "Simple system prompt"
|
75
|
-
assert default_config["data"]["user_prompt"] == "Simple user prompt: {input}"
|
76
|
-
assert default_config["data"]["model"] == "openai:gpt-4o"
|
77
|
-
assert default_config["data"]["max_turns"] == 2
|
78
|
-
|
79
|
-
# Verify removed fields are not present
|
80
|
-
assert "system_prompt" not in simple_agent
|
81
|
-
assert "user_prompt" not in simple_agent
|
82
|
-
assert "model" not in simple_agent
|
83
|
-
assert "max_turns" not in simple_agent
|
84
|
-
assert "overwrites" not in simple_agent
|
85
|
-
|
86
|
-
# Check agent with tools
|
87
|
-
tools_agent = next(a for a in agents if a["name"] == "agent_with_tools")
|
88
|
-
assert len(tools_agent["tool_definitions"]) == 1
|
89
|
-
assert tools_agent["tool_definitions"][0]["name"] == "test_tool"
|
90
|
-
|
91
|
-
|
92
|
-
async def test_update_agent_endpoint(
|
93
|
-
client: PlanarTestClient, app: PlanarApp, session: AsyncSession
|
94
|
-
):
|
95
|
-
"""Test the PATCH /agents/{agent_name} endpoint creates configs."""
|
96
|
-
# Get agents first
|
97
|
-
response = await client.get("/planar/v1/agents/")
|
98
|
-
assert response.status_code == 200
|
99
|
-
agents = response.json()
|
100
|
-
assert len(agents) == 2
|
101
|
-
|
102
|
-
# Update the agent
|
103
|
-
update_data = {
|
104
|
-
"system_prompt": "Updated system prompt",
|
105
|
-
"user_prompt": "Updated user prompt: {input}",
|
106
|
-
}
|
107
|
-
response = await client.patch(
|
108
|
-
"/planar/v1/agents/simple_test_agent", json=update_data
|
109
|
-
)
|
110
|
-
assert response.status_code == 200
|
111
|
-
|
112
|
-
updated_agent = response.json()
|
113
|
-
assert "configs" in updated_agent
|
114
|
-
assert len(updated_agent["configs"]) == 2
|
115
|
-
|
116
|
-
# Check the config data
|
117
|
-
config = updated_agent["configs"][0]
|
118
|
-
assert config["data"]["system_prompt"] == "Updated system prompt"
|
119
|
-
assert config["data"]["user_prompt"] == "Updated user prompt: {input}"
|
120
|
-
assert config["version"] == 1
|
121
|
-
assert config["object_type"] == "agent"
|
122
|
-
assert config["object_name"] == "simple_test_agent"
|
123
|
-
|
124
|
-
|
125
|
-
async def test_agent_with_multiple_configs(
|
126
|
-
client: PlanarTestClient, app: PlanarApp, session: AsyncSession
|
127
|
-
):
|
128
|
-
"""Test that agents return all configs when multiple exist."""
|
129
|
-
# Get the agent ID first
|
130
|
-
response = await client.get("/planar/v1/agents/")
|
131
|
-
agents = response.json()
|
132
|
-
simple_agent = next(a for a in agents if a["name"] == "simple_test_agent")
|
133
|
-
|
134
|
-
# Create first config via PATCH endpoint
|
135
|
-
config1_data = {
|
136
|
-
"system_prompt": "Config 1 system",
|
137
|
-
"user_prompt": "Config 1 user: {input}",
|
138
|
-
"model": "openai:gpt-4o",
|
139
|
-
"max_turns": 2,
|
140
|
-
"model_parameters": {"temperature": 0.7},
|
141
|
-
}
|
142
|
-
response = await client.patch(
|
143
|
-
f"/planar/v1/agents/{simple_agent['name']}", json=config1_data
|
144
|
-
)
|
145
|
-
assert response.status_code == 200
|
146
|
-
|
147
|
-
# Create second config via PATCH endpoint
|
148
|
-
config2_data = {
|
149
|
-
"system_prompt": "Config 2 system",
|
150
|
-
"user_prompt": "Config 2 user: {input}",
|
151
|
-
"model": "anthropic:claude-3-opus",
|
152
|
-
"max_turns": 4,
|
153
|
-
"model_parameters": {"temperature": 0.9},
|
154
|
-
}
|
155
|
-
response = await client.patch(
|
156
|
-
f"/planar/v1/agents/{simple_agent['name']}", json=config2_data
|
157
|
-
)
|
158
|
-
assert response.status_code == 200
|
159
|
-
|
160
|
-
# Get agents
|
161
|
-
response = await client.get("/planar/v1/agents/")
|
162
|
-
agents = response.json()
|
163
|
-
simple_agent = next(a for a in agents if a["name"] == "simple_test_agent")
|
164
|
-
|
165
|
-
# Verify all configs are returned (including default config)
|
166
|
-
assert len(simple_agent["configs"]) == 3
|
167
|
-
assert simple_agent["configs"][0]["version"] == 2 # Latest first
|
168
|
-
assert simple_agent["configs"][1]["version"] == 1
|
169
|
-
assert simple_agent["configs"][2]["version"] == 0 # Default config
|
170
|
-
|
171
|
-
# Verify config data
|
172
|
-
assert simple_agent["configs"][0]["data"]["system_prompt"] == "Config 2 system"
|
173
|
-
assert simple_agent["configs"][1]["data"]["system_prompt"] == "Config 1 system"
|
174
|
-
assert simple_agent["configs"][2]["data"]["system_prompt"] == "Simple system prompt"
|
@@ -1,49 +0,0 @@
|
|
1
|
-
import io
|
2
|
-
from uuid import UUID
|
3
|
-
|
4
|
-
import pytest
|
5
|
-
from sqlmodel.ext.asyncio.session import AsyncSession
|
6
|
-
|
7
|
-
from planar import PlanarApp, sqlite_config
|
8
|
-
from planar.files.models import PlanarFileMetadata
|
9
|
-
from planar.testing.planar_test_client import PlanarTestClient
|
10
|
-
|
11
|
-
|
12
|
-
@pytest.fixture(name="app")
|
13
|
-
def app_fixture(tmp_db_path: str):
|
14
|
-
return PlanarApp(
|
15
|
-
config=sqlite_config(tmp_db_path),
|
16
|
-
title="Test app for files router",
|
17
|
-
description="Testing files endpoints",
|
18
|
-
)
|
19
|
-
|
20
|
-
|
21
|
-
async def test_upload_parquet_sets_content_type(
|
22
|
-
client: PlanarTestClient, session: AsyncSession
|
23
|
-
):
|
24
|
-
"""Uploading a .parquet file should persist application/x-parquet in metadata."""
|
25
|
-
|
26
|
-
# Prepare a small in-memory payload and intentionally send an octet-stream
|
27
|
-
# to simulate browsers that don't know parquet. The route should override
|
28
|
-
# this using mimetypes.guess_type.
|
29
|
-
filename = "test_data.parquet"
|
30
|
-
payload = b"PAR1" # content doesn't matter for MIME guessing by filename
|
31
|
-
|
32
|
-
files = {
|
33
|
-
"files": (filename, io.BytesIO(payload), "application/octet-stream"),
|
34
|
-
}
|
35
|
-
|
36
|
-
resp = await client.post("/planar/v1/file/upload", files=files)
|
37
|
-
assert resp.status_code == 200
|
38
|
-
|
39
|
-
body = resp.json()
|
40
|
-
assert isinstance(body, list) and len(body) == 1
|
41
|
-
file_item = body[0]
|
42
|
-
assert file_item["filename"] == filename
|
43
|
-
|
44
|
-
# Verify the database record has the correct MIME type
|
45
|
-
file_id = UUID(file_item["id"])
|
46
|
-
meta = await session.get(PlanarFileMetadata, file_id)
|
47
|
-
|
48
|
-
assert meta is not None
|
49
|
-
assert meta.content_type == "application/x-parquet"
|
@@ -1,367 +0,0 @@
|
|
1
|
-
"""
|
2
|
-
Tests for object configuration router endpoints.
|
3
|
-
|
4
|
-
This module tests the object configuration router endpoints to ensure they work correctly
|
5
|
-
for both agent and rule configurations.
|
6
|
-
"""
|
7
|
-
|
8
|
-
from typing import AsyncGenerator
|
9
|
-
from uuid import UUID, uuid4
|
10
|
-
|
11
|
-
import pytest
|
12
|
-
from pydantic import BaseModel, Field
|
13
|
-
from sqlmodel.ext.asyncio.session import AsyncSession
|
14
|
-
|
15
|
-
from planar.ai.agent import Agent
|
16
|
-
from planar.ai.agent_utils import agent_configuration
|
17
|
-
from planar.ai.models import AgentConfig
|
18
|
-
from planar.app import PlanarApp
|
19
|
-
from planar.config import sqlite_config
|
20
|
-
from planar.object_config import DEFAULT_UUID, ObjectConfiguration
|
21
|
-
from planar.object_config.object_config import ConfigurableObjectType
|
22
|
-
from planar.object_registry import ObjectRegistry
|
23
|
-
from planar.rules.decorator import rule
|
24
|
-
from planar.rules.models import Rule, RuleEngineConfig, create_jdm_graph
|
25
|
-
from planar.rules.rule_configuration import rule_configuration
|
26
|
-
from planar.testing.planar_test_client import PlanarTestClient
|
27
|
-
|
28
|
-
|
29
|
-
class InputForTestRule(BaseModel):
|
30
|
-
"""Input for test rule."""
|
31
|
-
|
32
|
-
value: int = Field(description="Test value")
|
33
|
-
category: str = Field(description="Test category")
|
34
|
-
|
35
|
-
|
36
|
-
class OutputFromTestRule(BaseModel):
|
37
|
-
"""Output from test rule."""
|
38
|
-
|
39
|
-
result: int = Field(description="Result value")
|
40
|
-
message: str = Field(description="Result message")
|
41
|
-
|
42
|
-
|
43
|
-
@pytest.fixture(name="app")
|
44
|
-
def app_fixture(tmp_db_path: str):
|
45
|
-
"""Create a test app with agents and rules."""
|
46
|
-
app = PlanarApp(
|
47
|
-
config=sqlite_config(tmp_db_path),
|
48
|
-
title="Test app for object config router",
|
49
|
-
description="Testing object configuration endpoints",
|
50
|
-
)
|
51
|
-
|
52
|
-
# Register a simple agent
|
53
|
-
simple_agent = Agent(
|
54
|
-
name="test_agent",
|
55
|
-
system_prompt="Test system prompt",
|
56
|
-
user_prompt="Test user prompt: {input}",
|
57
|
-
model="openai:gpt-4o",
|
58
|
-
max_turns=2,
|
59
|
-
)
|
60
|
-
app.register_agent(simple_agent)
|
61
|
-
|
62
|
-
# Create and register a rule
|
63
|
-
@rule(description="Test rule for configuration")
|
64
|
-
def test_rule(input: InputForTestRule) -> OutputFromTestRule:
|
65
|
-
# Default implementation
|
66
|
-
return OutputFromTestRule(
|
67
|
-
result=input.value * 2, message=f"Processed {input.category}"
|
68
|
-
)
|
69
|
-
|
70
|
-
app.register_rule(test_rule)
|
71
|
-
|
72
|
-
return app
|
73
|
-
|
74
|
-
|
75
|
-
@pytest.fixture
|
76
|
-
async def agent_with_configs(app: PlanarApp, session: AsyncSession):
|
77
|
-
"""Create an agent with multiple configurations."""
|
78
|
-
# First config
|
79
|
-
agent_config_1 = AgentConfig(
|
80
|
-
system_prompt="Config 1 system",
|
81
|
-
user_prompt="Config 1 user: {input}",
|
82
|
-
model="openai:gpt-4o",
|
83
|
-
max_turns=3,
|
84
|
-
)
|
85
|
-
await agent_configuration.write_config("test_agent", agent_config_1)
|
86
|
-
|
87
|
-
# Second config
|
88
|
-
agent_config_2 = AgentConfig(
|
89
|
-
system_prompt="Config 2 system",
|
90
|
-
user_prompt="Config 2 user: {input}",
|
91
|
-
model="anthropic:claude-3-sonnet",
|
92
|
-
max_turns=5,
|
93
|
-
)
|
94
|
-
config_2 = await agent_configuration.write_config("test_agent", agent_config_2)
|
95
|
-
|
96
|
-
# Make the second config active
|
97
|
-
await agent_configuration.promote_config(config_2.id)
|
98
|
-
|
99
|
-
return config_2.id
|
100
|
-
|
101
|
-
|
102
|
-
@pytest.fixture
|
103
|
-
async def rule_with_configs(
|
104
|
-
session: AsyncSession,
|
105
|
-
) -> AsyncGenerator[tuple[Rule, list[ObjectConfiguration]], None]:
|
106
|
-
class RuleInputOutput(BaseModel):
|
107
|
-
test: str
|
108
|
-
|
109
|
-
rule = Rule(
|
110
|
-
name=f"test_rule_promote_{uuid4().hex}",
|
111
|
-
description="Test rule for promoting configuration",
|
112
|
-
input=RuleInputOutput,
|
113
|
-
output=RuleInputOutput,
|
114
|
-
)
|
115
|
-
ObjectRegistry.get_instance().register(rule)
|
116
|
-
|
117
|
-
# Create some configs
|
118
|
-
jdm_config_1 = create_jdm_graph(rule)
|
119
|
-
jdm_config_2 = create_jdm_graph(rule)
|
120
|
-
|
121
|
-
rule_config_1 = RuleEngineConfig(jdm=jdm_config_1)
|
122
|
-
rule_config_2 = RuleEngineConfig(jdm=jdm_config_2)
|
123
|
-
|
124
|
-
config1 = await rule_configuration.write_config(rule.name, rule_config_1)
|
125
|
-
config2 = await rule_configuration.write_config(rule.name, rule_config_2)
|
126
|
-
|
127
|
-
yield rule, [config1, config2]
|
128
|
-
|
129
|
-
|
130
|
-
async def test_promote_agent_config(
|
131
|
-
client: PlanarTestClient,
|
132
|
-
app: PlanarApp,
|
133
|
-
session: AsyncSession,
|
134
|
-
agent_with_configs: UUID,
|
135
|
-
):
|
136
|
-
"""Test promoting an agent configuration."""
|
137
|
-
# Get the configurations first to find a non-active one
|
138
|
-
agent = app._object_registry.get_agents()[0]
|
139
|
-
configs = await agent_configuration.read_configs_with_default(
|
140
|
-
"test_agent", agent.to_config()
|
141
|
-
)
|
142
|
-
|
143
|
-
# Find the first (inactive) config
|
144
|
-
inactive_config = next(c for c in configs if not c.active)
|
145
|
-
|
146
|
-
# Promote the inactive config
|
147
|
-
request_data = {
|
148
|
-
"object_type": ConfigurableObjectType.AGENT,
|
149
|
-
"config_id": str(inactive_config.id),
|
150
|
-
"object_name": "test_agent",
|
151
|
-
}
|
152
|
-
|
153
|
-
response = await client.post(
|
154
|
-
"/planar/v1/object-configurations/promote", json=request_data
|
155
|
-
)
|
156
|
-
assert response.status_code == 200
|
157
|
-
|
158
|
-
result = response.json()
|
159
|
-
assert "configs" in result
|
160
|
-
assert len(result["configs"]) >= 3 # At least 2 configs + default
|
161
|
-
|
162
|
-
# Verify the promoted config is now active
|
163
|
-
promoted_config = next(
|
164
|
-
c for c in result["configs"] if c["id"] == str(inactive_config.id)
|
165
|
-
)
|
166
|
-
assert promoted_config["active"] is True
|
167
|
-
|
168
|
-
# Verify other configs are inactive
|
169
|
-
for config in result["configs"]:
|
170
|
-
if config["id"] != str(inactive_config.id):
|
171
|
-
assert config["active"] is False
|
172
|
-
|
173
|
-
|
174
|
-
async def test_promote_rule_config(
|
175
|
-
client: PlanarTestClient,
|
176
|
-
app: PlanarApp,
|
177
|
-
session: AsyncSession,
|
178
|
-
):
|
179
|
-
"""Test promoting a rule configuration."""
|
180
|
-
# Get the configurations first to find a non-active one
|
181
|
-
rule = ObjectRegistry.get_instance().get_rules()[0]
|
182
|
-
|
183
|
-
await rule_configuration.write_config(
|
184
|
-
rule.name, RuleEngineConfig(jdm=create_jdm_graph(rule))
|
185
|
-
)
|
186
|
-
|
187
|
-
configs = await rule_configuration.read_configs_with_default(
|
188
|
-
rule.name, rule.to_config()
|
189
|
-
)
|
190
|
-
|
191
|
-
assert len(configs) == 2
|
192
|
-
|
193
|
-
# Find the first (inactive) config
|
194
|
-
inactive_config = next(c for c in configs if not c.active)
|
195
|
-
|
196
|
-
# Promote the inactive config
|
197
|
-
request_data = {
|
198
|
-
"object_type": ConfigurableObjectType.RULE,
|
199
|
-
"config_id": str(inactive_config.id),
|
200
|
-
"object_name": rule.name,
|
201
|
-
}
|
202
|
-
|
203
|
-
response = await client.post(
|
204
|
-
"/planar/v1/object-configurations/promote", json=request_data
|
205
|
-
)
|
206
|
-
assert response.status_code == 200
|
207
|
-
|
208
|
-
result = response.json()
|
209
|
-
assert "configs" in result
|
210
|
-
assert len(result["configs"]) == 2
|
211
|
-
|
212
|
-
# Verify the promoted config is now active
|
213
|
-
promoted_config = next(
|
214
|
-
c for c in result["configs"] if c["id"] == str(inactive_config.id)
|
215
|
-
)
|
216
|
-
assert promoted_config["active"] is True
|
217
|
-
|
218
|
-
# Verify the config data is correct
|
219
|
-
assert promoted_config["object_type"] == "rule"
|
220
|
-
assert promoted_config["object_name"] == rule.name
|
221
|
-
assert "jdm" in promoted_config["data"]
|
222
|
-
|
223
|
-
|
224
|
-
async def test_promote_to_default_agent(
|
225
|
-
client: PlanarTestClient,
|
226
|
-
app: PlanarApp,
|
227
|
-
session: AsyncSession,
|
228
|
-
agent_with_configs: UUID,
|
229
|
-
):
|
230
|
-
"""Test promoting to default (revert to original implementation) for agent."""
|
231
|
-
# Promote to default using the special UUID
|
232
|
-
request_data = {
|
233
|
-
"object_type": ConfigurableObjectType.AGENT,
|
234
|
-
"config_id": str(DEFAULT_UUID),
|
235
|
-
"object_name": "test_agent",
|
236
|
-
}
|
237
|
-
|
238
|
-
response = await client.post(
|
239
|
-
"/planar/v1/object-configurations/promote", json=request_data
|
240
|
-
)
|
241
|
-
assert response.status_code == 200
|
242
|
-
|
243
|
-
result = response.json()
|
244
|
-
assert "configs" in result
|
245
|
-
|
246
|
-
# Verify all non-default configs are inactive
|
247
|
-
for config in result["configs"]:
|
248
|
-
if config["version"] == 0: # Default config
|
249
|
-
assert config["active"] is True
|
250
|
-
else:
|
251
|
-
assert config["active"] is False
|
252
|
-
|
253
|
-
|
254
|
-
async def test_promote_to_default_rule(
|
255
|
-
client: PlanarTestClient,
|
256
|
-
app: PlanarApp,
|
257
|
-
session: AsyncSession,
|
258
|
-
rule_with_configs: UUID,
|
259
|
-
):
|
260
|
-
"""Test promoting to default (revert to original implementation) for rule."""
|
261
|
-
# Promote to default using the special UUID
|
262
|
-
request_data = {
|
263
|
-
"object_type": ConfigurableObjectType.RULE,
|
264
|
-
"config_id": str(DEFAULT_UUID),
|
265
|
-
"object_name": "test_rule",
|
266
|
-
}
|
267
|
-
|
268
|
-
response = await client.post(
|
269
|
-
"/planar/v1/object-configurations/promote", json=request_data
|
270
|
-
)
|
271
|
-
assert response.status_code == 200
|
272
|
-
|
273
|
-
result = response.json()
|
274
|
-
assert "configs" in result
|
275
|
-
|
276
|
-
# Verify all non-default configs are inactive
|
277
|
-
for config in result["configs"]:
|
278
|
-
if config["version"] == 0: # Default config
|
279
|
-
assert config["active"] is True
|
280
|
-
else:
|
281
|
-
assert config["active"] is False
|
282
|
-
|
283
|
-
|
284
|
-
async def test_promote_nonexistent_agent(
|
285
|
-
client: PlanarTestClient, app: PlanarApp, session: AsyncSession
|
286
|
-
):
|
287
|
-
"""Test promoting config for non-existent agent."""
|
288
|
-
request_data = {
|
289
|
-
"object_type": ConfigurableObjectType.AGENT,
|
290
|
-
"config_id": str(UUID("12345678-1234-5678-1234-567812345678")),
|
291
|
-
"object_name": "nonexistent_agent",
|
292
|
-
}
|
293
|
-
|
294
|
-
response = await client.post(
|
295
|
-
"/planar/v1/object-configurations/promote", json=request_data
|
296
|
-
)
|
297
|
-
assert response.status_code == 404
|
298
|
-
assert response.json()["detail"] == "Agent not found"
|
299
|
-
|
300
|
-
|
301
|
-
async def test_promote_nonexistent_rule(
|
302
|
-
client: PlanarTestClient, app: PlanarApp, session: AsyncSession
|
303
|
-
):
|
304
|
-
"""Test promoting config for non-existent rule."""
|
305
|
-
request_data = {
|
306
|
-
"object_type": ConfigurableObjectType.RULE,
|
307
|
-
"config_id": str(UUID("12345678-1234-5678-1234-567812345678")),
|
308
|
-
"object_name": "nonexistent_rule",
|
309
|
-
}
|
310
|
-
|
311
|
-
response = await client.post(
|
312
|
-
"/planar/v1/object-configurations/promote", json=request_data
|
313
|
-
)
|
314
|
-
assert response.status_code == 404
|
315
|
-
assert response.json()["detail"] == "Rule not found"
|
316
|
-
|
317
|
-
|
318
|
-
async def test_promote_nonexistent_config(
|
319
|
-
client: PlanarTestClient,
|
320
|
-
app: PlanarApp,
|
321
|
-
session: AsyncSession,
|
322
|
-
agent_with_configs: UUID,
|
323
|
-
):
|
324
|
-
"""Test promoting a non-existent configuration."""
|
325
|
-
# Try to promote a config that doesn't exist
|
326
|
-
request_data = {
|
327
|
-
"object_type": ConfigurableObjectType.AGENT,
|
328
|
-
"config_id": str(UUID("99999999-9999-9999-9999-999999999999")),
|
329
|
-
"object_name": "test_agent",
|
330
|
-
}
|
331
|
-
|
332
|
-
# This should fail with an error from the promote_config method
|
333
|
-
response = await client.post(
|
334
|
-
"/planar/v1/object-configurations/promote", json=request_data
|
335
|
-
)
|
336
|
-
assert response.status_code == 404
|
337
|
-
|
338
|
-
|
339
|
-
async def test_config_versions_ordering(
|
340
|
-
client: PlanarTestClient,
|
341
|
-
app: PlanarApp,
|
342
|
-
session: AsyncSession,
|
343
|
-
agent_with_configs: UUID,
|
344
|
-
):
|
345
|
-
"""Test that configurations are returned in correct version order."""
|
346
|
-
# Promote to ensure we have a known state
|
347
|
-
request_data = {
|
348
|
-
"object_type": ConfigurableObjectType.AGENT,
|
349
|
-
"config_id": str(agent_with_configs),
|
350
|
-
"object_name": "test_agent",
|
351
|
-
}
|
352
|
-
|
353
|
-
response = await client.post(
|
354
|
-
"/planar/v1/object-configurations/promote", json=request_data
|
355
|
-
)
|
356
|
-
assert response.status_code == 200
|
357
|
-
|
358
|
-
result = response.json()
|
359
|
-
configs = result["configs"]
|
360
|
-
|
361
|
-
# Verify configs are ordered by version descending (except default which is always last)
|
362
|
-
non_default_configs = [c for c in configs if c["version"] != 0]
|
363
|
-
versions = [c["version"] for c in non_default_configs]
|
364
|
-
assert versions == sorted(versions, reverse=True)
|
365
|
-
|
366
|
-
# Verify default config is last
|
367
|
-
assert configs[-1]["version"] == 0
|