praetorian-cli 2.2.2__py3-none-any.whl → 2.2.4__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.
@@ -1,5 +1,6 @@
1
1
  from praetorian_cli.handlers.utils import error
2
2
  from praetorian_cli.sdk.model.globals import Seed, Kind
3
+ from praetorian_cli.sdk.model.query import Query, Node, Filter, KIND_TO_LABEL
3
4
 
4
5
 
5
6
  class Seeds:
@@ -9,97 +10,148 @@ class Seeds:
9
10
  def __init__(self, api):
10
11
  self.api = api
11
12
 
12
- def add(self, dns, status=Seed.PENDING.value):
13
+ def add(self, status=Seed.PENDING.value, seed_type=Kind.ASSET.value, **kwargs):
13
14
  """
14
- Add a seed to the account.
15
-
16
- :param dns: The DNS name, IP address, or CIDR range to add as a seed. Accepts domain names (e.g., 'example.com'), IP addresses (e.g., '192.168.1.1'), or CIDR ranges (e.g., '192.168.1.0/24')
17
- :type dns: str
18
- :param status: Seed status from Seed enum ('A' for Active, 'F' for Frozen, 'D' for Deleted, 'P' for Pending, 'FR' for Frozen Rejected)
19
- :type status: str
15
+ Add a seed of specified type with dynamic fields.
16
+
17
+ :param status: Status for backward compatibility
18
+ :type status: str or None
19
+ :param type: Asset type (e.g., 'asset', 'addomain', etc.)
20
+ :type type: str
21
+ :param kwargs: Dynamic fields for the asset type
20
22
  :return: The seed that was added
21
23
  :rtype: dict
22
24
  """
23
- return self.api.upsert('seed', dict(dns=dns, status=status))
25
+ # Handle status if provided
26
+ kwargs['status'] = status
27
+
28
+ # Build payload with type wrapper
29
+ payload = {
30
+ 'type': seed_type,
31
+ 'model': kwargs
32
+ }
33
+
34
+ return self.api.upsert('seed', payload)
24
35
 
25
36
  def get(self, key):
26
37
  """
27
38
  Get details of a seed by key.
28
39
 
29
- :param key: Entity key in format #seed#{type}#{dns} where type is 'domain', 'ip', or 'cidr' and dns is the seed value
40
+ :param key: Entity key (e.g., '#asset#example.com#example.com')
30
41
  :type key: str
31
42
  :return: The seed matching the specified key, or None if not found
32
43
  :rtype: dict or None
33
44
  """
34
- return self.api.search.by_exact_key(key, False)
35
-
36
- def update(self, key, status):
45
+
46
+ # Create a Filter for the key field
47
+ key_filter = Filter(
48
+ field=Filter.Field.KEY,
49
+ operator=Filter.Operator.EQUAL,
50
+ value=key
51
+ )
52
+
53
+ # Create a Node with Seed label and key filter
54
+ node = Node(
55
+ labels=[Node.Label.SEED],
56
+ filters=[key_filter]
57
+ )
58
+
59
+ # Create the Query object
60
+ query = Query(node=node)
61
+
62
+ # Call by_query with the constructed Query object
63
+ results_tuple = self.api.search.by_query(query)
64
+ if not results_tuple:
65
+ return None
66
+
67
+ results, _ = results_tuple
68
+ if len(results) == 0:
69
+ return None
70
+ return results[0]
71
+
72
+ def update(self, key, status=None):
37
73
  """
38
- Update a seed's status.
39
-
40
- Note: The seed PUT endpoint is different from other PUT endpoints. This method
41
- internally uses the DNS of the original seed rather than the key for the update operation.
42
-
43
- :param key: Entity key in format #seed#{type}#{dns} where type is 'domain', 'ip', or 'cidr' and dns is the seed value
74
+ Update seed fields dynamically.
75
+
76
+ :param key: Seed/Asset key (e.g., '#seed#domain#example.com' or '#asset#domain#example.com')
44
77
  :type key: str
45
- :param status: Seed status from Seed enum ('A' for Active, 'F' for Frozen, 'D' for Deleted, 'P' for Pending, 'FR' for Frozen Rejected)
46
- :type status: str
78
+ :param status: Status for backward compatibility (can be positional)
79
+ :type status: str or None
80
+ :param kwargs: Fields to update
47
81
  :return: The updated seed, or None if the seed was not found
48
82
  :rtype: dict or None
49
83
  """
50
- seed = self.api.search.by_exact_key(key)
84
+
85
+ seed = self.get(key) # This already handles old key format conversion
51
86
  if seed:
52
- # the seed PUT endpoint is different from other PUT endpoints. This one has to
53
- # take the DNS of the original seed, instead of the key of the seed record.
54
- # TODO, 2024-12-23, peter: check with Noah as to why. Ideally, we should
55
- # standardize to how other endpoints do it
56
- return self.api.upsert('seed', dict(dns=seed['dns'], status=status))
87
+ update_payload = {
88
+ 'key': key,
89
+ 'status': status
90
+ }
91
+
92
+ return self.api.upsert('seed', update_payload)
57
93
  else:
58
- error(f'Seed {key} is not found.')
94
+ error(f'Seed {key} not found.')
59
95
 
60
96
  def delete(self, key):
61
97
  """
62
- Delete a seed by setting its status to DELETED.
63
-
64
- Note: This method does not actually delete the seed from the database. Instead,
65
- it sets the seed's status to DELETED ('D'), which marks it as deleted while
66
- preserving the record for audit purposes.
67
-
68
- :param key: Entity key in format #seed#{type}#{dns} where type is 'domain', 'ip', or 'cidr' and dns is the seed value
98
+ Delete seed (supports both old and new key formats).
99
+
100
+ :param key: Seed/Asset key (e.g., '#asset#domain#example.com')
69
101
  :type key: str
70
102
  :return: The seed that was marked as deleted, or None if the seed was not found
71
103
  :rtype: dict or None
72
104
  """
73
- seed = self.api.search.by_exact_key(key)
105
+ seed = self.get(key) # This already handles old key format conversion
106
+
74
107
  if seed:
75
- # TODO, 2024-12-23, peter: check with Noah why this is different from
76
- # deleting assets and risks
77
- return self.api.upsert('seed', dict(dns=seed['dns'], status=Seed.DELETED.value))
108
+ delete_payload = {
109
+ 'key': key,
110
+ 'status': Seed.DELETED.value
111
+ }
112
+
113
+ return self.api.upsert('seed', delete_payload)
78
114
  else:
79
- error(f'Seed {key} is not found.')
115
+ error(f'Seed {key} not found.')
80
116
 
81
- def list(self, type='', prefix_filter='', offset=None, pages=100000) -> tuple:
117
+ def list(self, seed_type=Kind.SEED.value, key_prefix='', pages=100000) -> tuple:
82
118
  """
83
- List seeds with optional filtering.
84
-
85
- :param type: The type of seed to filter by ('domain', 'ip', 'cidr'). If empty, returns all seed types
86
- :type type: str
87
- :param prefix_filter: Supply this to perform prefix-filtering of the seed DNS/IP values after the seed type portion of the key
88
- :type prefix_filter: str
89
- :param offset: The offset of the page you want to retrieve results. If this is not supplied, this function retrieves from the first page
90
- :type offset: str or None
119
+ List seeds by querying assets with 'Seed' label.
120
+
121
+ :param seed_type: Optional asset seed_type filter (e.g., 'asset', 'addomain')
122
+ :seed_type seed_type: str or None
123
+ :param key_prefix: Filter by key prefix
124
+ :seed_type key_prefix: str
91
125
  :param pages: The number of pages of results to retrieve. <mcp>Start with one page of results unless specifically requested.</mcp>
92
- :type pages: int
126
+ :seed_type pages: int
93
127
  :return: A tuple containing (list of seeds, next page offset)
94
- :rtype: tuple
128
+ :rseed_type: tuple
95
129
  """
96
- prefix_term = '#seed#'
97
- if type:
98
- prefix_term = f'{prefix_term}{type}#'
99
- if prefix_filter:
100
- prefix_term = f'{prefix_term}{prefix_filter}'
101
130
 
102
- return self.api.search.by_key_prefix(prefix_term, offset, pages)
131
+ if seed_type in KIND_TO_LABEL:
132
+ seed_type = KIND_TO_LABEL[seed_type]
133
+ elif not seed_type:
134
+ seed_type = Node.Label.SEED
135
+ else:
136
+ raise ValueError(f'Invalid seed type: {seed_type}')
137
+
138
+ node = Node(
139
+ labels=[seed_type],
140
+ filters=[]
141
+ )
142
+
143
+ key_filter = Filter(
144
+ field=Filter.Field.KEY,
145
+ operator=Filter.Operator.STARTS_WITH,
146
+ value=key_prefix
147
+ )
148
+
149
+ if key_prefix:
150
+ node.filters.append(key_filter)
151
+
152
+ query = Query(node=node)
153
+
154
+ return self.api.search.by_query(query, pages)
103
155
 
104
156
  def attributes(self, key):
105
157
  """
@@ -0,0 +1,180 @@
1
+ from praetorian_cli.sdk.model.query import Query, Node, Filter, Relationship
2
+
3
+
4
+ class Webpage:
5
+ """The methods in this class are to be accessed from sdk.webpage, where sdk
6
+ is an instance of Chariot."""
7
+
8
+ def __init__(self, api):
9
+ self.api = api
10
+
11
+ def add(self, url, parent_key=None):
12
+ """
13
+ Add a Webpage to the Chariot database.
14
+
15
+ WebPages represent individual pages or endpoints that can be optionally
16
+ associated with a parent WebApplication. The backend uses a WebpageRequest
17
+ structure with embedded webpage data and optional parent key.
18
+
19
+ :param url: The full URL of the page
20
+ :type url: str
21
+ :param parent_key: Optional key of the parent WebApplication
22
+ :type webapp_key: str or None
23
+ :return: The created WebPage object
24
+ :rtype: dict
25
+ :raises Exception: If the URL is invalid or the request fails
26
+
27
+ **Example Usage:**
28
+ >>> # Add a simple page without parent
29
+ >>> page = sdk.webpage.add("https://app.example.com/login")
30
+
31
+ >>> # Add a page with parent WebApplication
32
+ >>> page = sdk.webpage.add(
33
+ ... url="https://app.example.com/admin",
34
+ ... parent_key="#webapplication#https://app.example.com")
35
+
36
+ **WebPage Object Structure:**
37
+ The returned Webpage object contains:
38
+ - key: Webpage identifier in format #webpage#{url}
39
+ - url: Full URL of the page
40
+ - status: Current status
41
+ - parent: Parent WebApplication relationship (if applicable)
42
+ - created: Creation timestamp
43
+ """
44
+ if not url:
45
+ raise Exception("URL is required for Webpage")
46
+
47
+ if parent_key and not parent_key.startswith('#webapplication#'):
48
+ raise Exception("Invalid WebApplication key format")
49
+
50
+ payload = {
51
+ 'webpage': {
52
+ 'url': url,
53
+ 'status': 'A' # Active status
54
+ }
55
+
56
+ }
57
+
58
+ if parent_key:
59
+ payload['parent_key'] = parent_key
60
+
61
+ return self.api.post('webpage', payload)
62
+
63
+ def get(self, key):
64
+ """
65
+ Get details of a specific Webpage by its key.
66
+
67
+ :param key: The WebPage key identifier
68
+ :type key: str
69
+ :return: Webpage object with detailed information, or None if not found
70
+ :rtype: dict or None
71
+
72
+ **Example Usage:**
73
+ >>> # Get a specific Webpage
74
+ >>> page = sdk.webpage.get("webpage_key_123")
75
+
76
+ **Webpage Object Structure:**
77
+ The returned Webpage object contains:
78
+ - key: Webpage identifier
79
+ - url: Full URL of the page
80
+ - created: Creation timestamp
81
+ - updated: Last update timestamp
82
+ """
83
+ query = Query(node=Node(labels=[Node.Label.WEBPAGE], filters=[Filter(field=Filter.Field.KEY, operator=Filter.Operator.EQUAL, value=key)]))
84
+ return self.api.search.by_query(query)[0][0]
85
+
86
+ def list(self, parent_key=None, filter=None, offset=0, pages=100000) -> tuple:
87
+ """
88
+ List Webpages, optionally filtered by parent WebApplication.
89
+
90
+ Retrieve Webpage entities with optional filtering capabilities. Can filter by
91
+ parent WebApplication.
92
+
93
+ :param parent_key: Filter pages by specific WebApplication (optional)
94
+ :type parent_key: str or None
95
+ :param filter: Filter pages by specific URL (optional)
96
+ :type filter: str or None
97
+ :param offset: The offset for pagination to retrieve a specific page of results
98
+ :type offset: str or None
99
+ :param pages: Maximum number of pages to retrieve (default: 100000 for all results)
100
+ :type pages: int
101
+ :return: A tuple containing (list of matching Webpages, next page offset)
102
+ :rtype: tuple
103
+
104
+ **Example Usage:**
105
+ >>> # List all WebPages
106
+ >>> pages, offset = sdk.webpage.list()
107
+
108
+ >>> # List pages for specific WebApplication
109
+ >>> pages, offset = sdk.webpage.list(
110
+ ... webapp_key="#asset#webapp#https://app.example.com#https://app.example.com")
111
+
112
+ **WebPage Filtering:**
113
+ - parent_key: Filters by parent WebApplication
114
+ - filter: Filters by specific URL
115
+ """
116
+ if parent_key and not parent_key.startswith('#webapplication#'):
117
+ raise Exception("Invalid WebApplication key format")
118
+
119
+ relationships = []
120
+ filters = []
121
+ if parent_key:
122
+ parentFilter = Filter(field=Filter.Field.KEY, operator=Filter.Operator.EQUAL, value=parent_key)
123
+ relationship = Relationship(label=Relationship.Label.HAS_WEBPAGE, target=Node(labels=[Node.Label.WEBAPPLICATION], filters=[parentFilter]))
124
+ relationships.append(relationship)
125
+ if filter:
126
+ urlFilter = Filter(field=Filter.Field.KEY, operator=Filter.Operator.CONTAINS, value=filter)
127
+ filters.append(urlFilter)
128
+ node = Node(labels=[Node.Label.WEBPAGE], filters=filters, relationships=relationships)
129
+ query = Query(node=node, page=offset, limit=pages)
130
+ return self.api.search.by_query(query, pages)
131
+
132
+ def delete(self, key):
133
+ """
134
+ Delete a webpage by its key.
135
+
136
+ :param key: The WebPage key identifier
137
+ :type key: str
138
+ """
139
+ body = {
140
+ 'webpage': {
141
+ 'key': key
142
+ }
143
+ }
144
+ self.api.delete('webpage', params={}, body=body)
145
+
146
+ def link_source(self, webpage_key, entity_key):
147
+ """
148
+ Link a file or repository to a webpage as source code.
149
+
150
+ :param webpage_key: The webpage key in format #webpage#{url}
151
+ :type webpage_key: str
152
+ :param entity_key: The entity key (file or repository) to link. Format: #file#{path} or #repository#{url}#{name}
153
+ :type entity_key: str
154
+ :return: The updated webpage with linked artifacts
155
+ :rtype: dict
156
+ """
157
+ data = {
158
+ 'webpageKey': webpage_key,
159
+ 'entityKey': entity_key
160
+ }
161
+
162
+ return self.api.put('webpage/link', data, {})
163
+
164
+ def unlink_source(self, webpage_key, entity_key):
165
+ """
166
+ Unlink a file or repository from a webpage's source code.
167
+
168
+ :param webpage_key: The webpage key in format #webpage#{url}
169
+ :type webpage_key: str
170
+ :param entity_key: The entity key (file or repository) to unlink. Format: #file#{path} or #repository#{url}#{name}
171
+ :type entity_key: str
172
+ :return: The updated webpage with artifacts removed
173
+ :rtype: dict
174
+ """
175
+ data = {
176
+ 'webpageKey': webpage_key,
177
+ 'entityKey': entity_key
178
+ }
179
+
180
+ return self.api.delete('webpage/link', data, {})
@@ -8,7 +8,6 @@ from mcp.server.lowlevel import Server
8
8
  from mcp.server.stdio import stdio_server
9
9
  from mcp.types import Tool, TextContent
10
10
 
11
-
12
11
  class MCPServer:
13
12
  def __init__(self, chariot_instance, allowable_tools: Optional[List[str]] = None):
14
13
  self.chariot = chariot_instance
@@ -157,7 +156,7 @@ class MCPServer:
157
156
  tools = []
158
157
  for tool_name, tool_info in self.discovered_tools.items():
159
158
  parameters = self._extract_parameters_from_doc(tool_info['doc'], tool_info['signature'])
160
-
159
+
161
160
  properties = {}
162
161
  required = []
163
162
 
@@ -170,7 +169,7 @@ class MCPServer:
170
169
  "description": param_info["description"]
171
170
  }
172
171
 
173
- if param_info["required"]:
172
+ if param_info.get("required", False):
174
173
  required.append(param_name)
175
174
 
176
175
  tool_schema = {
@@ -108,6 +108,8 @@ class Kind(Enum):
108
108
  SEED = 'seed'
109
109
  PRESEED = 'preseed'
110
110
  OTHERS = 'others'
111
+ WEBAPPLICATION = 'webapplication'
112
+ WEBPAGE = 'webpage'
111
113
 
112
114
  EXACT_FLAG = {'exact': 'true'}
113
115
  DESCENDING_FLAG = {'desc': 'true'}
@@ -49,6 +49,8 @@ class Filter:
49
49
  KEV = 'kev'
50
50
  EXPLOIT = 'exploit'
51
51
  PRIVATE = 'private'
52
+ PRIMARY_URL = 'primary_url'
53
+ URL = 'url'
52
54
 
53
55
  def __init__(self, field: Field, operator: Operator, value: str, not_: bool = False):
54
56
  self.field = field
@@ -65,6 +67,7 @@ class Relationship:
65
67
  HAS_VULNERABILITY = 'HAS_VULNERABILITY'
66
68
  DISCOVERED = 'DISCOVERED'
67
69
  HAS_ATTRIBUTE = 'HAS_ATTRIBUTE'
70
+ HAS_WEBPAGE = 'HAS_WEBPAGE'
68
71
 
69
72
  def __init__(self, label: Label, source: 'Node' = None, target: 'Node' = None, optional: bool = False, length: int = 0):
70
73
  self.label = label
@@ -91,12 +94,14 @@ class Node:
91
94
  ASSET = 'Asset'
92
95
  REPOSITORY = 'Repository'
93
96
  INTEGRATION = 'Integration'
94
- ADDOMAIN = 'Addomain'
97
+ ADDOMAIN = 'ADDomain'
95
98
  ATTRIBUTE = 'Attribute'
96
99
  RISK = 'Risk'
97
100
  PRESEED = 'Preseed'
98
101
  SEED = 'Seed'
99
102
  TTL = 'TTL'
103
+ WEBAPPLICATION = 'WebApplication'
104
+ WEBPAGE = 'Webpage'
100
105
 
101
106
  def __init__(self, labels: list[Label] = None, filters: list[Filter] = None,
102
107
  relationships: list[Relationship] = None):
@@ -157,6 +162,8 @@ KIND_TO_LABEL = {
157
162
  Kind.REPOSITORY.value: Node.Label.REPOSITORY,
158
163
  Kind.INTEGRATION.value: Node.Label.INTEGRATION,
159
164
  Kind.ADDOMAIN.value: Node.Label.ADDOMAIN,
165
+ Kind.WEBAPPLICATION.value: Node.Label.WEBAPPLICATION,
166
+ Kind.WEBPAGE.value: Node.Label.WEBPAGE,
160
167
  }
161
168
 
162
169
 
@@ -15,14 +15,11 @@ def integration_key(dns, name):
15
15
  def risk_key(dns, name):
16
16
  return f'#risk#{dns}#{name}'
17
17
 
18
-
19
18
  def attribute_key(name, value, source_key):
20
19
  return f'#attribute#{name}#{value}{source_key}'
21
20
 
22
-
23
- def seed_key(type, dns):
24
- return f'#seed#{type}#{dns}'
25
-
21
+ def seed_asset_key(dns):
22
+ return f'#asset#{dns}#{dns}'
26
23
 
27
24
  def preseed_key(type, title, value):
28
25
  return f'#preseed#{type}#{title}#{value}'
@@ -32,6 +29,3 @@ def setting_key(name):
32
29
 
33
30
  def configuration_key(name):
34
31
  return f'#configuration#{name}'
35
-
36
- def seed_status(type, status_code):
37
- return f'{type}#{status_code}'
@@ -42,7 +42,7 @@ class TestAsset:
42
42
  assert any([a['group'] == self.asset_dns for a in deleted_assets])
43
43
 
44
44
  def test_add_ad_domain(self):
45
- asset = self.sdk.assets.add(self.ad_domain_name, self.ad_domain_name, status=Asset.ACTIVE.value, surface='test-surface', type=Kind.ADDOMAIN.value)
45
+ asset = self.sdk.assets.add(self.ad_domain_name, self.ad_object_id, status=Asset.ACTIVE.value, surface='test-surface', type=Kind.ADDOMAIN.value)
46
46
  assert asset['key'] == self.ad_domain_key
47
47
  assert len(asset['attackSurface']) == 1
48
48
  assert 'test-surface' in asset['attackSurface']
@@ -51,7 +51,7 @@ class TestAsset:
51
51
  def test_get_ad_domain(self):
52
52
  asset = self.sdk.assets.get(self.ad_domain_key)
53
53
  assert asset['key'] == self.ad_domain_key
54
- assert asset['name'] == self.ad_domain_name
54
+ assert asset['domain'] == self.ad_domain_name
55
55
  assert asset['status'] == Asset.ACTIVE.value
56
56
 
57
57
  def test_list_ad_domain(self):
@@ -71,11 +71,47 @@ class TestAsset:
71
71
  deleted_assets, _ = self.sdk.search.by_status(Asset.DELETED.value, Kind.ADDOMAIN.value)
72
72
  assert any([a['key'] == self.ad_domain_key for a in deleted_assets])
73
73
 
74
+ def test_add_webapplication(self):
75
+ asset = self.sdk.assets.add(self.webapp_name, self.webapp_url, status=Asset.ACTIVE.value, surface='test-surface', type=Kind.WEBAPPLICATION.value)
76
+ assert asset['key'] == self.webapp_key
77
+ assert len(asset['attackSurface']) == 1
78
+ assert 'test-surface' in asset['attackSurface']
79
+ assert asset['status'] == Asset.ACTIVE.value
80
+
81
+ def test_get_webapplication(self):
82
+ asset = self.sdk.assets.get(self.webapp_key)
83
+ assert asset['key'] == self.webapp_key
84
+ assert asset['group'] == self.webapp_name
85
+ assert asset['identifier'] == self.webapp_url
86
+ assert asset['status'] == Asset.ACTIVE.value
87
+
88
+ def test_list_webapplication(self):
89
+ results, _ = self.sdk.assets.list(asset_type=Kind.WEBAPPLICATION.value)
90
+ assert len(results) > 0
91
+ assert any([a['key'] == self.webapp_key for a in results])
92
+ assert any([a['group'] == self.webapp_name for a in results])
93
+ assert any([a['identifier'] == self.webapp_url for a in results])
94
+
95
+ def test_update_webapplication(self):
96
+ self.sdk.assets.update(self.webapp_key, status=Asset.FROZEN.value, surface='abc')
97
+ asset = self.get_webapplication()
98
+ assert asset['status'] == Asset.FROZEN.value
99
+ assert 'abc' in asset['attackSurface']
100
+
101
+ def test_delete_webapplication(self):
102
+ self.sdk.assets.delete(self.webapp_key)
103
+ assert self.get_webapplication()['status'] == Asset.DELETED.value
104
+ deleted_assets, _ = self.sdk.search.by_status(Asset.DELETED.value, Kind.WEBAPPLICATION.value)
105
+ assert any([a['key'] == self.webapp_key for a in deleted_assets])
106
+
74
107
  def get_asset(self):
75
108
  return self.sdk.assets.get(self.asset_key)
76
109
 
77
110
  def get_ad_domain(self):
78
111
  return self.sdk.assets.get(self.ad_domain_key)
79
112
 
113
+ def get_webapplication(self):
114
+ return self.sdk.assets.get(self.webapp_key)
115
+
80
116
  def teardown_class(self):
81
117
  clean_test_entities(self.sdk, self)
@@ -1,7 +1,6 @@
1
1
  import pytest
2
2
 
3
- from praetorian_cli.sdk.model.globals import Seed
4
- from praetorian_cli.sdk.model.utils import seed_status
3
+ from praetorian_cli.sdk.model.globals import Seed, Kind
5
4
  from praetorian_cli.sdk.test.utils import make_test_values, clean_test_entities, setup_chariot
6
5
 
7
6
 
@@ -12,30 +11,30 @@ class TestSeed:
12
11
  self.sdk = setup_chariot()
13
12
  make_test_values(self)
14
13
 
15
- def test_add_seed(self):
16
- seed = self.sdk.seeds.add(self.seed_dns)
17
- assert seed['key'] == self.seed_key
14
+ def test_add_asset_seed(self):
15
+ seed = self.sdk.seeds.add(dns=self.seed_asset_dns)
16
+ assert seed['key'] == self.seed_asset_key
18
17
 
19
18
  def test_get_seed(self):
20
19
  a = self.get_seed()
21
- assert a['dns'] == self.seed_dns
22
- assert a['status'] == seed_status('domain', Seed.PENDING.value)
20
+ assert a['dns'] == self.seed_asset_dns
21
+ assert a['status'] == Seed.PENDING.value
23
22
 
24
23
  def test_list_seed(self):
25
- results, _ = self.sdk.seeds.list('domain', self.seed_dns)
24
+ results, _ = self.sdk.seeds.list(Kind.ASSET.value, f"#asset#{self.seed_asset_dns}")
26
25
  assert len(results) == 1
27
- assert results[0]['dns'] == self.seed_dns
26
+ assert results[0]['dns'] == self.seed_asset_dns
28
27
 
29
28
  def test_update_seed(self):
30
- self.sdk.seeds.update(self.seed_key, Seed.ACTIVE.value)
31
- assert self.get_seed()['status'] == seed_status('domain', Seed.ACTIVE.value)
29
+ self.sdk.seeds.update(self.seed_asset_key, Seed.ACTIVE.value)
30
+ assert self.get_seed()['status'] == Seed.ACTIVE.value
32
31
 
33
32
  def test_delete_seed(self):
34
- self.sdk.seeds.delete(self.seed_key)
35
- assert self.sdk.seeds.get(self.seed_key)['status'] == seed_status('domain', Seed.DELETED.value)
33
+ self.sdk.seeds.delete(self.seed_asset_key)
34
+ assert self.sdk.seeds.get(self.seed_asset_key)['status'] == Seed.DELETED.value
36
35
 
37
36
  def get_seed(self):
38
- return self.sdk.seeds.get(self.seed_key)
37
+ return self.sdk.seeds.get(self.seed_asset_key)
39
38
 
40
39
  def teardown_class(self):
41
40
  clean_test_entities(self.sdk, self)
@@ -0,0 +1,46 @@
1
+ import pytest
2
+
3
+ from praetorian_cli.sdk.test.utils import make_test_values, setup_chariot
4
+
5
+
6
+ @pytest.mark.coherence
7
+ class TestWebpage:
8
+ """Test suite for the Webpage entity class."""
9
+
10
+ def setup_class(self):
11
+ self.sdk = setup_chariot()
12
+ make_test_values(self)
13
+
14
+ def test_add_webpage(self):
15
+ """Test adding a Webpage with URL provided."""
16
+ result = self.sdk.webpage.add(self.webpage_url)
17
+
18
+ assert result is not None
19
+ webpage = result.get('webpages')[0]
20
+ assert webpage.get('key') == self.webpage_key
21
+ assert webpage.get('url') == self.webpage_url
22
+
23
+ def test_get_webpage(self):
24
+ """Test retrieving a Webpage by key."""
25
+ result = self.sdk.webpage.get(self.webpage_key)
26
+ assert result is not None
27
+ assert result.get('key') == self.webpage_key
28
+ assert result.get('url') == self.webpage_url
29
+
30
+ def test_list_webpages(self):
31
+ """Test listing Webpages."""
32
+ results, offset = self.sdk.webpage.list(filter=self.webpage_url[:len(self.webpage_url)//2])
33
+ assert isinstance(results, list)
34
+ assert len(results) > 0
35
+ assert any(r.get('key') == self.webpage_key for r in results)
36
+ assert any(r.get('url') == self.webpage_url for r in results)
37
+
38
+ def test_add_webpage_empty_url_raises_exception(self):
39
+ """Test that adding a Webpage with empty URL raises an exception."""
40
+ with pytest.raises(Exception, match="URL is required for Webpage"):
41
+ self.sdk.webpage.add("")
42
+
43
+ def test_add_webpage_none_url_raises_exception(self):
44
+ """Test that adding a Webpage with None URL raises an exception."""
45
+ with pytest.raises(Exception, match="URL is required for Webpage"):
46
+ self.sdk.webpage.add(None)