pyxecm 2.0.0__py3-none-any.whl → 2.0.1__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.

Potentially problematic release.


This version of pyxecm might be problematic. Click here for more details.

Files changed (50) hide show
  1. pyxecm/__init__.py +2 -1
  2. pyxecm/avts.py +79 -33
  3. pyxecm/customizer/api/app.py +45 -796
  4. pyxecm/customizer/api/auth/__init__.py +1 -0
  5. pyxecm/customizer/api/{auth.py → auth/functions.py} +2 -64
  6. pyxecm/customizer/api/auth/router.py +78 -0
  7. pyxecm/customizer/api/common/__init__.py +1 -0
  8. pyxecm/customizer/api/common/functions.py +47 -0
  9. pyxecm/customizer/api/{metrics.py → common/metrics.py} +1 -1
  10. pyxecm/customizer/api/common/models.py +21 -0
  11. pyxecm/customizer/api/{payload_list.py → common/payload_list.py} +6 -1
  12. pyxecm/customizer/api/common/router.py +72 -0
  13. pyxecm/customizer/api/settings.py +25 -0
  14. pyxecm/customizer/api/terminal/__init__.py +1 -0
  15. pyxecm/customizer/api/terminal/router.py +87 -0
  16. pyxecm/customizer/api/v1_csai/__init__.py +1 -0
  17. pyxecm/customizer/api/v1_csai/router.py +87 -0
  18. pyxecm/customizer/api/v1_maintenance/__init__.py +1 -0
  19. pyxecm/customizer/api/v1_maintenance/functions.py +100 -0
  20. pyxecm/customizer/api/v1_maintenance/models.py +12 -0
  21. pyxecm/customizer/api/v1_maintenance/router.py +76 -0
  22. pyxecm/customizer/api/v1_otcs/__init__.py +1 -0
  23. pyxecm/customizer/api/v1_otcs/functions.py +61 -0
  24. pyxecm/customizer/api/v1_otcs/router.py +179 -0
  25. pyxecm/customizer/api/v1_payload/__init__.py +1 -0
  26. pyxecm/customizer/api/v1_payload/functions.py +179 -0
  27. pyxecm/customizer/api/v1_payload/models.py +51 -0
  28. pyxecm/customizer/api/v1_payload/router.py +499 -0
  29. pyxecm/customizer/browser_automation.py +568 -326
  30. pyxecm/customizer/customizer.py +204 -430
  31. pyxecm/customizer/guidewire.py +907 -43
  32. pyxecm/customizer/k8s.py +243 -56
  33. pyxecm/customizer/m365.py +104 -15
  34. pyxecm/customizer/payload.py +1943 -885
  35. pyxecm/customizer/pht.py +19 -2
  36. pyxecm/customizer/servicenow.py +22 -5
  37. pyxecm/customizer/settings.py +9 -6
  38. pyxecm/helper/xml.py +69 -0
  39. pyxecm/otac.py +1 -1
  40. pyxecm/otawp.py +2104 -1535
  41. pyxecm/otca.py +569 -0
  42. pyxecm/otcs.py +201 -37
  43. pyxecm/otds.py +35 -13
  44. {pyxecm-2.0.0.dist-info → pyxecm-2.0.1.dist-info}/METADATA +6 -29
  45. pyxecm-2.0.1.dist-info/RECORD +76 -0
  46. {pyxecm-2.0.0.dist-info → pyxecm-2.0.1.dist-info}/WHEEL +1 -1
  47. pyxecm-2.0.0.dist-info/RECORD +0 -54
  48. /pyxecm/customizer/api/{models.py → auth/models.py} +0 -0
  49. {pyxecm-2.0.0.dist-info → pyxecm-2.0.1.dist-info}/licenses/LICENSE +0 -0
  50. {pyxecm-2.0.0.dist-info → pyxecm-2.0.1.dist-info}/top_level.txt +0 -0
pyxecm/otawp.py CHANGED
@@ -8,37 +8,63 @@ __email__ = "mdiefenb@opentext.com"
8
8
 
9
9
  import json
10
10
  import logging
11
+ import platform
11
12
  import re
13
+ import sys
12
14
  import time
13
15
  import uuid
14
- import xml.etree.ElementTree as ET
16
+ from http import HTTPStatus
17
+ from importlib.metadata import version
15
18
 
16
19
  import requests
17
20
 
21
+ from pyxecm.helper.xml import XML
18
22
  from pyxecm.otds import OTDS
19
23
 
20
- default_logger = logging.getLogger("pyxecm.otawp")
24
+ APP_NAME = "pyxecm"
25
+ APP_VERSION = version("pyxecm")
26
+ MODULE_NAME = APP_NAME + ".otawp"
21
27
 
22
- REQUEST_HEADERS = {
28
+ PYTHON_VERSION = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
29
+ OS_INFO = f"{platform.system()} {platform.release()}"
30
+ ARCH_INFO = platform.machine()
31
+ REQUESTS_VERSION = requests.__version__
32
+
33
+ USER_AGENT = (
34
+ f"{APP_NAME}/{APP_VERSION} ({MODULE_NAME}/{APP_VERSION}; "
35
+ f"Python/{PYTHON_VERSION}; {OS_INFO}; {ARCH_INFO}; Requests/{REQUESTS_VERSION})"
36
+ )
37
+
38
+ REQUEST_HEADERS_XML = {
39
+ "User-Agent": USER_AGENT,
23
40
  "Content-Type": "text/xml; charset=utf-8",
24
41
  "accept": "application/xml",
25
42
  }
26
43
 
27
44
  REQUEST_FORM_HEADERS = {
45
+ "User-Agent": USER_AGENT,
28
46
  "accept": "application/xml;charset=utf-8",
29
47
  "Content-Type": "application/x-www-form-urlencoded",
30
48
  }
31
49
 
32
50
  REQUEST_HEADERS_JSON = {
51
+ "User-Agent": USER_AGENT,
33
52
  "Content-Type": "application/json; charset=utf-8",
34
53
  "accept": "application/json",
35
54
  }
55
+
36
56
  REQUEST_TIMEOUT = 120
57
+ REQUEST_MAX_RETRIES = 10
58
+ REQUEST_RETRY_DELAY = 30
37
59
  SYNC_PUBLISH_REQUEST_TIMEOUT = 300
38
60
 
61
+ default_logger = logging.getLogger(MODULE_NAME)
62
+
63
+ SOAP_FAULT_INDICATOR = "Fault"
64
+
39
65
 
40
66
  class OTAWP:
41
- """Class OTRAWP is used to automate settings in OpenText AppWorks Platform (OTAWP)."""
67
+ """Class OTAWP is used to automate settings in OpenText AppWorks Platform (OTAWP)."""
42
68
 
43
69
  logger: logging.Logger = default_logger
44
70
 
@@ -47,6 +73,241 @@ class OTAWP:
47
73
  _cookie = None
48
74
  _otawp_ticket = None
49
75
 
76
+ @classmethod
77
+ def resource_payload(
78
+ cls,
79
+ org_name: str,
80
+ username: str,
81
+ password: str,
82
+ ) -> dict:
83
+ """Create data structure for OTDS resource settings we need for AppWorks.
84
+
85
+ Args:
86
+ org_name (str):
87
+ The name of the organization.
88
+ username (str):
89
+ The user name.
90
+ password (str):
91
+ The password.
92
+
93
+ Returns:
94
+ dict:
95
+ AppWorks specific payload.
96
+
97
+ """
98
+
99
+ additional_payload = {}
100
+ additional_payload["connectorid"] = "rest"
101
+ additional_payload["resourceType"] = "rest"
102
+ user_attribute_mapping = [
103
+ {
104
+ "sourceAttr": ["oTExternalID1"],
105
+ "destAttr": "__NAME__",
106
+ "mappingFormat": "%s",
107
+ },
108
+ {
109
+ "sourceAttr": ["displayname"],
110
+ "destAttr": "DisplayName",
111
+ "mappingFormat": "%s",
112
+ },
113
+ {"sourceAttr": ["mail"], "destAttr": "Email", "mappingFormat": "%s"},
114
+ {
115
+ "sourceAttr": ["oTTelephoneNumber"],
116
+ "destAttr": "Telephone",
117
+ "mappingFormat": "%s",
118
+ },
119
+ {
120
+ "sourceAttr": ["oTMobile"],
121
+ "destAttr": "Mobile",
122
+ "mappingFormat": "%s",
123
+ },
124
+ {
125
+ "sourceAttr": ["oTFacsimileTelephoneNumber"],
126
+ "destAttr": "Fax",
127
+ "mappingFormat": "%s",
128
+ },
129
+ {
130
+ "sourceAttr": ["oTStreetAddress,l,st,postalCode,c"],
131
+ "destAttr": "Address",
132
+ "mappingFormat": "%s%n%s %s %s%n%s",
133
+ },
134
+ {
135
+ "sourceAttr": ["oTCompany"],
136
+ "destAttr": "Company",
137
+ "mappingFormat": "%s",
138
+ },
139
+ {
140
+ "sourceAttr": ["ds-pwp-account-disabled"],
141
+ "destAttr": "AccountDisabled",
142
+ "mappingFormat": "%s",
143
+ },
144
+ {
145
+ "sourceAttr": ["oTExtraAttr9"],
146
+ "destAttr": "IsServiceAccount",
147
+ "mappingFormat": "%s",
148
+ },
149
+ {
150
+ "sourceAttr": ["custom:proxyConfiguration"],
151
+ "destAttr": "ProxyConfiguration",
152
+ "mappingFormat": "%s",
153
+ },
154
+ {
155
+ "sourceAttr": ["c"],
156
+ "destAttr": "Identity-CountryOrRegion",
157
+ "mappingFormat": "%s",
158
+ },
159
+ {
160
+ "sourceAttr": ["gender"],
161
+ "destAttr": "Identity-Gender",
162
+ "mappingFormat": "%s",
163
+ },
164
+ {
165
+ "sourceAttr": ["displayName"],
166
+ "destAttr": "Identity-DisplayName",
167
+ "mappingFormat": "%s",
168
+ },
169
+ {
170
+ "sourceAttr": ["oTStreetAddress"],
171
+ "destAttr": "Identity-Address",
172
+ "mappingFormat": "%s",
173
+ },
174
+ {
175
+ "sourceAttr": ["l"],
176
+ "destAttr": "Identity-City",
177
+ "mappingFormat": "%s",
178
+ },
179
+ {
180
+ "sourceAttr": ["mail"],
181
+ "destAttr": "Identity-Email",
182
+ "mappingFormat": "%s",
183
+ },
184
+ {
185
+ "sourceAttr": ["givenName"],
186
+ "destAttr": "Identity-FirstName",
187
+ "mappingFormat": "%s",
188
+ },
189
+ {
190
+ "sourceAttr": ["sn"],
191
+ "destAttr": "Identity-LastName",
192
+ "mappingFormat": "%s",
193
+ },
194
+ {
195
+ "sourceAttr": ["initials"],
196
+ "destAttr": "Identity-MiddleNames",
197
+ "mappingFormat": "%s",
198
+ },
199
+ {
200
+ "sourceAttr": ["oTMobile"],
201
+ "destAttr": "Identity-Mobile",
202
+ "mappingFormat": "%s",
203
+ },
204
+ {
205
+ "sourceAttr": ["postalCode"],
206
+ "destAttr": "Identity-PostalCode",
207
+ "mappingFormat": "%s",
208
+ },
209
+ {
210
+ "sourceAttr": ["st"],
211
+ "destAttr": "Identity-StateOrProvince",
212
+ "mappingFormat": "%s",
213
+ },
214
+ {
215
+ "sourceAttr": ["title"],
216
+ "destAttr": "Identity-title",
217
+ "mappingFormat": "%s",
218
+ },
219
+ {
220
+ "sourceAttr": ["physicalDeliveryOfficeName"],
221
+ "destAttr": "Identity-physicalDeliveryOfficeName",
222
+ "mappingFormat": "%s",
223
+ },
224
+ {
225
+ "sourceAttr": ["oTFacsimileTelephoneNumber"],
226
+ "destAttr": "Identity-oTFacsimileTelephoneNumber",
227
+ "mappingFormat": "%s",
228
+ },
229
+ {
230
+ "sourceAttr": ["notes"],
231
+ "destAttr": "Identity-notes",
232
+ "mappingFormat": "%s",
233
+ },
234
+ {
235
+ "sourceAttr": ["oTCompany"],
236
+ "destAttr": "Identity-oTCompany",
237
+ "mappingFormat": "%s",
238
+ },
239
+ {
240
+ "sourceAttr": ["oTDepartment"],
241
+ "destAttr": "Identity-oTDepartment",
242
+ "mappingFormat": "%s",
243
+ },
244
+ {
245
+ "sourceAttr": ["birthDate"],
246
+ "destAttr": "Identity-Birthday",
247
+ "mappingFormat": "%s",
248
+ },
249
+ {
250
+ "sourceAttr": ["cn"],
251
+ "destAttr": "Identity-UserName",
252
+ "mappingFormat": "%s",
253
+ },
254
+ {
255
+ "sourceAttr": ["Description"],
256
+ "destAttr": "Identity-UserDescription",
257
+ "mappingFormat": "%s",
258
+ },
259
+ {
260
+ "sourceAttr": ["oTTelephoneNumber"],
261
+ "destAttr": "Identity-Phone",
262
+ "mappingFormat": "%s",
263
+ },
264
+ {
265
+ "sourceAttr": ["displayName"],
266
+ "destAttr": "Identity-IdentityDisplayName",
267
+ "mappingFormat": "%s",
268
+ },
269
+ ]
270
+ additional_payload["userAttributeMapping"] = user_attribute_mapping
271
+ group_attribute_mapping = [
272
+ {
273
+ "sourceAttr": ["cn"],
274
+ "destAttr": "__NAME__",
275
+ "mappingFormat": '%js:function format(name) { return name.replace(/&/g,"-and-"); }',
276
+ },
277
+ {
278
+ "sourceAttr": ["description"],
279
+ "destAttr": "Description",
280
+ "mappingFormat": "%s",
281
+ },
282
+ {
283
+ "sourceAttr": ["description"],
284
+ "destAttr": "Identity-Description",
285
+ "mappingFormat": "%s",
286
+ },
287
+ {
288
+ "sourceAttr": ["displayName"],
289
+ "destAttr": "Identity-DisplayName",
290
+ "mappingFormat": "%s",
291
+ },
292
+ ]
293
+ additional_payload["groupAttributeMapping"] = group_attribute_mapping
294
+ additional_payload["connectorName"] = "REST (Generic)"
295
+ additional_payload["pcCreatePermissionAllowed"] = "true"
296
+ additional_payload["pcModifyPermissionAllowed"] = "true"
297
+ additional_payload["pcDeletePermissionAllowed"] = "false"
298
+ additional_payload["connectionParamInfo"] = [
299
+ {
300
+ "name": "fBaseURL",
301
+ "value": "http://appworks:8080/home/" + org_name + "/app/otdspush",
302
+ },
303
+ {"name": "fUsername", "value": username},
304
+ {"name": "fPassword", "value": password},
305
+ ]
306
+
307
+ return additional_payload
308
+
309
+ # end method definition
310
+
50
311
  def __init__(
51
312
  self,
52
313
  protocol: str,
@@ -54,14 +315,9 @@ class OTAWP:
54
315
  port: int,
55
316
  username: str | None = None,
56
317
  password: str | None = None,
318
+ organization: str | None = None,
57
319
  otawp_ticket: str | None = None,
58
- otcs_partition_name: str | None = None,
59
- otds_admin_partition_mame: str | None = None,
60
320
  config_map_name: str | None = None,
61
- otcs_resource_id: str | None = None,
62
- otds_url: str | None = None,
63
- otcs_url: str | None = None,
64
- otcs_base_path: str | None = None,
65
321
  license_file: str | None = None,
66
322
  product_name: str | None = None,
67
323
  product_description: str | None = None,
@@ -70,23 +326,30 @@ class OTAWP:
70
326
  """Initialize OTAWP (AppWorks Platform) object.
71
327
 
72
328
  Args:
73
- protocol (str): #TODO _description_
74
- hostname (str): #TODO _description_
75
- port (int): #TODO _description_
76
- username (str | None, optional): #TODO _description_. Defaults to None.
77
- password (str | None, optional): #TODO _description_. Defaults to None.
78
- otawp_ticket (str | None, optional): #TODO _description_. Defaults to None.
79
- otcs_partition_name (str | None, optional): #TODO _description_. Defaults to None.
80
- otds_admin_partition_mame (str | None, optional): #TODO _description_. Defaults to None.
81
- config_map_name (str | None, optional): #TODO _description_. Defaults to None.
82
- otcs_resource_id (str | None, optional): #TODO _description_. Defaults to None.
83
- otds_url (str | None, optional): #TODO _description_. Defaults to None.
84
- otcs_url (str | None, optional): #TODO _description_. Defaults to None.
85
- otcs_base_path (str | None, optional): #TODO _description_. Defaults to None.
86
- license_file (str | None, optional): #TODO _description_. Defaults to None.
87
- product_name (str | None, optional): #TODO _description_. Defaults to None.
88
- product_description (str | None, optional): #TODO _description_. Defaults to None.
89
- logger (logging.Logger, optional): #TODO: _description_. Defaults to default_logger.
329
+ protocol (str):
330
+ Either http or https.
331
+ hostname (str):
332
+ The hostname of Extended ECM server to communicate with.
333
+ port (int):
334
+ The port number used to talk to the Extended ECM server.
335
+ username (str, optional):
336
+ The admin user name of OTAWP. Optional if otawp_ticket is provided.
337
+ password (str, optional):
338
+ The admin password of OTAWP. Optional if otawp_ticket is provided.
339
+ organization (str, optional):
340
+ The AppWorks organization. Used in LDAP strings and base URL.
341
+ otawp_ticket (str, optional):
342
+ The authentication ticket of OTAWP.
343
+ config_map_name (str | None, optional):
344
+ The AppWorks Kubernetes Config Map name. Defaults to None.
345
+ license_file (str | None, optional):
346
+ The file name and path to the license file for AppWorks. Defaults to None.
347
+ product_name (str | None, optional):
348
+ The product name for OTAWP used in the OTDS license. Defaults to None.
349
+ product_description (str | None, optional):
350
+ The product description in the OTDS license. Defaults to None.
351
+ logger (logging.Logger, optional):
352
+ The logging object to use for all log messages. Defaults to default_logger.
90
353
 
91
354
  """
92
355
 
@@ -102,94 +365,32 @@ class OTAWP:
102
365
  otawp_config["port"] = port if port else 8080
103
366
  otawp_config["username"] = username if username else "sysadmin"
104
367
  otawp_config["password"] = password if password else ""
105
- otawp_config["otcs_partition_name"] = otcs_partition_name if otcs_partition_name else ""
106
- otawp_config["otds_admin_partition_mame"] = otds_admin_partition_mame if otds_admin_partition_mame else ""
107
- otawp_config["config_map_name"] = config_map_name if config_map_name else ""
108
- otawp_config["otcs_resource_id"] = otcs_resource_id if otcs_resource_id else ""
109
- otawp_config["otds_url"] = otds_url if otds_url else ""
110
- otawp_config["otcs_url"] = otcs_url if otcs_url else ""
111
- otawp_config["otcs_base_path"] = otcs_base_path if otcs_base_path else ""
112
- otawp_config["license_file"] = license_file if license_file else ""
113
- otawp_config["product_name"] = product_name if product_name else "APPWORKS_PLATFORM"
114
- otawp_config["product_description"] = (
368
+ otawp_config["organization"] = organization if organization else "system"
369
+ otawp_config["configMapName"] = config_map_name if config_map_name else ""
370
+ otawp_config["licenseFile"] = license_file if license_file else ""
371
+ otawp_config["productName"] = product_name if product_name else "APPWORKS_PLATFORM"
372
+ otawp_config["productDescription"] = (
115
373
  product_description if product_description else "OpenText Appworks Platform"
116
374
  )
117
375
 
118
376
  if otawp_ticket:
377
+ self._otawp_ticket = otawp_ticket
119
378
  self._cookie = {"defaultinst_SAMLart": otawp_ticket}
120
379
 
121
- server = "{}://{}".format(protocol, otawp_config["hostname"])
380
+ server_url = "{}://{}".format(protocol, otawp_config["hostname"])
122
381
  if str(port) not in ["80", "443"]:
123
- server += f":{port}"
124
-
125
- otawp_base_url = server + "/home/system"
126
-
127
- otawp_config["server"] = server if server else "http://appworks"
128
-
129
- otawp_config["gatewayAuthenticationUrl"] = (
130
- otawp_base_url
131
- + "/com.eibus.web.soap.Gateway.wcp?organization=o=system,cn=cordys,cn=defaultInst,o=opentext.net"
132
- )
133
-
134
- otawp_config["soapGatewayUrl"] = (
135
- otawp_base_url
136
- + "/com.eibus.web.soap.Gateway.wcp?organization=o=system,cn=cordys,cn=defaultInst,o=opentext.net&defaultinst_ct=abcd"
137
- )
382
+ server_url += ":{}".format(port)
138
383
 
139
- otawp_config["createPriority"] = (
140
- otawp_base_url + "/app/entityRestService/api/OpentextCaseManagement/entities/Priority?defaultinst_ct=abcd"
141
- )
142
- otawp_config["getAllPriorities"] = (
143
- otawp_base_url + "/app/entityRestService/api/OpentextCaseManagement/entities/Priority/lists/PriorityList"
144
- )
145
-
146
- otawp_config["createCustomer"] = (
147
- otawp_base_url + "/app/entityRestService/api/OpentextCaseManagement/entities/Customer?defaultinst_ct=abcd"
148
- )
149
- otawp_config["getAllCustomers"] = (
150
- otawp_base_url + "/app/entityRestService/api/OpentextCaseManagement/entities/Customer/lists/CustomerList"
151
- )
152
-
153
- otawp_config["createCaseType"] = (
154
- otawp_base_url + "/app/entityRestService/api/OpentextCaseManagement/entities/CaseType?defaultinst_ct=abcd"
155
- )
156
- otawp_config["getAllCaseTypes"] = (
157
- otawp_base_url + "/app/entityRestService/api/OpentextCaseManagement/entities/CaseType/lists/AllCaseTypes"
158
- )
159
-
160
- otawp_config["createCategory"] = (
161
- otawp_base_url + "/app/entityRestService/api/OpentextCaseManagement/entities/Category?defaultinst_ct=abcd"
162
- )
163
- otawp_config["getAllCategories"] = (
164
- otawp_base_url + "/app/entityRestService/api/OpentextCaseManagement/entities/Category/lists/CategoryList"
165
- )
166
-
167
- otawp_config["createSource"] = (
168
- otawp_base_url + "/app/entityRestService/api/OpentextCaseManagement/entities/Source"
169
- )
170
-
171
- otawp_config["getAllSources"] = (
172
- otawp_base_url + "/app/entityRestService/api/OpentextCaseManagement/entities/Source/lists/AllSources"
173
- )
174
-
175
- otawp_config["getAllSubCategories"] = (
176
- otawp_base_url
177
- + "/app/entityRestService/api/OpentextCaseManagement/entities/Category/childEntities/SubCategory/lists/AllSubcategories"
178
- )
384
+ otawp_config["serverUrl"] = server_url
179
385
 
180
- otawp_config["baseurl"] = otawp_base_url + ""
181
- otawp_config["createLoan"] = (
182
- otawp_base_url + "/app/entityRestService/api/OpentextCaseManagement/entities/Case?defaultinst_ct=abcd"
183
- )
184
- otawp_config["getAllLoans"] = (
185
- otawp_base_url + "/app/entityRestService/api/OpentextCaseManagement/entities/Case/lists/AllCasesList"
186
- )
187
386
  self._config = otawp_config
188
387
 
388
+ self.set_organization(otawp_config["organization"])
389
+
189
390
  # end method definition
190
391
 
191
- def server(self) -> str:
192
- """Return server information.
392
+ def server_url(self) -> str:
393
+ """Return AppWorks server information.
193
394
 
194
395
  Returns:
195
396
  str:
@@ -202,193 +403,154 @@ class OTAWP:
202
403
  # end method definition
203
404
 
204
405
  def set_organization(self, organization: str) -> None:
205
- """Set the organization context.
406
+ """Set the AppWorks organization context.
407
+
408
+ This requires to also update all URLs that are including
409
+ the organization.
206
410
 
207
411
  Args:
208
412
  organization (str):
209
- Organization name.
413
+ The AppWorks organization name.
210
414
 
211
415
  """
212
416
 
213
- otawp_base_url = f"/home/{organization}"
214
- otawp_url = self.server() + otawp_base_url
215
- ldap_root = (
216
- f"com.eibus.web.soap.Gateway.wcp?organization=o={organization},cn=cordys,cn=defaultInst,o=opentext.net"
417
+ self._config["organization"] = organization
418
+
419
+ otawp_base_url = self._config["serverUrl"] + "/home/{}".format(self._config["organization"])
420
+ self._config["baseUrl"] = otawp_base_url
421
+
422
+ ldap_root = "organization=o={},cn=cordys,cn=defaultInst,o=opentext.net".format(self._config["organization"])
423
+ self._config["gatewayAuthenticationUrl"] = otawp_base_url + "/com.eibus.web.soap.Gateway.wcp?" + ldap_root
424
+
425
+ self._config["soapGatewayUrl"] = self._config["gatewayAuthenticationUrl"] + "&defaultinst_ct=abcd"
426
+
427
+ self._config["entityUrl"] = otawp_base_url + "/app/entityRestService/api/OpentextCaseManagement/entities"
428
+
429
+ self._config["priorityUrl"] = self._config["entityUrl"] + "/Priority"
430
+ self._config["priorityListUrl"] = self._config["priorityUrl"] + "/lists/PriorityList"
431
+
432
+ self._config["customerUrl"] = self._config["entityUrl"] + "/Customer"
433
+ self._config["customerListUrl"] = self._config["customerUrl"] + "/lists/CustomerList"
434
+
435
+ self._config["caseTypeUrl"] = self._config["entityUrl"] + "/CaseType"
436
+ self._config["caseTypeListUrl"] = self._config["caseTypeUrl"] + "/lists/AllCaseTypes"
437
+
438
+ self._config["categoryUrl"] = self._config["entityUrl"] + "/Category"
439
+ self._config["categoryListUrl"] = self._config["categoryUrl"] + "/lists/CategoryList"
440
+
441
+ self._config["subCategoryListUrl"] = (
442
+ self._config["categoryUrl"] + "/childEntities/SubCategory/lists/AllSubcategories"
217
443
  )
218
444
 
219
- # Assign to self._config if that's where you store configuration data
220
- self._config["gatewayAuthenticationUrl"] = otawp_url + f"/com.eibus.web.soap.Gateway.wcp?{ldap_root}"
445
+ self._config["sourceUrl"] = self._config["entityUrl"] + "/Source"
446
+ self._config["sourceListUrl"] = self._config["sourceUrl"] + "/lists/AllSources"
221
447
 
222
- self._config["soapGatewayUrl"] = otawp_url + f"/com.eibus.web.soap.Gateway.wcp?{ldap_root}&defaultinst_ct=abcd"
448
+ self._config["caseUrl"] = self._config["entityUrl"] + "/Case"
449
+ self._config["caseListUrl"] = self._config["caseUrl"] + "/lists/AllCasesList"
223
450
 
224
- self.logger.info("Organization set to '%s'.", organization)
451
+ self.logger.info("AppWorks organization set to -> '%s'.", organization)
225
452
 
226
453
  # end method definition
227
454
 
228
- def baseurl(self) -> str:
229
- """Return the configuration dictionary.
455
+ def base_url(self) -> str:
456
+ """Return the base URL of AppWorks.
230
457
 
231
458
  Returns:
232
459
  str:
233
- Base URL of AppWorks Platform.
460
+ The base URL of AppWorks Platform.
234
461
 
235
462
  """
236
463
 
237
- return self.config()["baseurl"]
464
+ return self.config()["baseUrl"]
238
465
 
239
466
  # end method definition
240
467
 
241
468
  def license_file(self) -> str:
242
- """Return the license_file.
469
+ """Return the AppWorks license file.
243
470
 
244
471
  Returns:
245
472
  str:
246
- Returns license_file
473
+ The name (including path) of the AppWorks license file.
247
474
 
248
475
  """
249
476
 
250
- return self.config()["license_file"]
477
+ return self.config()["licenseFile"]
251
478
 
252
479
  # end method definition
253
480
 
254
481
  def product_name(self) -> str:
255
- """Return the product_name.
482
+ """Return the AppWorks product name as used in the OTDS license.
256
483
 
257
484
  Returns:
258
485
  str:
259
- Returns product_name
486
+ The AppWorks product name.
260
487
 
261
488
  """
262
489
 
263
- return self.config()["product_name"]
490
+ return self.config()["productName"]
264
491
 
265
492
  # end method definition
266
493
 
267
494
  def product_description(self) -> str:
268
- """Return the product_description.
495
+ """Return the AppWorks product description as used in the OTDS license.
269
496
 
270
497
  Returns:
271
498
  str:
272
- Returns product_description
499
+ The AppWorks product description.
273
500
 
274
501
  """
275
502
 
276
- return self.config()["product_description"]
503
+ return self.config()["productDescription"]
277
504
 
278
505
  # end method definition
279
506
 
280
507
  def hostname(self) -> str:
281
- """Return hostname.
508
+ """Return the AppWorks hostname.
282
509
 
283
510
  Returns:
284
- str: Returns hostname
511
+ str:
512
+ The AppWorks hostname.
285
513
 
286
514
  """
287
515
 
288
516
  return self.config()["hostname"]
289
517
 
290
518
  def username(self) -> str:
291
- """Return username.
519
+ """Return the AppWorks username.
292
520
 
293
521
  Returns:
294
- str: Returns username
522
+ str:
523
+ The AppWorks username
295
524
 
296
525
  """
526
+
297
527
  return self.config()["username"]
298
528
 
299
529
  # end method definition
300
530
 
301
531
  def password(self) -> str:
302
- """Return password.
303
-
304
- Returns:
305
- str: Returns password
306
-
307
- """
308
- return self.config()["password"]
309
-
310
- # end method definition
311
-
312
- def otcs_partition_name(self) -> str:
313
- """Return OTCS partition name.
314
-
315
- Returns:
316
- str: Returns OTCS partition name
317
-
318
- """
319
- return self.config()["otcs_partition_name"]
320
-
321
- # end method definition
322
-
323
- def otds_admin_partition_mame(self) -> str:
324
- """Return OTDS admin partition name.
532
+ """Return the AppWorks password.
325
533
 
326
534
  Returns:
327
535
  str:
328
- Returns OTDS admin partition mame.
536
+ The AppWorks password.
329
537
 
330
538
  """
331
539
 
332
- return self.config()["otds_admin_partition_mame"]
540
+ return self.config()["password"]
333
541
 
334
542
  # end method definition
335
543
 
336
544
  def config_map_name(self) -> str:
337
- """Return config map name.
338
-
339
- Returns:
340
- str:
341
- Returns config map name
342
-
343
- """
344
- return self.config()["config_map_name"]
345
-
346
- # end method definition
347
-
348
- def otcs_resource_id(self) -> str:
349
- """Return OTCS resource ID.
350
-
351
- Returns:
352
- str:
353
- Returns otcs resource id
354
-
355
- """
356
- return self.config()["otcs_resource_id"]
357
-
358
- # end method definition
359
-
360
- def otcs_url(self) -> str:
361
- """Return OTCS URL.
362
-
363
- Returns:
364
- str:
365
- Returns the OTCS URL.
366
-
367
- """
368
- return self.config()["otcs_url"]
369
-
370
- # end method definition
371
-
372
- def otds_url(self) -> str:
373
- """Return the OTDS URL.
545
+ """Return AppWorks Kubernetes config map name.
374
546
 
375
547
  Returns:
376
548
  str:
377
- Returns otds url
549
+ The Kubernetes config map name of AppWorks.
378
550
 
379
551
  """
380
- return self.config()["otds_url"]
381
-
382
- # end method definition
383
552
 
384
- def otcs_base_path(self) -> str:
385
- """Return the OTCS base path.
386
-
387
- Returns:
388
- str: Returns otcs base path
389
-
390
- """
391
- return self.config()["otcs_base_path"]
553
+ return self.config()["configMapName"]
392
554
 
393
555
  # end method definition
394
556
 
@@ -461,10 +623,11 @@ class OTAWP:
461
623
  # end method definition
462
624
 
463
625
  def credential_url(self) -> str:
464
- """Return the Credentials URL of OTAWP.
626
+ """Return the credentials URL of OTAWP.
465
627
 
466
628
  Returns:
467
- str: Credentials URL
629
+ str:
630
+ The AppWorks credentials URL.
468
631
 
469
632
  """
470
633
 
@@ -477,7 +640,7 @@ class OTAWP:
477
640
 
478
641
  Returns:
479
642
  str:
480
- The SOAP gateway URL.
643
+ The AppWorks SOAP gateway URL.
481
644
 
482
645
  """
483
646
 
@@ -485,32 +648,33 @@ class OTAWP:
485
648
 
486
649
  # end method definition
487
650
 
488
- def create_priority_url(self) -> str:
651
+ def get_create_priority_url(self) -> str:
489
652
  """Return create priority URL of OTAWP.
490
653
 
491
654
  Returns:
492
- str: createPriority URL
655
+ str:
656
+ The create priority URL.
493
657
 
494
658
  """
495
659
 
496
- return self.config()["createPriority"]
660
+ return self.config()["priorityUrl"] + "?defaultinst_ct=abcd"
497
661
 
498
662
  # end method definition
499
663
 
500
- def get_all_priorities_url(self) -> str:
501
- """Return get all priorities URL of OTAWP.
664
+ def get_priorities_list_url(self) -> str:
665
+ """Get OTAWP URL to retrieve a list of all priorities.
502
666
 
503
667
  Returns:
504
668
  str:
505
- The getAllPriorities URL of OTAWP.
669
+ The AppWorks URL to get a list of all priorities.
506
670
 
507
671
  """
508
672
 
509
- return self.config()["getAllPriorities"]
673
+ return self.config()["priorityListUrl"]
510
674
 
511
675
  # end method definition
512
676
 
513
- def create_customer_url(self) -> str:
677
+ def get_create_customer_url(self) -> str:
514
678
  """Return create customer URL of OTAWP.
515
679
 
516
680
  Returns:
@@ -519,24 +683,24 @@ class OTAWP:
519
683
 
520
684
  """
521
685
 
522
- return self.config()["createCustomer"]
686
+ return self.config()["customerUrl"] + "?defaultinst_ct=abcd"
523
687
 
524
688
  # end method definition
525
689
 
526
- def get_all_customeres_url(self) -> str:
527
- """Return get all customers URL of OTAWP.
690
+ def get_customers_list_url(self) -> str:
691
+ """Get OTAWP URL to retrieve a list of all customers.
528
692
 
529
693
  Returns:
530
694
  str:
531
- The get all customers URL.
695
+ The AppWorks URL to get a list of all customers.
532
696
 
533
697
  """
534
698
 
535
- return self.config()["getAllCustomers"]
699
+ return self.config()["customerListUrl"]
536
700
 
537
701
  # end method definition
538
702
 
539
- def create_casetype_url(self) -> str:
703
+ def get_create_casetype_url(self) -> str:
540
704
  """Return create case type URL of OTAWP.
541
705
 
542
706
  Returns:
@@ -545,12 +709,12 @@ class OTAWP:
545
709
 
546
710
  """
547
711
 
548
- return self.config()["createCaseType"]
712
+ return self.config()["caseTypeUrl"] + "?defaultinst_ct=abcd"
549
713
 
550
714
  # end method definition
551
715
 
552
- def get_all_case_types_url(self) -> str:
553
- """Return get all case types URL of OTAWP.
716
+ def get_casetypes_list_url(self) -> str:
717
+ """Get OTAWP URL to retrieve a list of all case types.
554
718
 
555
719
  Returns:
556
720
  str:
@@ -558,12 +722,12 @@ class OTAWP:
558
722
 
559
723
  """
560
724
 
561
- return self.config()["getAllCaseTypes"]
725
+ return self.config()["caseTypeListUrl"]
562
726
 
563
727
  # end method definition
564
728
 
565
- def create_category_url(self) -> str:
566
- """Return create category URL of OTAWP.
729
+ def get_create_category_url(self) -> str:
730
+ """Get OTAWP URL to create a category.
567
731
 
568
732
  Returns:
569
733
  str:
@@ -571,12 +735,12 @@ class OTAWP:
571
735
 
572
736
  """
573
737
 
574
- return self.config()["createCategory"]
738
+ return self.config()["categoryUrl"] + "?defaultinst_ct=abcd"
575
739
 
576
740
  # end method definition
577
741
 
578
- def get_all_categories_url(self) -> str:
579
- """Return the get all categories URL of OTAWP.
742
+ def get_categories_list_url(self) -> str:
743
+ """Get OTAWP URL to retrieve a list of all categories.
580
744
 
581
745
  Returns:
582
746
  str:
@@ -584,60 +748,65 @@ class OTAWP:
584
748
 
585
749
  """
586
750
 
587
- return self.config()["getAllCategories"]
751
+ return self.config()["categoryListUrl"]
588
752
 
589
753
  # end method definition
590
754
 
591
- def get_all_loans_url(self) -> str:
592
- """Return get all loans URL of OTAWP.
755
+ def get_create_case_url(self) -> str:
756
+ """Get OTAWP URL to create a case (e.g. a loan).
593
757
 
594
758
  Returns:
595
759
  str:
596
- The get all loans URL.
760
+ The create case URL.
597
761
 
598
762
  """
599
763
 
600
- return self.config()["getAllLoans"]
764
+ return self.config()["caseUrl"] + "?defaultinst_ct=abcd"
601
765
 
602
766
  # end method definition
603
767
 
604
- def remove_namespace(self, tag: str) -> str:
605
- """Remove namespace from XML tag."""
768
+ def get_cases_list_url(self) -> str:
769
+ """Return get all loans URL of OTAWP.
770
+
771
+ Returns:
772
+ str:
773
+ The get all loans URL.
774
+
775
+ """
606
776
 
607
- return tag.split("}", 1)[-1]
777
+ return self.config()["caseListUrl"]
608
778
 
609
779
  # end method definition
610
780
 
611
781
  def parse_xml(self, xml_string: str) -> dict:
612
- """Parse XML string and return a dictionary without namespaces."""
613
-
614
- def element_to_dict(element) -> dict: # noqa: ANN001
615
- """Convert XML element to dictionary."""
616
- tag = self.remove_namespace(element.tag)
617
- children = list(element)
618
- if children:
619
- return {
620
- tag: {self.remove_namespace(child.tag): element_to_dict(child) for child in children},
621
- }
622
- return {tag: element.text.strip() if element.text else None}
782
+ """Parse XML string and return a dictionary without namespaces.
783
+
784
+ Args:
785
+ xml_string (str):
786
+ The XML string to process.
787
+
788
+ Returns:
789
+ dict:
790
+ The XML structure converted to a dictionary.
623
791
 
624
- root = ET.fromstring(xml_string)
792
+ """
625
793
 
626
- return element_to_dict(root)
794
+ return XML.xml_to_dict(xml_string=xml_string)
627
795
 
628
796
  # end method definition
629
797
 
630
- def find_key(self, data: dict | list, target_key: str) -> str:
798
+ def find_key(self, data: dict | list, target_key: str) -> str | None:
631
799
  """Recursively search for a key in a nested dictionary and return its value.
632
800
 
633
801
  Args:
634
802
  data (dict | list):
635
- TODO: _description_
803
+ The data structure to find a key in.
636
804
  target_key (str):
637
- TODO: _description_
805
+ The key to find.
638
806
 
639
807
  Returns:
640
- _type_: _description_
808
+ str:
809
+ The value for the key.
641
810
 
642
811
  """
643
812
 
@@ -658,6 +827,157 @@ class OTAWP:
658
827
 
659
828
  # end method definition
660
829
 
830
+ def get_soap_element(self, soap_response: str, soap_tag: str) -> str | None:
831
+ """Retrieve an element from the XML SOAP response.
832
+
833
+ Args:
834
+ soap_response (str):
835
+ The unparsed XML string of the SOAP response.
836
+ soap_tag (str):
837
+ The XML tag name (without namespace) of the element
838
+ incuding the text to be returned.
839
+
840
+ Returns:
841
+ str | None:
842
+ SOAP message if found in the SOAP response or NONE otherwise.
843
+
844
+ """
845
+
846
+ soap_data = self.parse_xml(soap_response)
847
+ soap_string = self.find_key(data=soap_data, target_key=soap_tag)
848
+
849
+ return soap_string
850
+
851
+ # end method definition
852
+
853
+ def do_request(
854
+ self,
855
+ url: str,
856
+ method: str = "GET",
857
+ headers: dict | None = None,
858
+ cookies: dict | None = None,
859
+ data: dict | None = None,
860
+ json_data: dict | None = None,
861
+ files: dict | None = None,
862
+ timeout: int | None = REQUEST_TIMEOUT,
863
+ show_error: bool = True,
864
+ show_warning: bool = False,
865
+ warning_message: str = "",
866
+ failure_message: str = "",
867
+ success_message: str = "",
868
+ parse_request_response: bool = True,
869
+ verify: bool = True,
870
+ ) -> dict | None:
871
+ """Call an AppWorks REST API in a safe way.
872
+
873
+ Args:
874
+ url (str):
875
+ The URL to send the request to.
876
+ method (str, optional):
877
+ HTTP method (GET, POST, etc.). Defaults to "GET".
878
+ headers (dict | None, optional):
879
+ Request Headers. Defaults to None.
880
+ cookies (dict | None, optional):
881
+ Request cookies. Defaults to None.
882
+ data (dict | None, optional):
883
+ Request payload. Defaults to None
884
+ json_data (dict | None, optional):
885
+ Request payload for the JSON parameter. Defaults to None.
886
+ files (dict | None, optional):
887
+ Dictionary of {"name": file-tuple} for multipart encoding upload.
888
+ The file-tuple can be a 2-tuple ("filename", fileobj) or a 3-tuple ("filename", fileobj, "content_type")
889
+ timeout (int | None, optional):
890
+ Timeout for the request in seconds. Defaults to REQUEST_TIMEOUT.
891
+ show_error (bool, optional):
892
+ Whether or not an error should be logged in case of a failed REST call.
893
+ If False, then only a warning is logged. Defaults to True.
894
+ show_warning (bool, optional):
895
+ Whether or not an warning should be logged in case of a
896
+ failed REST call.
897
+ If False, then only a warning is logged. Defaults to True.
898
+ warning_message (str, optional):
899
+ Specific warning message. Defaults to "". If not given the error_message will be used.
900
+ failure_message (str, optional):
901
+ Specific error message. Defaults to "".
902
+ success_message (str, optional):
903
+ Specific success message. Defaults to "".
904
+ parse_request_response (bool, optional):
905
+ If True the response.text will be interpreted as json and loaded into a dictionary.
906
+ True is the default.
907
+ user_credentials (bool, optional):
908
+ Defines if admin or user credentials are used for the REST API call.
909
+ Default = False = admin credentials
910
+ verify (bool, optional):
911
+ Specify whether or not SSL certificates should be verified when making an HTTPS request.
912
+ Default = True
913
+
914
+ Returns:
915
+ dict | None:
916
+ Response of OTDS REST API or None in case of an error.
917
+
918
+ """
919
+
920
+ # In case of an expired session we reauthenticate and
921
+ # try 1 more time. Session expiration should not happen
922
+ # twice in a row:
923
+ retries = 0
924
+
925
+ while True:
926
+ try:
927
+ response = requests.request(
928
+ method=method,
929
+ url=url,
930
+ data=data,
931
+ json=json_data,
932
+ files=files,
933
+ headers=headers,
934
+ cookies=cookies,
935
+ timeout=timeout,
936
+ verify=verify,
937
+ )
938
+
939
+ except requests.RequestException as req_exception:
940
+ self.logger.error(
941
+ "%s; error -> %s",
942
+ failure_message if failure_message else "Request to -> %s failed",
943
+ str(req_exception),
944
+ )
945
+ return None
946
+
947
+ if response.ok:
948
+ if success_message:
949
+ self.logger.info(success_message)
950
+ if parse_request_response:
951
+ return self.parse_request_response(response_object=response, show_error=show_error)
952
+ else:
953
+ return response
954
+ # Check if Session has expired - then re-authenticate and try once more
955
+ elif response.status_code == 401 and retries == 0:
956
+ self.logger.warning("Session has expired - try to re-authenticate...")
957
+ self.authenticate(revalidate=True)
958
+ retries += 1
959
+ continue
960
+ elif show_error:
961
+ self.logger.error(
962
+ "%s; status -> %s/%s; error -> %s",
963
+ failure_message,
964
+ response.status_code,
965
+ HTTPStatus(response.status_code).phrase,
966
+ response.text,
967
+ )
968
+ elif show_warning:
969
+ self.logger.warning(
970
+ "%s; status -> %s/%s; warning -> %s",
971
+ warning_message if warning_message else failure_message,
972
+ response.status_code,
973
+ HTTPStatus(response.status_code).phrase,
974
+ response.text,
975
+ )
976
+ return None
977
+ # end while True
978
+
979
+ # end method definition
980
+
661
981
  def parse_request_response(
662
982
  self,
663
983
  response_object: object,
@@ -706,207 +1026,540 @@ class OTAWP:
706
1026
  else:
707
1027
  self.logger.warning(message)
708
1028
  return None
1029
+
709
1030
  return dict_object
710
1031
 
711
1032
  # end method definition
712
1033
 
713
- def authenticate(self, revalidate: bool = False) -> dict | None:
714
- """Authenticate at AppWorks.
1034
+ def get_entity_value(self, entity: dict, key: str, show_error: bool = True) -> str | int | None:
1035
+ """Read an entity value from the REST API response.
715
1036
 
716
1037
  Args:
717
- revalidate (bool, optional):
718
- Determine if a re-authentication is enforced
719
- (e.g. if session has timed out with 401 error).
1038
+ entity (dict):
1039
+ An entity - typically consisting of a dictionary with a "_links" and "Properties" keys. Example:
1040
+ {
1041
+ '_links': {
1042
+ 'item': {...}
1043
+ },
1044
+ 'Properties': {
1045
+ 'Name': 'Test 1',
1046
+ 'Description': 'Test 1 Description',
1047
+ 'CasePrefix': 'TEST',
1048
+ 'Status': 1
1049
+ }
1050
+ }
1051
+ key (str):
1052
+ Key to find (e.g., "id", "name"). For key "id" there's a special
1053
+ handling as the ID is only provided in the 'href' in the '_links'
1054
+ sub-dictionary.
1055
+ show_error (bool, optional):
1056
+ Whether an error or just a warning should be logged.
720
1057
 
721
1058
  Returns:
722
- dict:
723
- Cookie information. Also stores cookie information in self._cookie
1059
+ str | None:
1060
+ Value of the entity property with the given key, or None if no value is found.
724
1061
 
725
1062
  """
726
1063
 
727
- self.logger.info("SAMLart generation started")
728
- if self._cookie and not revalidate:
729
- self.logger.debug(
730
- "Session still valid - return existing cookie -> %s",
731
- str(self._cookie),
732
- )
733
- return self._cookie
1064
+ if not entity or "Properties" not in entity:
1065
+ return None
734
1066
 
735
- otawp_ticket = "NotSet"
1067
+ properties = entity["Properties"]
736
1068
 
737
- response = None
738
- try:
739
- self.credentials()
740
- response = requests.post(
741
- url=self.credential_url(),
742
- data=self.credentials(),
743
- headers=REQUEST_HEADERS,
744
- timeout=REQUEST_TIMEOUT,
745
- )
746
- except requests.exceptions.RequestException as exception:
747
- self.logger.warning(
748
- "Unable to connect to OTAWP authentication endpoint -> %s; error -> %s",
749
- self.credential_url(),
750
- str(exception),
751
- )
752
- self.logger.warning("OTAWP service may not be ready yet.")
1069
+ if key not in properties and key != "id":
1070
+ if show_error:
1071
+ self.logger.error("Key -> '%s' not found in entity -> '%s'!", key, str(entity))
753
1072
  return None
754
1073
 
755
- if response.ok:
756
- self.logger.info("SAMLart generated successfully")
757
- authenticate_dict = self.parse_xml(response.text)
758
- if not authenticate_dict:
1074
+ # special handling of IDs which we extract from the self href:
1075
+ if key == "id" and "_links" in entity:
1076
+ links = entity["_links"]
1077
+ if "item" in links:
1078
+ links = links["item"]
1079
+ self_link = links.get("href")
1080
+ match = re.search(r"/(\d+)(?=[^/]*$)", self_link)
1081
+ if not match:
759
1082
  return None
760
- assertion_artifact_dict = self.find_key(
761
- authenticate_dict,
762
- "AssertionArtifact",
763
- )
764
- if isinstance(assertion_artifact_dict, dict):
765
- otawp_ticket = assertion_artifact_dict.get("AssertionArtifact")
766
- self.logger.debug("SAML token -> %s", otawp_ticket)
767
- else:
768
- self.logger.error(
769
- "Failed to request an OTAWP ticket; error -> %s",
770
- response.text,
771
- )
772
- return None
773
-
774
- self._cookie = {"defaultinst_SAMLart": otawp_ticket, "defaultinst_ct": "abcd"}
775
- self._otawp_ticket = otawp_ticket
1083
+ return int(match.group(1))
776
1084
 
777
- return self._cookie
1085
+ return properties[key]
778
1086
 
779
1087
  # end method definition
780
1088
 
781
- def create_workspace(self, workspace_name: str, workspace_id: str) -> dict | None:
782
- """Create a workspace in cws.
1089
+ def get_result_value(
1090
+ self,
1091
+ response: dict,
1092
+ entity_type: str,
1093
+ key: str,
1094
+ index: int = 0,
1095
+ show_error: bool = True,
1096
+ ) -> str | int | None:
1097
+ """Read an item value from the REST API response.
783
1098
 
784
1099
  Args:
785
- workspace_name (str):
786
- The name of the workspace.
787
- workspace_id (str):
788
- The ID of the workspace.
1100
+ response (dict):
1101
+ REST API response object.
1102
+ entity_type (str):
1103
+ Name of the sub-dictionary holding the actual values.
1104
+ This typically stands for the type of the AppWorks entity.
1105
+ key (str):
1106
+ Key to find (e.g., "id", "name").
1107
+ index (int, optional):
1108
+ Index to use if a list of results is delivered (1st element has index 0).
1109
+ Defaults to 0.
1110
+ show_error (bool, optional):
1111
+ Whether an error or just a warning should be logged.
789
1112
 
790
1113
  Returns:
791
- dict | None:
792
- Response dictionary or error text
1114
+ str | None:
1115
+ Value of the item with the given key, or None if no value is found.
793
1116
 
794
1117
  """
795
1118
 
796
- self.logger.info(
797
- "Create workspace -> '%s' (%s)...",
798
- workspace_name,
799
- workspace_id,
800
- )
801
- unique_id = uuid.uuid4()
1119
+ if not response:
1120
+ return None
802
1121
 
803
- license_post_body_json = f"""<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
804
- <SOAP:Body>
805
- <createWorkspace xmlns="http://schemas.cordys.com/cws/runtime/types/workspace/creation/DevelopmentWorkspaceCreator/1.0" async="false" workspaceID="__CWS System__" xmlns:c="http://schemas.cordys.com/cws/1.0">
806
- <instance>
807
- <c:Document s="T" path="D43B04C1-CD0B-A1EB-A898-53C71DB5D652">
808
- <c:Header>
809
- <c:System>
810
- <c:TypeID>001A6B1E-0C0C-11DF-F5E9-866B84E5D671</c:TypeID>
811
- <c:ID>D43B04C1-CD0B-A1EB-A898-53C71DB5D652</c:ID>
812
- <c:Name>D43B04C1-CD0B-A1EB-A898-53C71DB5D652</c:Name>
813
- <c:Description>D43B04C1-CD0B-A1EB-A898-53C71DB5D652</c:Description>
814
- </c:System>
815
- </c:Header>
816
- <c:Content>
817
- <DevelopmentWorkspaceCreator type="com.cordys.cws.runtime.types.workspace.creation.DevelopmentWorkspaceCreator" runtimeDocumentID="D43B04C1-CD0B-A1EB-A898-53C71DB61652">
818
- <Workspace>
819
- <uri id="{workspace_id}"/>
820
- </Workspace>
821
- </DevelopmentWorkspaceCreator>
822
- </c:Content>
823
- </c:Document>
824
- </instance>
825
- <__prefetch>
826
- <Document xmlns="http://schemas.cordys.com/cws/1.0" path="{workspace_name}" s="N" isLocal="IN_LOCAL">
827
- <Header>
828
- <System>
829
- <ID>{workspace_id}</ID>
830
- <Name>{workspace_name}</Name>
831
- <TypeID>{{4CE11E00-2D97-45C0-BC6C-FAEC1D871026}}</TypeID>
832
- <ParentID/>
833
- <Description>{workspace_name}</Description>
834
- <CreatedBy>sysadmin</CreatedBy>
835
- <CreationDate/>
836
- <LastModifiedBy>sysadmin</LastModifiedBy>
837
- <LastModifiedDate>2021-04-21T06:52:34.254</LastModifiedDate>
838
- <FQN/>
839
- <Annotation/>
840
- <ParentID/>
841
- <OptimisticLock/>
842
- </System>
843
- </Header>
844
- <Content>
845
- <DevelopmentWorkspace xmlns="http://schemas.cordys.com/cws/runtime/types/workspace/DevelopmentWorkspace/1.0" runtimeDocumentID="D43B04C1-CD0B-A1EB-A898-53C71DB59652" type="com.cordys.cws.runtime.types.workspace.DevelopmentWorkspace">
846
- <ExternalID/>
847
- <OrganizationName/>
848
- <SCMAdapter>
849
- <uri id="{unique_id}"/>
850
- </SCMAdapter>
851
- <UpgradedTo/>
852
- <LastWorkspaceUpgradeStep/>
853
- <Metaspace/>
854
- </DevelopmentWorkspace>
855
- </Content>
856
- </Document>
857
- <Document xmlns="http://schemas.cordys.com/cws/1.0" path="{workspace_name}/Untitled No SCM adapter" s="N" isLocal="IN_LOCAL">
858
- <Header>
859
- <System>
860
- <ID>{unique_id}</ID>
861
- <Name>Untitled No SCM adapter</Name>
862
- <TypeID>{{E89F3F62-8CA3-4F93-95A8-F76642FD5124}}</TypeID>
863
- <ParentID>{workspace_id}</ParentID>
864
- <Description>Untitled No SCM adapter</Description>
865
- <CreatedBy>sysadmin</CreatedBy>
866
- <CreationDate/>
867
- <LastModifiedBy>sysadmin</LastModifiedBy>
868
- <LastModifiedDate>2021-04-21T06:52:34.254</LastModifiedDate>
869
- <FQN/>
870
- <Annotation/>
871
- <OptimisticLock/>
872
- </System>
873
- </Header>
874
- <Content>
875
- <NullAdapter xmlns="http://schemas.cordys.com/cws/runtime/types/teamdevelopment/NullAdapter/1.0" runtimeDocumentID="D43B04C1-CD0B-A1EB-A898-53C71DB51652" type="com.cordys.cws.runtime.types.teamdevelopment.NullAdapter">
876
- <Workspace>
877
- <uri id="{workspace_id}"/>
878
- </Workspace>
879
- </NullAdapter>
880
- </Content>
881
- </Document>
882
- </__prefetch>
883
- </createWorkspace>
884
- </SOAP:Body>
885
- </SOAP:Envelope>"""
1122
+ if "_embedded" not in response:
1123
+ return None
886
1124
 
887
- retries = 0
888
- while True:
889
- response = requests.post(
890
- url=self.gateway_url(),
891
- data=license_post_body_json,
892
- headers=REQUEST_HEADERS,
893
- cookies=self.cookie(),
894
- timeout=REQUEST_TIMEOUT,
1125
+ embedded_data = response["_embedded"]
1126
+
1127
+ if entity_type not in embedded_data:
1128
+ if show_error:
1129
+ self.logger.error("Entity type -> '%s' is not included in response!", entity_type)
1130
+ return None
1131
+
1132
+ entity_list = embedded_data[entity_type]
1133
+
1134
+ try:
1135
+ entity = entity_list[index]
1136
+ except KeyError:
1137
+ if show_error:
1138
+ self.logger.error("Response does not have an entity at index -> %d", index)
1139
+ return None
1140
+
1141
+ return self.get_entity_value(entity=entity, key=key, show_error=show_error)
1142
+
1143
+ # end method definition
1144
+
1145
+ def get_result_values(
1146
+ self,
1147
+ response: dict,
1148
+ entity_type: str,
1149
+ key: str,
1150
+ show_error: bool = True,
1151
+ ) -> list | None:
1152
+ """Read an values from the REST API response.
1153
+
1154
+ Args:
1155
+ response (dict):
1156
+ REST API response object.
1157
+ entity_type (str):
1158
+ Name of the sub-dictionary holding the actual values.
1159
+ This typically stands for the type of the AppWorks entity.
1160
+ key (str):
1161
+ Key to find (e.g., "id", "name").
1162
+ show_error (bool, optional):
1163
+ Whether an error or just a warning should be logged.
1164
+
1165
+ Returns:
1166
+ list | None:
1167
+ Values of the items with the given key, or [] if the list
1168
+ of values is empty, or None if the response is not in the
1169
+ expected format.
1170
+
1171
+ """
1172
+
1173
+ results = []
1174
+
1175
+ if not response:
1176
+ return None
1177
+
1178
+ if "_embedded" not in response:
1179
+ return None
1180
+
1181
+ embedded_data = response["_embedded"]
1182
+
1183
+ if entity_type not in embedded_data:
1184
+ if show_error:
1185
+ self.logger.error("Entity type -> '%s' is not included in response!", entity_type)
1186
+ return None
1187
+
1188
+ entity_list = embedded_data[entity_type]
1189
+
1190
+ for entity in entity_list or []:
1191
+ entity_value = self.get_entity_value(entity=entity, key=key, show_error=show_error)
1192
+ if entity_value:
1193
+ results.append(entity_value)
1194
+
1195
+ return results
1196
+
1197
+ # end method definition
1198
+
1199
+ def get_result_item(
1200
+ self,
1201
+ response: dict,
1202
+ entity_type: str,
1203
+ key: str,
1204
+ value: str,
1205
+ show_error: bool = True,
1206
+ ) -> dict | None:
1207
+ """Check existence of key / value pair in the response properties of an REST API call.
1208
+
1209
+ Args:
1210
+ response (dict):
1211
+ REST response from an AppWorks REST Call.
1212
+ Name of the sub-dictionary holding the actual values.
1213
+ This typically stands for the type of the AppWorks entity.
1214
+ entity_type (str):
1215
+ Name of the sub-dictionary holding the actual values.
1216
+ This typically stands for the type of the AppWorks entity.
1217
+ key (str):
1218
+ The property name (key).
1219
+ value (str):
1220
+ The value to find in the item with the matching key.
1221
+ show_error (bool, optional):
1222
+ Whether an error or just a warning should be logged.
1223
+
1224
+ Returns:
1225
+ dict | None:
1226
+ Entity data or None in case entity with key/value was not found.
1227
+
1228
+ """
1229
+
1230
+ if not response:
1231
+ return None
1232
+
1233
+ if "_embedded" not in response:
1234
+ return None
1235
+
1236
+ embedded_data = response["_embedded"]
1237
+
1238
+ if entity_type not in embedded_data:
1239
+ if show_error:
1240
+ self.logger.error("Entity type -> '%s' is not included in response!", entity_type)
1241
+ return None
1242
+
1243
+ entity_list = embedded_data[entity_type]
1244
+
1245
+ for entity in entity_list:
1246
+ if "Properties" not in entity:
1247
+ continue
1248
+
1249
+ properties = entity["Properties"]
1250
+
1251
+ if key not in properties:
1252
+ if show_error:
1253
+ self.logger.error("Key -> '%s' is not in properties of entity -> '%s'!", key, str(entity))
1254
+ continue
1255
+ if properties[key] == value:
1256
+ return entity
1257
+
1258
+ return None
1259
+
1260
+ # end method definition
1261
+
1262
+ def authenticate(self, revalidate: bool = False) -> dict | None:
1263
+ """Authenticate at AppWorks.
1264
+
1265
+ Args:
1266
+ revalidate (bool, optional):
1267
+ Determine if a re-authentication is enforced
1268
+ (e.g. if session has timed out with 401 error).
1269
+
1270
+ Returns:
1271
+ dict | None:
1272
+ Cookie information. Also stores cookie information in self._cookie.
1273
+ None in case of an error.
1274
+
1275
+ Example:
1276
+ {
1277
+ 'defaultinst_SAMLart': 'e0pBVkEtQUVTL0...tj5m6w==',
1278
+ 'defaultinst_ct': 'abcd'
1279
+ }
1280
+
1281
+ """
1282
+
1283
+ self.logger.info("Authenticate at AppWorks organization -> '%s'...", self.config()["organization"])
1284
+
1285
+ if self._cookie and not revalidate:
1286
+ self.logger.debug(
1287
+ "Session still valid - return existing cookie -> %s",
1288
+ str(self._cookie),
895
1289
  )
1290
+ return self._cookie
1291
+
1292
+ otawp_ticket = "NotSet"
1293
+
1294
+ request_url = self.credential_url()
1295
+
1296
+ retries = 0
1297
+ response = None # seems to be necessary here
1298
+
1299
+ while retries < REQUEST_MAX_RETRIES:
1300
+ try:
1301
+ response = requests.post(
1302
+ url=request_url,
1303
+ data=self.credentials(),
1304
+ headers=REQUEST_HEADERS_XML,
1305
+ timeout=REQUEST_TIMEOUT,
1306
+ )
1307
+ except requests.exceptions.RequestException as exception:
1308
+ self.logger.warning(
1309
+ "Unable to connect to OTAWP authentication endpoint -> %s; error -> %s",
1310
+ self.credential_url(),
1311
+ str(exception),
1312
+ )
1313
+ self.logger.warning("OTAWP service may not be ready yet. Retry in %d seconds...", REQUEST_RETRY_DELAY)
1314
+ time.sleep(REQUEST_RETRY_DELAY)
1315
+ retries += 1
1316
+ continue
1317
+
1318
+ if response.ok:
1319
+ soap_response = self.parse_xml(xml_string=response.text)
1320
+ if not soap_response:
1321
+ self.logger.error("Failed to parse the SOAP response with the authentication data!")
1322
+ self.logger.debug("SOAP message -> %s", response.text)
1323
+ return None
1324
+ otawp_ticket = self.find_key(
1325
+ data=soap_response,
1326
+ target_key="AssertionArtifact",
1327
+ )
1328
+ if otawp_ticket:
1329
+ self.logger.info(
1330
+ "Successfully authenticated at AppWorks organization -> '%s' with URL -> %s and user -> '%s'.",
1331
+ self.config()["organization"],
1332
+ self.credential_url(),
1333
+ self.config()["username"],
1334
+ )
1335
+ self.logger.debug("SAML token -> %s", otawp_ticket)
1336
+ self._cookie = {"defaultinst_SAMLart": otawp_ticket, "defaultinst_ct": "abcd"}
1337
+ self._otawp_ticket = otawp_ticket
1338
+
1339
+ return self._cookie
1340
+ else:
1341
+ self.logger.error(
1342
+ "Cannot retrieve OTAWP ticket! Received corrupt authentication data -> %s",
1343
+ response.text,
1344
+ )
1345
+ return None
1346
+ else:
1347
+ self.logger.error(
1348
+ "Failed to request an OTAWP ticket at authentication URL -> %s with user -> '%s'!%s",
1349
+ self.credential_url(),
1350
+ self.config()["username"],
1351
+ " Reason -> '{}'".format(response.reason) if response.reason else "",
1352
+ )
1353
+ return None
1354
+
1355
+ self.logger.error(
1356
+ "Authentication at AppWorks platform failed after %d retries. %sBailing out.",
1357
+ REQUEST_MAX_RETRIES,
1358
+ "{}. ".format(response.text) if response and response.text else "",
1359
+ )
1360
+ return None
1361
+
1362
+ # end method definition
1363
+
1364
+ def create_workspace(
1365
+ self, workspace_name: str, workspace_id: str, show_error: bool = True
1366
+ ) -> tuple[dict | None, bool]:
1367
+ """Create a workspace in cws.
1368
+
1369
+ Args:
1370
+ workspace_name (str):
1371
+ The name of the workspace.
1372
+ workspace_id (str):
1373
+ The ID of the workspace.
1374
+ show_error (bool, optional):
1375
+ Whether to show an error or a warning instead.
1376
+
1377
+ Returns:
1378
+ dict | None:
1379
+ Response dictionary or error text.
1380
+ bool:
1381
+ True, if a new workspace has been created, False if the workspace did already exist.
1382
+
1383
+ """
1384
+
1385
+ self.logger.info(
1386
+ "Create workspace -> '%s' (%s)...",
1387
+ workspace_name,
1388
+ workspace_id,
1389
+ )
1390
+
1391
+ unique_id = uuid.uuid4()
1392
+
1393
+ create_workspace_data = f"""
1394
+ <SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
1395
+ <SOAP:Body>
1396
+ <createWorkspace xmlns="http://schemas.cordys.com/cws/runtime/types/workspace/creation/DevelopmentWorkspaceCreator/1.0" async="false" workspaceID="__CWS System__" xmlns:c="http://schemas.cordys.com/cws/1.0">
1397
+ <instance>
1398
+ <c:Document s="T" path="D43B04C1-CD0B-A1EB-A898-53C71DB5D652">
1399
+ <c:Header>
1400
+ <c:System>
1401
+ <c:TypeID>001A6B1E-0C0C-11DF-F5E9-866B84E5D671</c:TypeID>
1402
+ <c:ID>D43B04C1-CD0B-A1EB-A898-53C71DB5D652</c:ID>
1403
+ <c:Name>D43B04C1-CD0B-A1EB-A898-53C71DB5D652</c:Name>
1404
+ <c:Description>D43B04C1-CD0B-A1EB-A898-53C71DB5D652</c:Description>
1405
+ </c:System>
1406
+ </c:Header>
1407
+ <c:Content>
1408
+ <DevelopmentWorkspaceCreator type="com.cordys.cws.runtime.types.workspace.creation.DevelopmentWorkspaceCreator" runtimeDocumentID="D43B04C1-CD0B-A1EB-A898-53C71DB61652">
1409
+ <Workspace>
1410
+ <uri id="{workspace_id}"/>
1411
+ </Workspace>
1412
+ </DevelopmentWorkspaceCreator>
1413
+ </c:Content>
1414
+ </c:Document>
1415
+ </instance>
1416
+ <__prefetch>
1417
+ <Document xmlns="http://schemas.cordys.com/cws/1.0" path="{workspace_name}" s="N" isLocal="IN_LOCAL">
1418
+ <Header>
1419
+ <System>
1420
+ <ID>{workspace_id}</ID>
1421
+ <Name>{workspace_name}</Name>
1422
+ <TypeID>{{4CE11E00-2D97-45C0-BC6C-FAEC1D871026}}</TypeID>
1423
+ <ParentID/>
1424
+ <Description>{workspace_name}</Description>
1425
+ <CreatedBy>sysadmin</CreatedBy>
1426
+ <CreationDate/>
1427
+ <LastModifiedBy>sysadmin</LastModifiedBy>
1428
+ <LastModifiedDate>2021-04-21T06:52:34.254</LastModifiedDate>
1429
+ <FQN/>
1430
+ <Annotation/>
1431
+ <ParentID/>
1432
+ <OptimisticLock/>
1433
+ </System>
1434
+ </Header>
1435
+ <Content>
1436
+ <DevelopmentWorkspace xmlns="http://schemas.cordys.com/cws/runtime/types/workspace/DevelopmentWorkspace/1.0" runtimeDocumentID="D43B04C1-CD0B-A1EB-A898-53C71DB59652" type="com.cordys.cws.runtime.types.workspace.DevelopmentWorkspace">
1437
+ <ExternalID/>
1438
+ <OrganizationName/>
1439
+ <SCMAdapter>
1440
+ <uri id="{unique_id}"/>
1441
+ </SCMAdapter>
1442
+ <UpgradedTo/>
1443
+ <LastWorkspaceUpgradeStep/>
1444
+ <Metaspace/>
1445
+ </DevelopmentWorkspace>
1446
+ </Content>
1447
+ </Document>
1448
+ <Document xmlns="http://schemas.cordys.com/cws/1.0" path="{workspace_name}/Untitled No SCM adapter" s="N" isLocal="IN_LOCAL">
1449
+ <Header>
1450
+ <System>
1451
+ <ID>{unique_id}</ID>
1452
+ <Name>Untitled No SCM adapter</Name>
1453
+ <TypeID>{{E89F3F62-8CA3-4F93-95A8-F76642FD5124}}</TypeID>
1454
+ <ParentID>{workspace_id}</ParentID>
1455
+ <Description>Untitled No SCM adapter</Description>
1456
+ <CreatedBy>sysadmin</CreatedBy>
1457
+ <CreationDate/>
1458
+ <LastModifiedBy>sysadmin</LastModifiedBy>
1459
+ <LastModifiedDate>2021-04-21T06:52:34.254</LastModifiedDate>
1460
+ <FQN/>
1461
+ <Annotation/>
1462
+ <OptimisticLock/>
1463
+ </System>
1464
+ </Header>
1465
+ <Content>
1466
+ <NullAdapter xmlns="http://schemas.cordys.com/cws/runtime/types/teamdevelopment/NullAdapter/1.0" runtimeDocumentID="D43B04C1-CD0B-A1EB-A898-53C71DB51652" type="com.cordys.cws.runtime.types.teamdevelopment.NullAdapter">
1467
+ <Workspace>
1468
+ <uri id="{workspace_id}"/>
1469
+ </Workspace>
1470
+ </NullAdapter>
1471
+ </Content>
1472
+ </Document>
1473
+ </__prefetch>
1474
+ </createWorkspace>
1475
+ </SOAP:Body>
1476
+ </SOAP:Envelope>"""
1477
+
1478
+ error_messages = [
1479
+ "Collaborative Workspace Service Container is not able to handle the SOAP request",
1480
+ "Service Group Lookup failure",
1481
+ ]
1482
+
1483
+ exist_messages = [
1484
+ "Object already exists",
1485
+ "createWorkspaceResponse",
1486
+ ]
1487
+
1488
+ request_url = self.gateway_url()
1489
+
1490
+ retries = 0
1491
+
1492
+ while retries < REQUEST_MAX_RETRIES:
1493
+ try:
1494
+ response = requests.post(
1495
+ url=request_url,
1496
+ data=create_workspace_data,
1497
+ headers=REQUEST_HEADERS_XML,
1498
+ cookies=self.cookie(),
1499
+ timeout=REQUEST_TIMEOUT,
1500
+ )
1501
+ except requests.RequestException as req_exception:
1502
+ self.logger.warning(
1503
+ "Request to create workspace -> '%s' failed with error -> %s. Retry in %d seconds...",
1504
+ workspace_name,
1505
+ str(req_exception),
1506
+ REQUEST_RETRY_DELAY,
1507
+ )
1508
+ time.sleep(REQUEST_RETRY_DELAY)
1509
+ retries += 1
1510
+ continue
1511
+
896
1512
  if response.ok:
897
1513
  self.logger.info(
898
- "Successfully created workspace -> '%s' with ID -> %s",
1514
+ "Successfully created workspace -> '%s' (%s).",
899
1515
  workspace_name,
900
1516
  workspace_id,
901
1517
  )
902
- return response.text
1518
+ # True indicates that a new workspaces has been created.
1519
+ return (self.parse_xml(response.text), True)
1520
+
903
1521
  # Check if Session has expired - then re-authenticate and try once more
904
1522
  if response.status_code == 401 and retries == 0:
905
- self.logger.warning("Session has expired - try to re-authenticate...")
1523
+ self.logger.warning("Session expired. Re-authenticating...")
906
1524
  self.authenticate(revalidate=True)
907
1525
  retries += 1
908
- self.logger.error(response.text)
909
- return response.text
1526
+ continue
1527
+
1528
+ # Check if the workspace does exist already:
1529
+ if any(exist_message in response.text for exist_message in exist_messages):
1530
+ self.logger.info("Workspace -> '%s' with ID -> '%s' already exists!", workspace_name, workspace_id)
1531
+ self.logger.debug("SOAP message -> %s", response.text)
1532
+
1533
+ # False indicates that a new workspaces has NOT been created.
1534
+ return (self.parse_xml(response.text), False)
1535
+
1536
+ # Check if any error message is in the response:
1537
+ if any(error_message in response.text for error_message in error_messages):
1538
+ self.logger.warning(
1539
+ "Workspace service error, waiting %d seconds to retry... (Retry %d of %d)",
1540
+ REQUEST_RETRY_DELAY,
1541
+ retries + 1,
1542
+ REQUEST_MAX_RETRIES,
1543
+ )
1544
+ self.logger.debug("SOAP message -> %s", response.text)
1545
+ time.sleep(REQUEST_RETRY_DELAY)
1546
+ retries += 1
1547
+
1548
+ # end while retries < REQUEST_MAX_RETRIES:
1549
+
1550
+ # After max retries, log and return the response or handle as needed
1551
+ if show_error:
1552
+ self.logger.error(
1553
+ "Max retries reached for workspace -> '%s', unable to create workspace.",
1554
+ workspace_name,
1555
+ )
1556
+ else:
1557
+ self.logger.warning(
1558
+ "Max retries reached for workspace -> '%s', unable to create workspace.",
1559
+ workspace_name,
1560
+ )
1561
+
1562
+ return (None, False)
910
1563
 
911
1564
  # end method definition
912
1565
 
@@ -925,13 +1578,17 @@ class OTAWP:
925
1578
 
926
1579
  """
927
1580
 
928
- self.logger.info(
929
- "Starting synchronization of workspace -> '%s'...",
930
- workspace_name,
931
- )
1581
+ if not workspace_id:
1582
+ self.logger.error(
1583
+ "Cannot synchronize workspace%s without a workspace ID!",
1584
+ " -> '{}'".format(workspace_name) if workspace_name else "",
1585
+ )
1586
+ return None
1587
+
1588
+ self.logger.info("Starting synchronization of workspace -> '%s' (%s)...", workspace_name, workspace_id)
932
1589
 
933
1590
  # SOAP request body
934
- license_post_body_json = f"""
1591
+ sync_workspace_data = f"""
935
1592
  <SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
936
1593
  <SOAP:Body>
937
1594
  <Synchronize workspaceID="{workspace_id}" xmlns="http://schemas.cordys.com/cws/synchronize/1.0">
@@ -942,47 +1599,62 @@ class OTAWP:
942
1599
  </SOAP:Envelope>
943
1600
  """
944
1601
 
1602
+ request_url = self.gateway_url()
1603
+
1604
+ self.logger.debug(
1605
+ "Synchronize workspace -> '%s' (%s); calling -> '%s'",
1606
+ workspace_name,
1607
+ workspace_id,
1608
+ request_url,
1609
+ )
1610
+
945
1611
  retries = 0
946
- max_retries = 6
947
- retry_delay = 60
948
1612
 
949
- while retries < max_retries:
1613
+ while retries < REQUEST_MAX_RETRIES:
950
1614
  try:
951
1615
  response = requests.post(
952
- url=self.gateway_url(),
953
- data=license_post_body_json,
954
- headers=REQUEST_HEADERS,
1616
+ url=request_url,
1617
+ data=sync_workspace_data,
1618
+ headers=REQUEST_HEADERS_XML,
955
1619
  cookies=self.cookie(),
956
1620
  timeout=SYNC_PUBLISH_REQUEST_TIMEOUT,
957
1621
  )
1622
+ except requests.RequestException as req_exception:
1623
+ self.logger.warning(
1624
+ "Request to synchronize workspace -> '%s' failed with error -> %s. Retry in %d seconds...",
1625
+ workspace_name,
1626
+ str(req_exception),
1627
+ REQUEST_RETRY_DELAY,
1628
+ )
1629
+ time.sleep(REQUEST_RETRY_DELAY)
1630
+ retries += 1
1631
+ continue
958
1632
 
959
- if response.ok:
960
- self.logger.info(
961
- "Workspace -> '%s' synchronized successfully.",
962
- workspace_name,
963
- )
964
- return self.parse_xml(response.text)
1633
+ if response.ok:
1634
+ self.logger.info("Successfully synchronized workspace -> '%s' (%s).", workspace_name, workspace_id)
1635
+ return self.parse_xml(response.text)
965
1636
 
966
- if response.status_code == 401:
967
- self.logger.warning("Session expired. Re-authenticating...")
968
- self.authenticate(revalidate=True)
969
- retries += 1
970
- continue
1637
+ # Check if Session has expired - then re-authenticate and try once more
1638
+ if response.status_code == 401 and retries == 0:
1639
+ self.logger.warning("Session expired. Re-authenticating...")
1640
+ self.authenticate(revalidate=True)
1641
+ retries += 1
1642
+ continue
971
1643
 
972
- if "faultcode" in response.text or "FaultDetails" in response.text:
973
- self.logger.warning("SOAP fault occurred: %s", response.text)
974
- retries += 1
975
- time.sleep(retry_delay)
976
- continue
1644
+ if SOAP_FAULT_INDICATOR in response.text:
1645
+ self.logger.error(
1646
+ "Workspace synchronization failed with error -> '%s' when calling -> %s!",
1647
+ self.get_soap_element(soap_response=response.text, soap_tag="faultstring"),
1648
+ self.get_soap_element(soap_response=response.text, soap_tag="faultactor"),
1649
+ )
1650
+ self.logger.debug("SOAP message -> %s", response.text)
1651
+ return None
977
1652
 
978
- self.logger.error("Unexpected error during sync: %s", response.text)
979
- time.sleep(retry_delay)
980
- retries += 1
1653
+ self.logger.error("Unexpected error during workspace synchronization -> %s", response.text)
1654
+ time.sleep(REQUEST_RETRY_DELAY)
1655
+ retries += 1
981
1656
 
982
- except requests.RequestException:
983
- self.logger.error("Sync failed with error. Proceeding with retry...")
984
- time.sleep(retry_delay)
985
- retries += 1
1657
+ # end while retries < REQUEST_MAX_RETRIES:
986
1658
 
987
1659
  self.logger.error(
988
1660
  "Synchronization failed for workspace -> '%s' after %d retries.",
@@ -996,30 +1668,55 @@ class OTAWP:
996
1668
  def publish_project(
997
1669
  self,
998
1670
  workspace_name: str,
999
- project_name: str,
1000
1671
  workspace_id: str,
1672
+ project_name: str,
1001
1673
  project_id: str,
1002
- ) -> dict | bool:
1674
+ ) -> bool:
1003
1675
  """Publish the workspace project.
1004
1676
 
1005
1677
  Args:
1006
- workspace_name (str): The name of the workspace.
1007
- project_name (str): The name of the project.
1008
- workspace_id (str): The workspace ID.
1009
- project_id (str): The project ID.
1678
+ workspace_name (str):
1679
+ The name of the workspace.
1680
+ workspace_id (str):
1681
+ The workspace ID.
1682
+ project_name (str):
1683
+ The name of the project.
1684
+ project_id (str):
1685
+ The project ID.
1010
1686
 
1011
1687
  Returns:
1012
- dict | bool: Request response (dictionary) if successful, False if it fails after retries.
1688
+ bool:
1689
+ True if successful, False if it fails after retries.
1013
1690
 
1014
1691
  """
1015
1692
 
1016
1693
  self.logger.info(
1017
- "Publish project -> '%s' in workspace -> '%s'...",
1694
+ "Publish project -> '%s' (%s) in workspace -> '%s' (%s)...",
1018
1695
  project_name,
1696
+ project_id,
1019
1697
  workspace_name,
1698
+ workspace_id,
1020
1699
  )
1021
1700
 
1022
- project_publish = f"""<SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
1701
+ # Validation of parameters:
1702
+ required_fields = {
1703
+ "workspace": workspace_name,
1704
+ "workspace ID": workspace_id,
1705
+ "project": project_name,
1706
+ "project ID": project_id,
1707
+ }
1708
+
1709
+ for name, value in required_fields.items():
1710
+ if not value:
1711
+ self.logger.error(
1712
+ "Cannot publish project%s without a %s!",
1713
+ " -> '{}'".format(project_name) if project_name else "",
1714
+ name,
1715
+ )
1716
+ return None
1717
+
1718
+ project_publish_data = f"""
1719
+ <SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
1023
1720
  <SOAP:Body>
1024
1721
  <deployObject xmlns="http://schemas.cordys.com/cws/internal/buildhelper/BuildHelper/1.0" async="false" workspaceID="{workspace_id}" xmlns:c="http://schemas.cordys.com/cws/1.0">
1025
1722
  <object>
@@ -1027,62 +1724,72 @@ class OTAWP:
1027
1724
  </object>
1028
1725
  </deployObject>
1029
1726
  </SOAP:Body>
1030
- </SOAP:Envelope>"""
1727
+ </SOAP:Envelope>
1728
+ """
1031
1729
 
1032
1730
  # Initialize retry parameters
1033
- max_retries = 10
1034
1731
  retries = 0
1035
1732
  success_indicator = "deployObjectResponse"
1036
1733
 
1037
- while retries < max_retries:
1734
+ while retries < REQUEST_MAX_RETRIES:
1038
1735
  try:
1039
1736
  response = requests.post(
1040
1737
  url=self.gateway_url(),
1041
- data=project_publish,
1042
- headers=REQUEST_HEADERS,
1738
+ data=project_publish_data,
1739
+ headers=REQUEST_HEADERS_XML,
1043
1740
  cookies=self.cookie(),
1044
1741
  timeout=SYNC_PUBLISH_REQUEST_TIMEOUT,
1045
1742
  )
1743
+ except requests.RequestException as req_exception:
1744
+ self.logger.warning(
1745
+ "Request to publish project -> '%s' (%s) failed with error -> %s. Retry in %d seconds...",
1746
+ project_name,
1747
+ project_id,
1748
+ str(req_exception),
1749
+ REQUEST_RETRY_DELAY,
1750
+ )
1751
+ retries += 1
1752
+ time.sleep(REQUEST_RETRY_DELAY)
1753
+ continue
1046
1754
 
1047
- # Check if the response is successful
1048
- if response.ok:
1049
- if success_indicator in response.text:
1050
- self.logger.info(
1051
- "Successfully published project -> '%s' in workspace -> '%s'",
1052
- project_name,
1053
- workspace_name,
1054
- )
1055
- return True
1056
- else:
1057
- self.logger.warning(
1058
- "Expected success indicator -> '%s' but it was not found in response. Retrying in 30 seconds... (Attempt %d of %d)",
1059
- success_indicator,
1060
- retries + 1,
1061
- max_retries,
1062
- )
1063
- elif response.status_code == 401:
1064
- # Check for session expiry and retry authentication
1065
- self.logger.warning("Session has expired - re-authenticating...")
1066
- self.authenticate(revalidate=True)
1755
+ # Check if the response is successful
1756
+ if response.ok:
1757
+ if success_indicator in response.text:
1758
+ self.logger.info(
1759
+ "Successfully published project -> '%s' (%s) in workspace -> '%s' (%s)",
1760
+ project_name,
1761
+ project_id,
1762
+ workspace_name,
1763
+ workspace_id,
1764
+ )
1765
+ return True
1067
1766
  else:
1068
- self.logger.error(
1069
- "Unexpected error (status code %d). Retrying in 30 seconds... (Attempt %d of %d)",
1070
- response.status_code,
1767
+ self.logger.warning(
1768
+ "Expected success indicator -> '%s' but it was not found in response. Retrying in 30 seconds... (Attempt %d of %d)",
1769
+ success_indicator,
1071
1770
  retries + 1,
1072
- max_retries,
1771
+ REQUEST_MAX_RETRIES,
1073
1772
  )
1074
- self.logger.error(
1075
- "Error details: %s",
1076
- response.text,
1077
- )
1078
- self.sync_workspace(workspace_name, workspace_id)
1079
- retries += 1
1080
- time.sleep(30)
1773
+ elif response.status_code == 401:
1774
+ # Check for session expiry and retry authentication
1775
+ self.logger.warning("Session has expired - re-authenticating...")
1776
+ self.authenticate(revalidate=True)
1777
+ else:
1778
+ self.logger.error(
1779
+ "Unexpected error (status code -> %d). Retrying in 30 seconds... (Attempt %d of %d)",
1780
+ response.status_code,
1781
+ retries + 1,
1782
+ REQUEST_MAX_RETRIES,
1783
+ )
1784
+ self.logger.debug(
1785
+ "SOAP message -> %s",
1786
+ response.text,
1787
+ )
1788
+ self.sync_workspace(workspace_name=workspace_name, workspace_id=workspace_id)
1789
+ retries += 1
1790
+ time.sleep(REQUEST_RETRY_DELAY)
1081
1791
 
1082
- except requests.RequestException:
1083
- self.logger.error("Sync failed with error. Proceeding with retry...")
1084
- retries += 1
1085
- time.sleep(30)
1792
+ # end while retries < REQUEST_MAX_RETRIES:
1086
1793
 
1087
1794
  # After reaching the maximum number of retries, log failure and return False
1088
1795
  self.logger.error(
@@ -1090,376 +1797,185 @@ class OTAWP:
1090
1797
  project_name,
1091
1798
  workspace_name,
1092
1799
  )
1800
+
1093
1801
  return False
1094
1802
 
1095
1803
  # end method definition
1096
1804
 
1097
- def create_priority(self, name: str, description: str, status: int) -> dict | None:
1098
- """Create Priority entity instances.
1805
+ def create_priority(self, name: str, description: str = "", status: int = 1) -> dict | None:
1806
+ """Create Priority entity instance.
1099
1807
 
1100
1808
  Args:
1101
- name (str): name
1102
- description (str): description
1103
- status (int): status
1809
+ name (str):
1810
+ The name of the priority.
1811
+ description (str, optional):
1812
+ The description of the priority.
1813
+ status (int, optional):
1814
+ The status of the priority. Default is 1.
1104
1815
 
1105
1816
  Returns:
1106
1817
  dict:
1107
1818
  Request response (dictionary) or None if the REST call fails
1108
1819
 
1109
- """
1110
- create_priority = {
1111
- "Properties": {"Name": name, "Description": description, "Status": status},
1112
- }
1113
- retries = 0
1114
- while True:
1115
- response = requests.post(
1116
- url=self.create_priority_url(),
1117
- json=create_priority,
1118
- headers=REQUEST_HEADERS_JSON,
1119
- cookies=self.cookie(),
1120
- timeout=REQUEST_TIMEOUT,
1121
- )
1122
- if response.ok:
1123
- self.logger.info("Priority created successfully")
1124
- return self.parse_request_response(
1125
- response_object=response,
1126
- additional_error_message="This can be normal during restart",
1127
- show_error=False,
1128
- )
1129
- if response.status_code == 401 and retries == 0:
1130
- self.logger.warning("Session has expired - try to re-authenticate...")
1131
- self.authenticate(revalidate=True)
1132
- retries += 1
1133
- self.logger.error(response.text)
1134
- return None
1135
-
1136
- # end method definition
1137
-
1138
- def get_all_priorities(self) -> dict | None:
1139
- """Get all priorities from entity.
1140
-
1141
- Args:
1142
- None
1143
-
1144
- Returns:
1145
- dict:
1146
- Request response (dictionary) or None if the REST call fails.
1147
-
1148
- """
1149
-
1150
- retries = 0
1151
- while True:
1152
- response = requests.get(
1153
- url=self.get_all_priorities_url(),
1154
- headers=REQUEST_HEADERS_JSON,
1155
- cookies=self.cookie(),
1156
- timeout=REQUEST_TIMEOUT,
1157
- )
1158
- if response.ok:
1159
- authenticate_dict = self.parse_request_response(
1160
- response_object=response,
1161
- additional_error_message="This can be normal during restart",
1162
- show_error=False,
1163
- )
1164
- if not authenticate_dict:
1165
- return None
1166
- return authenticate_dict
1167
- if response.status_code == 401 and retries == 0:
1168
- self.logger.warning("Session has expired - try to re-authenticate...")
1169
- self.authenticate(revalidate=True)
1170
- retries += 1
1171
- self.logger.error(response.text)
1172
- return None
1173
-
1174
- # end method definition
1175
-
1176
- def create_customer(
1177
- self,
1178
- customer_name: str,
1179
- legal_business_name: str,
1180
- trading_name: str,
1181
- ) -> dict | None:
1182
- """Create customer entity instance.
1183
-
1184
- Args:
1185
- customer_name (str):
1186
- The name of the customer.
1187
- legal_business_name (str):
1188
- The legal business name.
1189
- trading_name (str):
1190
- The trading name.
1191
-
1192
- Returns:
1193
- dict:
1194
- Request response (dictionary) or None if the REST call fails.
1195
-
1196
- """
1197
-
1198
- create_customer = {
1199
- "Properties": {
1200
- "CustomerName": customer_name,
1201
- "LegalBusinessName": legal_business_name,
1202
- "TradingName": trading_name,
1820
+ Example:
1821
+ {
1822
+ 'Identity': {
1823
+ 'Id': '327681'
1203
1824
  },
1825
+ '_links': {
1826
+ 'self': {
1827
+ 'href': '/OpentextCaseManagement/entities/Priority/items/327681'
1828
+ }
1829
+ }
1204
1830
  }
1205
- retries = 0
1206
- while True:
1207
- response = requests.post(
1208
- url=self.create_customer_url(),
1209
- json=create_customer,
1210
- headers=REQUEST_HEADERS_JSON,
1211
- cookies=self.cookie(),
1212
- timeout=REQUEST_TIMEOUT,
1213
- )
1214
- if response.ok:
1215
- self.logger.info("Customer record created successfully")
1216
- return self.parse_request_response(
1217
- response_object=response,
1218
- additional_error_message="This can be normal during restart",
1219
- show_error=False,
1220
- )
1221
- if response.status_code == 401 and retries == 0:
1222
- self.logger.warning("Session has expired - try to re-authenticate...")
1223
- self.authenticate(revalidate=True)
1224
- retries += 1
1225
- self.logger.error(response.text)
1226
- return None
1227
-
1228
- # end method definition
1229
-
1230
- def get_all_customers(self) -> dict | None:
1231
- """Get all customer entity imstances.
1232
-
1233
- Args:
1234
- None
1235
-
1236
- Returns:
1237
- dict:
1238
- Request response (dictionary) or None if the REST call fails
1239
1831
 
1240
1832
  """
1241
1833
 
1242
- retries = 0
1243
- while True:
1244
- response = requests.get(
1245
- url=self.get_all_customeres_url(),
1246
- headers=REQUEST_HEADERS_JSON,
1247
- cookies=self.cookie(),
1248
- timeout=REQUEST_TIMEOUT,
1249
- )
1250
- if response.ok:
1251
- authenticate_dict = self.parse_request_response(
1252
- response_object=response,
1253
- additional_error_message="This can be normal during restart",
1254
- show_error=False,
1255
- )
1256
- if not authenticate_dict:
1257
- return None
1258
- return authenticate_dict
1259
- if response.status_code == 401 and retries == 0:
1260
- self.logger.warning("Session has expired - try to re-authenticate...")
1261
- self.authenticate(revalidate=True)
1262
- retries += 1
1263
- self.logger.error(response.text)
1834
+ # Sanity checks as the parameters come directly from payload:
1835
+ if not name:
1836
+ self.logger.error("Cannot create a priority without a name!")
1264
1837
  return None
1265
1838
 
1266
- # end method definition
1267
-
1268
- def create_case_type(self, name: str, description: str, status: int) -> dict | None:
1269
- """Create case type entity instances.
1270
-
1271
- Args:
1272
- name (str):
1273
- The name of the case type.
1274
- description (str):
1275
- The description of the case type.
1276
- status (str): status
1277
-
1278
- Returns:
1279
- dict:
1280
- Request response (dictionary) or None if the REST call fails.
1281
-
1282
- """
1283
-
1284
- create_case_type = {
1839
+ create_priority_data = {
1285
1840
  "Properties": {"Name": name, "Description": description, "Status": status},
1286
1841
  }
1287
- retries = 0
1288
- while True:
1289
- response = requests.post(
1290
- url=self.create_casetype_url(),
1291
- json=create_case_type,
1292
- headers=REQUEST_HEADERS_JSON,
1293
- cookies=self.cookie(),
1294
- timeout=REQUEST_TIMEOUT,
1295
- )
1296
- if response.ok:
1297
- self.logger.info("Case type created successfully")
1298
- return self.parse_request_response(
1299
- response_object=response,
1300
- additional_error_message="This can be normal during restart",
1301
- show_error=False,
1302
- )
1303
- if response.status_code == 401 and retries == 0:
1304
- self.logger.warning("Session has expired - try to re-authenticate...")
1305
- self.authenticate(revalidate=True)
1306
- retries += 1
1307
- self.logger.error(response.text)
1308
- return None
1842
+
1843
+ request_url = self.get_create_priority_url()
1844
+
1845
+ return self.do_request(
1846
+ url=request_url,
1847
+ method="POST",
1848
+ headers=REQUEST_HEADERS_JSON,
1849
+ cookies=self.cookie(),
1850
+ json_data=create_priority_data,
1851
+ timeout=REQUEST_TIMEOUT,
1852
+ failure_message="Request to create priority -> '{}' failed".format(name),
1853
+ )
1309
1854
 
1310
1855
  # end method definition
1311
1856
 
1312
- def get_all_case_type(self) -> dict | None:
1313
- """Get all case type entty instances.
1857
+ def get_priorities(self) -> dict | None:
1858
+ """Get all priorities from entity.
1314
1859
 
1315
1860
  Args:
1316
1861
  None
1317
1862
 
1318
1863
  Returns:
1319
1864
  dict:
1320
- Request response (dictionary) or None if the REST call fails.
1865
+ Request response (dictionary with priority values) or None if the REST call fails.
1866
+
1867
+ Example:
1868
+ {
1869
+ 'page': {
1870
+ 'skip': 0,
1871
+ 'top': 0,
1872
+ 'count': 4,
1873
+ 'ftsEnabled': False
1874
+ },
1875
+ '_links': {
1876
+ 'self': {
1877
+ 'href': '/OpentextCaseManagement/entities/Priority/lists/PriorityList'
1878
+ },
1879
+ 'first': {
1880
+ 'href': '/OpentextCaseManagement/entities/Priority/lists/PriorityList'
1881
+ }
1882
+ },
1883
+ '_embedded': {
1884
+ 'PriorityList': {
1885
+ 'PriorityList': [
1886
+ {
1887
+ '_links': {
1888
+ 'href': '/OpentextCaseManagement/entities/Priority/items/1'
1889
+ },
1890
+ 'Properties': {
1891
+ 'Name': 'High',
1892
+ 'Description': 'High',
1893
+ 'Status': 1
1894
+ }
1895
+ },
1896
+ {
1897
+ '_links': {'item': {...}},
1898
+ 'Properties': {'Name': 'Medium', 'Description': 'Medium', 'Status': 1}
1899
+ },
1900
+ {
1901
+ '_links': {'item': {...}},
1902
+ 'Properties': {'Name': 'Low', 'Description': 'Low', 'Status': 1}
1903
+ },
1904
+ {
1905
+ '_links': {'item': {...}},
1906
+ 'Properties': {'Name': 'Marc Test 1', 'Description': 'Marc Test 1 Description', 'Status': 1}
1907
+ }
1908
+ ]
1909
+ }
1910
+ }
1911
+ }
1321
1912
 
1322
1913
  """
1323
1914
 
1324
- retries = 0
1325
- while True:
1326
- response = requests.get(
1327
- url=self.get_all_case_types_url(),
1328
- headers=REQUEST_HEADERS_JSON,
1329
- cookies=self.cookie(),
1330
- timeout=REQUEST_TIMEOUT,
1331
- )
1332
- if response.ok:
1333
- authenticate_dict = self.parse_request_response(
1334
- response_object=response,
1335
- additional_error_message="This can be normal during restart",
1336
- show_error=False,
1337
- )
1338
- if not authenticate_dict:
1339
- return None
1340
- return authenticate_dict
1341
- if response.status_code == 401 and retries == 0:
1342
- self.logger.warning("Session has expired - try to re-authenticate...")
1343
- self.authenticate(revalidate=True)
1344
- retries += 1
1345
- self.logger.error(response.text)
1346
- return None
1915
+ request_url = self.get_priorities_list_url()
1347
1916
 
1348
- # end method definition
1917
+ return self.do_request(
1918
+ url=request_url,
1919
+ method="GET",
1920
+ headers=REQUEST_HEADERS_JSON,
1921
+ cookies=self.cookie(),
1922
+ timeout=REQUEST_TIMEOUT,
1923
+ failure_message="Request to get priorities failed",
1924
+ )
1349
1925
 
1350
- def create_category(
1351
- self,
1352
- case_prefix: str,
1353
- description: str,
1354
- name: str,
1355
- status: int,
1356
- ) -> dict | None:
1357
- """Create category entity instance.
1926
+ # end method definition
1927
+
1928
+ def get_priority_by_name(self, name: str) -> dict | None:
1929
+ """Get priority entity instance by its name.
1358
1930
 
1359
1931
  Args:
1360
- case_prefix (str):
1361
- The prefix for the case.
1362
- description (str):
1363
- The description for the category.
1364
1932
  name (str):
1365
- The name of the case.
1366
- status (int):
1367
- The status code.
1933
+ The name of the priority.
1368
1934
 
1369
1935
  Returns:
1370
- dict:
1371
- Request response (dictionary) or None if the REST call fails.
1936
+ dict | None:
1937
+ Returns the priority item or None if it does not exist.
1372
1938
 
1373
1939
  """
1374
1940
 
1375
- create_categoty = {
1376
- "Properties": {
1377
- "CasePrefix": case_prefix,
1378
- "Description": description,
1379
- "Name": name,
1380
- "Status": status,
1381
- },
1382
- }
1383
- retries = 0
1384
- while True:
1385
- response = requests.post(
1386
- url=self.create_category_url(),
1387
- json=create_categoty,
1388
- headers=REQUEST_HEADERS_JSON,
1389
- cookies=self.cookie(),
1390
- timeout=REQUEST_TIMEOUT,
1391
- )
1392
- if response.ok:
1393
- self.logger.info("Category created successfully")
1394
- return self.parse_request_response(
1395
- response_object=response,
1396
- additional_error_message="This can be normal during restart",
1397
- show_error=False,
1398
- )
1399
- if response.status_code == 401 and retries == 0:
1400
- self.logger.warning("Session has expired - try to re-authenticate...")
1401
- self.authenticate(revalidate=True)
1402
- retries += 1
1403
- self.logger.error(response.text)
1404
- return None
1941
+ priorities = self.get_priorities()
1942
+
1943
+ return self.get_result_item(response=priorities, entity_type="PriorityList", key="Name", value=name)
1405
1944
 
1406
1945
  # end method definition
1407
1946
 
1408
- def get_all_categories(self) -> dict | None:
1409
- """Get all categories entity intances.
1947
+ def get_priority_ids(self) -> list:
1948
+ """Get all priority entity instances IDs.
1410
1949
 
1411
1950
  Args:
1412
1951
  None
1413
1952
  Returns:
1414
- dict:
1415
- Request response (dictionary) or None if the REST call fails.
1953
+ list:
1954
+ A list with all priority IDs.
1416
1955
 
1417
1956
  """
1418
1957
 
1419
- retries = 0
1420
- while True:
1421
- response = requests.get(
1422
- url=self.get_all_categories_url(),
1423
- headers=REQUEST_HEADERS_JSON,
1424
- cookies=self.cookie(),
1425
- timeout=REQUEST_TIMEOUT,
1426
- )
1427
- if response.ok:
1428
- authenticate_dict = self.parse_request_response(
1429
- response_object=response,
1430
- additional_error_message="This can be normal during restart",
1431
- show_error=False,
1432
- )
1433
- if not authenticate_dict:
1434
- return None
1435
- return authenticate_dict
1436
- if response.status_code == 401 and retries == 0:
1437
- self.logger.warning("Session has expired - try to re-authenticate...")
1438
- self.authenticate(revalidate=True)
1439
- retries += 1
1440
- self.logger.error(response.text)
1441
- return None
1958
+ priorities = self.get_priorities()
1959
+
1960
+ return self.get_result_values(response=priorities, entity_type="PriorityList", key="id") or []
1442
1961
 
1443
1962
  # end method definition
1444
1963
 
1445
- def create_sub_categoy(
1964
+ def create_customer(
1446
1965
  self,
1447
- name: str,
1448
- description: str,
1449
- status: int,
1450
- parent_id: int,
1966
+ customer_name: str,
1967
+ legal_business_name: str,
1968
+ trading_name: str,
1451
1969
  ) -> dict | None:
1452
- """Create sub categoy entity instances.
1970
+ """Create customer entity instance.
1453
1971
 
1454
1972
  Args:
1455
- name (str):
1456
- The name of the sub-category.
1457
- description (str):
1458
- The description for the sub-category.
1459
- status (int):
1460
- The status ID.
1461
- parent_id (int):
1462
- The parent ID of the category.
1973
+ customer_name (str):
1974
+ The name of the customer.
1975
+ legal_business_name (str):
1976
+ The legal business name.
1977
+ trading_name (str):
1978
+ The trading name.
1463
1979
 
1464
1980
  Returns:
1465
1981
  dict:
@@ -1467,786 +1983,810 @@ class OTAWP:
1467
1983
 
1468
1984
  """
1469
1985
 
1470
- create_sub_categoty = {
1471
- "Properties": {"Name": name, "Description": description, "Status": status},
1472
- }
1473
- retries = 0
1474
- while True:
1475
- base_url = self.baseurl()
1476
- endpoint = "/app/entityRestService/api/OpentextCaseManagement/entities/Category/items/"
1477
- child_path = "/childEntities/SubCategory?defaultinst_ct=abcd"
1478
- response = requests.post(
1479
- url=base_url + endpoint + str(parent_id) + child_path,
1480
- json=create_sub_categoty,
1481
- headers=REQUEST_HEADERS_JSON,
1482
- cookies=self.cookie(),
1483
- timeout=REQUEST_TIMEOUT,
1484
- )
1485
- if response.ok:
1486
- self.logger.info("Sub category created successfully")
1487
- return self.parse_request_response(
1488
- response_object=response,
1489
- additional_error_message="This can be normal during restart",
1490
- show_error=False,
1491
- )
1492
- if response.status_code == 401 and retries == 0:
1493
- self.logger.warning("Session has expired - try to re-authenticate...")
1494
- self.authenticate(revalidate=True)
1495
- retries += 1
1496
- self.logger.error(response.text)
1986
+ # Sanity checks as the parameters come directly from payload:
1987
+ if not customer_name:
1988
+ self.logger.error("Cannot create a customer without a name!")
1497
1989
  return None
1498
1990
 
1991
+ create_customer_data = {
1992
+ "Properties": {
1993
+ "CustomerName": customer_name,
1994
+ "LegalBusinessName": legal_business_name,
1995
+ "TradingName": trading_name,
1996
+ },
1997
+ }
1998
+
1999
+ request_url = self.get_create_customer_url()
2000
+
2001
+ return self.do_request(
2002
+ url=request_url,
2003
+ method="POST",
2004
+ headers=REQUEST_HEADERS_JSON,
2005
+ cookies=self.cookie(),
2006
+ json_data=create_customer_data,
2007
+ timeout=REQUEST_TIMEOUT,
2008
+ failure_message="Request to create customer -> '{}' failed".format(customer_name),
2009
+ )
2010
+
1499
2011
  # end method definition
1500
2012
 
1501
- def get_all_sub_categeries(self, parent_id: int) -> dict | None:
1502
- """Get all sub categeries entity instances.
2013
+ def get_customers(self) -> dict | None:
2014
+ """Get all customer entity instances.
1503
2015
 
1504
2016
  Args:
1505
- parent_id (int):
1506
- The parent ID of the sub categories.
2017
+ None
1507
2018
 
1508
2019
  Returns:
1509
- dict:
2020
+ dict | None:
1510
2021
  Request response (dictionary) or None if the REST call fails.
1511
2022
 
2023
+ Example:
2024
+ {
2025
+ 'page': {
2026
+ 'skip': 0,
2027
+ 'top': 0,
2028
+ 'count': 4,
2029
+ 'ftsEnabled': False
2030
+ },
2031
+ '_links': {
2032
+ 'self': {
2033
+ 'href': '/OpentextCaseManagement/entities/Customer/lists/CustomerList'
2034
+ },
2035
+ 'first': {...}
2036
+ },
2037
+ '_embedded': {
2038
+ 'CustomerList': [
2039
+ {
2040
+ '_links': {
2041
+ 'item': {
2042
+ 'href': '/OpentextCaseManagement/entities/Customer/items/1'
2043
+ }
2044
+ },
2045
+ 'Properties': {
2046
+ 'CustomerName': 'InaPlex Limited',
2047
+ 'LegalBusinessName': 'InaPlex Limited',
2048
+ 'TradingName': 'InaPlex Limited'
2049
+ }
2050
+ },
2051
+ {
2052
+ '_links': {...},
2053
+ 'Properties': {...}
2054
+ },
2055
+ ...
2056
+ ]
2057
+ }
2058
+ }
2059
+
1512
2060
  """
1513
2061
 
1514
- retries = 0
1515
- while True:
1516
- base_url = self.baseurl()
1517
- endpoint = "/app/entityRestService/api/OpentextCaseManagement/entities/Category/items/"
1518
- child_path = "/childEntities/SubCategory"
1519
- response = requests.get(
1520
- url=base_url + endpoint + str(parent_id) + child_path,
1521
- headers=REQUEST_HEADERS_JSON,
1522
- cookies=self.cookie(),
1523
- timeout=REQUEST_TIMEOUT,
1524
- )
1525
- if response.ok:
1526
- authenticate_dict = self.parse_request_response(
1527
- response_object=response,
1528
- additional_error_message="This can be normal during restart",
1529
- show_error=False,
1530
- )
1531
- if not authenticate_dict:
1532
- return None
1533
- return authenticate_dict
1534
- if response.status_code == 401 and retries == 0:
1535
- self.logger.warning("Session has expired - try to re-authenticate...")
1536
- self.authenticate(revalidate=True)
1537
- retries += 1
1538
- self.logger.error(response.text)
1539
- return None
2062
+ request_url = self.get_customers_list_url()
2063
+
2064
+ return self.do_request(
2065
+ url=request_url,
2066
+ method="GET",
2067
+ headers=REQUEST_HEADERS_JSON,
2068
+ cookies=self.cookie(),
2069
+ timeout=REQUEST_TIMEOUT,
2070
+ failure_message="Request to get customers failed",
2071
+ )
1540
2072
 
1541
2073
  # end method definition
1542
2074
 
1543
- def create_loan(
1544
- self,
1545
- subject: str,
1546
- description: str,
1547
- loan_amount: str,
1548
- loan_duration_in_months: str,
1549
- category: str,
1550
- subcategory: str,
1551
- priority: str,
1552
- service: str,
1553
- customer: str,
1554
- ) -> dict | None:
1555
- """Create loan entity instance.
2075
+ def get_customer_by_name(self, name: str) -> dict | None:
2076
+ """Get customer entity instance by its name.
1556
2077
 
1557
2078
  Args:
1558
- subject (str): subject
1559
- description (str): description
1560
- loan_amount (str): loan_amount
1561
- loan_duration_in_months (str): loan_duration_in_months
1562
- category (str): category
1563
- subcategory (str): subcategory
1564
- priority (str): priority
1565
- service (str): service
1566
- customer (str): customer
1567
- Returns:
1568
- dict: Request response (dictionary) or None if the REST call fails
1569
-
1570
- """
1571
-
1572
- create_loan = f"""<SOAP:Envelope xmlns:SOAP=\"http://schemas.xmlsoap.org/soap/envelope/\">\r\n
1573
- <SOAP:Body>\r\n
1574
- <CreateCase xmlns=\"http://schemas/OpentextCaseManagement/Case/operations\">\r\n
1575
- <ns0:Case-create xmlns:ns0=\"http://schemas/OpentextCaseManagement/Case\">\r\n
1576
- <ns0:Subject>{subject}</ns0:Subject>\r\n
1577
- <ns0:Description>{description}</ns0:Description>\r\n
1578
- <ns0:LoanAmount>{loan_amount}</ns0:LoanAmount>\r\n
1579
- <ns0:LoanDurationInMonths>{loan_duration_in_months}</ns0:LoanDurationInMonths>\r\n
1580
- \r\n
1581
- <ns0:CaseType>\r\n
1582
- <ns1:CaseType-id xmlns:ns1=\"http://schemas/OpentextCaseManagement/CaseType\">\r\n
1583
- <ns1:Id>{service}</ns1:Id>\r\n
1584
- </ns1:CaseType-id>\r\n
1585
- </ns0:CaseType>\r\n
1586
- \r\n
1587
- <ns0:Category>\r\n
1588
- <ns2:Category-id xmlns:ns2=\"http://schemas/OpentextCaseManagement/Category\">\r\n
1589
- <ns2:Id>{category}</ns2:Id>\r\n
1590
- </ns2:Category-id>\r\n
1591
- </ns0:Category>\r\n
1592
- \r\n
1593
- <ns0:SubCategory>\r\n
1594
- <ns5:SubCategory-id xmlns:ns5=\"http://schemas/OpentextCaseManagement/Category.SubCategory\">\r\n
1595
- <ns5:Id>{category}</ns5:Id>\r\n
1596
- <ns5:Id1>{subcategory}</ns5:Id1>\r\n
1597
- </ns5:SubCategory-id>\r\n
1598
- </ns0:SubCategory>\r\n
1599
- \r\n
1600
- <ns0:Priority>\r\n
1601
- <ns3:Priority-id xmlns:ns3=\"http://schemas/OpentextCaseManagement/Priority\">\r\n
1602
- <ns3:Id>{priority}</ns3:Id>\r\n
1603
- </ns3:Priority-id>\r\n
1604
- </ns0:Priority>\r\n
1605
- \r\n
1606
- <ns0:ToCustomer>\r\n
1607
- <ns9:Customer-id xmlns:ns9=\"http://schemas/OpentextCaseManagement/Customer\">\r\n
1608
- <ns9:Id>{customer}</ns9:Id>\r\n
1609
- </ns9:Customer-id>\r\n
1610
- </ns0:ToCustomer>\r\n
1611
- \r\n
1612
- </ns0:Case-create>\r\n
1613
- </CreateCase>\r\n
1614
- </SOAP:Body>\r\n
1615
- </SOAP:Envelope>"""
2079
+ name (str):
2080
+ The name of the customer.
1616
2081
 
1617
- retries = 0
1618
- while True:
1619
- response = requests.post(
1620
- url=self.gateway_url(),
1621
- data=create_loan,
1622
- headers=REQUEST_HEADERS,
1623
- cookies=self.cookie(),
1624
- timeout=REQUEST_TIMEOUT,
1625
- )
1626
- if response.ok:
1627
- self.logger.info("Loan created successfully")
1628
- return self.parse_xml(response.text)
1629
- if response.status_code == 401 and retries == 0:
1630
- self.logger.warning("Session has expired - try to re-authenticate...")
1631
- self.authenticate(revalidate=True)
1632
- retries += 1
1633
- self.logger.error(response.text)
1634
- return None
2082
+ Returns:
2083
+ dict | None:
2084
+ Returns the customer data or None if no customer with the given name exists.
2085
+
2086
+ """
2087
+
2088
+ customers = self.get_customers()
2089
+
2090
+ return self.get_result_item(response=customers, entity_type="CustomerList", key="CustomerName", value=name)
1635
2091
 
1636
2092
  # end method definition
1637
2093
 
1638
- def get_all_loan(self) -> dict | None:
1639
- """Get all loan entity instances.
2094
+ def get_customer_ids(self) -> list:
2095
+ """Get all customer entity instances IDs.
1640
2096
 
1641
2097
  Args:
1642
- None
1643
-
2098
+ None
1644
2099
  Returns:
1645
- dict:
1646
- Request response (dictionary) or None if the REST call fails.
2100
+ list:
2101
+ A list of all customer IDs.
1647
2102
 
1648
2103
  """
1649
2104
 
1650
- retries = 0
1651
- while True:
1652
- response = requests.get(
1653
- url=self.get_all_loans_url(),
1654
- headers=REQUEST_HEADERS_JSON,
1655
- cookies=self.cookie(),
1656
- timeout=REQUEST_TIMEOUT,
1657
- )
1658
- if response.ok:
1659
- authenticate_dict = self.parse_request_response(
1660
- response_object=response,
1661
- additional_error_message="This can be normal during restart",
1662
- show_error=False,
1663
- )
1664
- if not authenticate_dict:
1665
- return None
1666
- return authenticate_dict
1667
- if response.status_code == 401 and retries == 0:
1668
- self.logger.warning("Session has expired - try to re-authenticate...")
1669
- self.authenticate(revalidate=True)
1670
- retries += 1
1671
- else:
1672
- self.logger.error(response.text)
1673
- return None
2105
+ customers = self.get_customers()
2106
+
2107
+ return self.get_result_values(response=customers, entity_type="CustomerList", key="id") or []
1674
2108
 
1675
2109
  # end method definition
1676
2110
 
1677
- def validate_workspace_response(self, response: str, workspace_name: str) -> bool:
1678
- """Verify if the workspace exists or was created successfully.
2111
+ def create_case_type(self, name: str, description: str = "", status: int = 1) -> dict | None:
2112
+ """Create case type entity instances.
1679
2113
 
1680
2114
  Args:
1681
- response (str): response to validate
1682
- workspace_name (str): The name of the workspace.
2115
+ name (str):
2116
+ The name of the case type.
2117
+ description (str, optional):
2118
+ The description of the case type.
2119
+ status (int, optional): status
1683
2120
 
1684
2121
  Returns:
1685
- bool: True if the workspace exists or was created successfully, else False.
2122
+ dict:
2123
+ Request response (dictionary) or None if the REST call fails.
1686
2124
 
1687
2125
  """
1688
2126
 
1689
- if "Object already exists" in response or "createWorkspaceResponse" in response:
1690
- self.logger.info(
1691
- "The workspace already exists or was created with the name -> '%s'",
1692
- workspace_name,
1693
- )
1694
- return True
2127
+ # Sanity checks as the parameters come directly from payload:
2128
+ if not name:
2129
+ self.logger.error("Cannot create a case type without a name!")
2130
+ return None
1695
2131
 
1696
- self.logger.info(
1697
- "The workspace -> '%s' does not exist or was not created. Please verify configurtion!",
1698
- workspace_name,
2132
+ create_case_type_data = {
2133
+ "Properties": {"Name": name, "Description": description, "Status": status},
2134
+ }
2135
+
2136
+ request_url = self.get_create_casetype_url()
2137
+
2138
+ return self.do_request(
2139
+ url=request_url,
2140
+ method="POST",
2141
+ headers=REQUEST_HEADERS_JSON,
2142
+ cookies=self.cookie(),
2143
+ json_data=create_case_type_data,
2144
+ timeout=REQUEST_TIMEOUT,
2145
+ failure_message="Request to create case type -> '{}' failed".format(name),
1699
2146
  )
1700
- return False
1701
2147
 
1702
2148
  # end method definition
1703
2149
 
1704
- def is_workspace_already_exists(self, response: str, workspace_name: str) -> bool:
1705
- """Verify if workspace exists.
2150
+ def get_case_types(self) -> dict | None:
2151
+ """Get all case type entity instances.
1706
2152
 
1707
2153
  Args:
1708
- response (str):
1709
- The response.
1710
- workspace_name (str):
1711
- The name of the workspace.
2154
+ None
1712
2155
 
1713
2156
  Returns:
1714
- bool:
1715
- Return True if workspace exist else return false.
2157
+ dict:
2158
+ Request response (dictionary) or None if the REST call fails.
2159
+
2160
+ Example:
2161
+ {
2162
+ 'page': {
2163
+ 'skip': 0,
2164
+ 'top': 0,
2165
+ 'count': 5,
2166
+ 'ftsEnabled': False
2167
+ },
2168
+ '_links': {
2169
+ 'self': {
2170
+ 'href': '/OpentextCaseManagement/entities/CaseType/lists/AllCaseTypes'
2171
+ },
2172
+ 'first': {...}
2173
+ },
2174
+ '_embedded': {
2175
+ 'AllCaseTypes': [
2176
+ {
2177
+ '_links': {
2178
+ 'item': {
2179
+ 'href': '/OpentextCaseManagement/entities/CaseType/items/1'
2180
+ }
2181
+ },
2182
+ 'Properties': {
2183
+ 'Name': 'Query',
2184
+ 'Description': 'Query',
2185
+ 'Status': 1
2186
+ }
2187
+ },
2188
+ {
2189
+ '_links': {...},
2190
+ 'Properties': {...}
2191
+ },
2192
+ ...
2193
+ ]
2194
+ }
2195
+ }
1716
2196
 
1717
2197
  """
1718
2198
 
1719
- if "Object already exists" in response:
1720
- self.logger.info(
1721
- "The workspace already exists with the name -> '%s'",
1722
- workspace_name,
1723
- )
1724
- return True
1725
- self.logger.info(
1726
- "The Workspace has been created with the name -> '%s'",
1727
- workspace_name,
2199
+ request_url = self.get_casetypes_list_url()
2200
+
2201
+ return self.do_request(
2202
+ url=request_url,
2203
+ method="GET",
2204
+ headers=REQUEST_HEADERS_JSON,
2205
+ cookies=self.cookie(),
2206
+ timeout=REQUEST_TIMEOUT,
2207
+ failure_message="Request to get case types failed",
1728
2208
  )
1729
- return False
1730
2209
 
1731
2210
  # end method definition
1732
2211
 
1733
- def create_workspace_with_retry(
1734
- self,
1735
- workspace_name: str,
1736
- workspace_gui_id: str,
1737
- ) -> dict | None:
1738
- """Call create_workspace and retries if the response contains specific error messages.
1739
-
1740
- Retries until the response does not contain the errors or a max retry limit is reached.
2212
+ def get_case_type_by_name(self, name: str) -> dict | None:
2213
+ """Get case type entity instance by its name.
1741
2214
 
1742
2215
  Args:
1743
- workspace_name (str):
1744
- The workspace name.
1745
- workspace_gui_id (str):
1746
- The workspace GUI ID.
2216
+ name (str):
2217
+ The name of the case type.
1747
2218
 
1748
2219
  Returns:
1749
2220
  dict | None:
1750
- The response of the workspace creation or None in case an error occured.
2221
+ Returns the case type data or None if no case type with the given name exists.
1751
2222
 
1752
2223
  """
1753
2224
 
1754
- max_retries = 20 # Define the maximum number of retries
1755
- retries = 0
1756
- error_messages = [
1757
- "Collaborative Workspace Service Container is not able to handle the SOAP request",
1758
- "Service Group Lookup failure",
1759
- ]
1760
-
1761
- while retries < max_retries:
1762
- response = self.create_workspace(workspace_name, workspace_gui_id)
1763
-
1764
- # Check if any error message is in the response
1765
- if any(error_message in response for error_message in error_messages):
1766
- self.logger.info(
1767
- "Workspace service error, waiting 60 seconds to retry... (Retry %d of %d)",
1768
- retries + 1,
1769
- max_retries,
1770
- )
1771
- time.sleep(60)
1772
- retries += 1
1773
- else:
1774
- self.logger.info("Collaborative Workspace Service Container is ready")
1775
- return response
2225
+ case_types = self.get_case_types()
1776
2226
 
1777
- # After max retries, log and return the response or handle as needed
1778
- self.logger.error(
1779
- "Max retries reached for workspace -> '%s', unable to create successfully.",
1780
- workspace_name,
1781
- )
1782
- return response
2227
+ return self.get_result_item(response=case_types, entity_type="AllCaseTypes", key="Name", value=name)
1783
2228
 
1784
2229
  # end method definition
1785
2230
 
1786
- def loan_management_runtime(self) -> dict | None:
1787
- """Create all runtime objects for loan management application.
2231
+ def get_case_type_ids(self) -> list:
2232
+ """Get All CaseType entity instances IDs.
1788
2233
 
1789
2234
  Args:
1790
2235
  None
2236
+
1791
2237
  Returns:
1792
- None
2238
+ list:
2239
+ List of all case type IDs.
1793
2240
 
1794
2241
  """
1795
2242
 
1796
- self.logger.debug(" RUNTIME -->> Category instance creation started ........ ")
1797
- category_resp_dict = []
1798
- if not self.verify_category_exists("Short Term Loan"):
1799
- self.create_category("LOAN", "Short Term Loan", "Short Term Loan", 1)
1800
- if not self.verify_category_exists("Long Term Loan"):
1801
- self.create_category("LOAN", "Long Term Loan", "Long Term Loan", 1)
1802
- if not self.verify_category_exists("Flexi Loan"):
1803
- self.create_category("LOAN", "Flexi Loan", "Flexi Loan", 1)
1804
- category_resp_dict = self.get_category_lists()
1805
- self.logger.debug(" RUNTIME -->> Category instance creation ended")
2243
+ case_types = self.get_case_types()
1806
2244
 
1807
- ############################# Sub category
1808
- self.logger.debug(
1809
- " RUNTIME -->> Sub Category instance creation started ........",
1810
- )
1811
- stl = 0
1812
- ltl = 0
1813
- fl = 0
1814
- if not self.verify_sub_category_exists("Business", 0, category_resp_dict):
1815
- response_dict = self.create_sub_categoy(
1816
- "Business",
1817
- "Business",
1818
- 1,
1819
- category_resp_dict[0],
1820
- )
1821
- stl = response_dict["Identity"]["Id"]
1822
- self.logger.info("Sub category id stl: %s ", stl)
1823
- else:
1824
- stl = self.return_sub_category_exists_id("Business", 0, category_resp_dict)
1825
- self.logger.info("Sub category id stl -> %s ", stl)
1826
-
1827
- if not self.verify_sub_category_exists("Business", 1, category_resp_dict):
1828
- response_dict = self.create_sub_categoy(
1829
- "Business",
1830
- "Business",
1831
- 1,
1832
- category_resp_dict[1],
1833
- )
1834
- ltl = response_dict["Identity"]["Id"]
1835
- self.logger.info("Sub category id ltl -> %s ", ltl)
1836
- else:
1837
- ltl = self.return_sub_category_exists_id(name="Business", index=1, category_resp_dict=category_resp_dict)
1838
- self.logger.info("Sub category id ltl -> %s ", ltl)
1839
- if not self.verify_sub_category_exists(name="Business", index=2, category_resp_dict=category_resp_dict):
1840
- response_dict = self.create_sub_categoy(
1841
- "Business",
1842
- "Business",
1843
- 1,
1844
- category_resp_dict[2],
1845
- )
1846
- fl = response_dict["Identity"]["Id"]
1847
- self.logger.info("Sub category id fl -> %s ", fl)
1848
- else:
1849
- fl = self.return_sub_category_exists_id(name="Business", index=2, category_resp_dict=category_resp_dict)
1850
- self.logger.info("Sub category id fl -> %s ", fl)
1851
- self.logger.debug(" RUNTIME -->> Sub Category instance creation ended")
1852
-
1853
- ############################# Case Types
1854
- self.logger.debug(" RUNTIME -->> Case Types instance creation started ........")
1855
- case_type_list = []
1856
-
1857
- if not self.vverify_case_type_exists("Query"):
1858
- self.create_case_type("Query", "Query", 1)
1859
- if not self.vverify_case_type_exists("Help"):
1860
- self.create_case_type("Help", "Help", 1)
1861
- if not self.vverify_case_type_exists("Update Contact Details"):
1862
- self.create_case_type("Update Contact Details", "Update Contact Details", 1)
1863
- if not self.vverify_case_type_exists("New Loan Request"):
1864
- self.create_case_type("New Loan Request", "New Loan Request", 1)
1865
- if not self.vverify_case_type_exists("Loan Closure"):
1866
- self.create_case_type("Loan Closure", "Loan Closure", 1)
1867
- case_type_list = self.get_case_type_lists()
1868
- self.logger.debug(" RUNTIME -->> Case Types instance creation ended")
1869
-
1870
- ############################# CUSTMOR
1871
- self.logger.debug(" RUNTIME -->> Customer instance creation stated ........")
1872
- customer_list = []
1873
- if not self.verify_customer_exists("InaPlex Limited"):
1874
- self.create_customer(
1875
- "InaPlex Limited",
1876
- "InaPlex Limited",
1877
- "InaPlex Limited",
1878
- )
2245
+ return self.get_result_values(response=case_types, entity_type="AllCaseTypes", key="id") or []
1879
2246
 
1880
- if not self.verify_customer_exists("Interwoven, Inc"):
1881
- self.create_customer(
1882
- "Interwoven, Inc",
1883
- "Interwoven, Inc",
1884
- "Interwoven, Inc",
1885
- )
2247
+ # end method definition
2248
+
2249
+ def create_category(
2250
+ self,
2251
+ case_prefix: str,
2252
+ name: str,
2253
+ description: str,
2254
+ status: int = 1,
2255
+ ) -> dict | None:
2256
+ """Create category entity instance.
2257
+
2258
+ Args:
2259
+ case_prefix (str):
2260
+ The prefix for the case.
2261
+ description (str):
2262
+ The description for the category.
2263
+ name (str):
2264
+ The name of the category.
2265
+ status (int):
2266
+ The status code.
2267
+
2268
+ Returns:
2269
+ dict:
2270
+ Request response (dictionary) or None if the REST call fails.
2271
+
2272
+ Example:
2273
+ {
2274
+ 'Identity': {
2275
+ 'Id': '327681'
2276
+ },
2277
+ '_links': {
2278
+ 'self': {
2279
+ href': '/OpentextCaseManagement/entities/Category/items/327681'
2280
+ }
2281
+ }
2282
+ }
1886
2283
 
1887
- if not self.verify_customer_exists("Jones Lang LaSalle"):
1888
- self.create_customer(
1889
- "Jones Lang LaSalle",
1890
- "Jones Lang LaSalle",
1891
- "Jones Lang LaSalle",
1892
- )
2284
+ """
1893
2285
 
1894
- if not self.verify_customer_exists("Key Point Consulting"):
1895
- self.create_customer(
1896
- "Key Point Consulting",
1897
- "Key Point Consulting",
1898
- "Key Point Consulting",
1899
- )
2286
+ # Sanity checks as the parameters come directly from payload:
2287
+ if not name:
2288
+ self.logger.error("Cannot create a category without a name!")
2289
+ return None
1900
2290
 
1901
- customer_list = self.get_customer_lists()
1902
- self.logger.debug(" RUNTIME -->> Customer instance creation ended")
1903
-
1904
- ######################################## PRIORITY
1905
- self.logger.debug(" RUNTIME -->> priority instance creation started ........")
1906
- prioity_list = []
1907
- if not self.verify_priority_exists("High"):
1908
- self.create_priority("High", "High", 1)
1909
- if not self.verify_priority_exists("Medium"):
1910
- self.create_priority("Medium", "Medium", 1)
1911
- if not self.verify_priority_exists("Low"):
1912
- self.create_priority("Low", "Low", 1)
1913
- prioity_list = self.get_priority_lists()
1914
- self.logger.debug(" RUNTIME -->> priority instance creation ended")
1915
-
1916
- ############################# LOAN
1917
- loan_for_business = "Loan for Business1"
1918
- loan_for_corporate_business = "Loan for Corporate Business1"
1919
- loan_for_business_loan_request = "Loan for Business Loan Request1"
1920
-
1921
- self.logger.debug(" RUNTIME -->> loan instance creation started ........")
1922
- loan_resp_dict = self.get_all_loan()
1923
- names = [item["Properties"]["Subject"] for item in loan_resp_dict["_embedded"]["AllCasesList"]]
1924
- if loan_for_business in names:
1925
- self.logger.info("Customer record Loan_for_business exists")
1926
- else:
1927
- self.logger.info("Creating customer Record with Loan_for_business ")
1928
- response_dict = self.create_loan(
1929
- subject=loan_for_business,
1930
- description=loan_for_business,
1931
- loan_amount=1,
1932
- loan_duration_in_months=2,
1933
- category=category_resp_dict[0],
1934
- subcategory=stl,
1935
- priority=prioity_list[0],
1936
- service=case_type_list[0],
1937
- customer=customer_list[0],
1938
- )
2291
+ create_category_data = {
2292
+ "Properties": {
2293
+ "CasePrefix": case_prefix,
2294
+ "Description": description,
2295
+ "Name": name,
2296
+ "Status": status,
2297
+ },
2298
+ }
1939
2299
 
1940
- if loan_for_corporate_business in names:
1941
- self.logger.info("Customer record Loan_for_Corporate_Business exists")
1942
- else:
1943
- self.logger.info(
1944
- "Creating customer Record with Loan_for_Corporate_Business ",
1945
- )
1946
- response_dict = self.create_loan(
1947
- subject=loan_for_corporate_business,
1948
- description=loan_for_corporate_business,
1949
- loan_amount=1,
1950
- loan_duration_in_months=2,
1951
- category=category_resp_dict[1],
1952
- subcategory=ltl,
1953
- priority=prioity_list[1],
1954
- service=case_type_list[1],
1955
- customer=customer_list[1],
1956
- )
2300
+ request_url = self.get_create_category_url()
1957
2301
 
1958
- if loan_for_business_loan_request in names:
1959
- self.logger.info("Customer record Loan_for_business_Loan_Request exists")
1960
- else:
1961
- self.logger.info(
1962
- "Creating customer Record with loan_for_business_loan_request",
1963
- )
1964
- response_dict = self.create_loan(
1965
- subject=loan_for_business_loan_request,
1966
- description=loan_for_business_loan_request,
1967
- loan_amount=1,
1968
- loan_duration_in_months=2,
1969
- category=category_resp_dict[2],
1970
- subcategory=fl,
1971
- priority=prioity_list[2],
1972
- service=case_type_list[2],
1973
- customer=customer_list[2],
1974
- )
1975
- self.logger.debug(" RUNTIME -->> loan instance creation ended")
2302
+ return self.do_request(
2303
+ url=request_url,
2304
+ method="POST",
2305
+ headers=REQUEST_HEADERS_JSON,
2306
+ cookies=self.cookie(),
2307
+ json_data=create_category_data,
2308
+ timeout=REQUEST_TIMEOUT,
2309
+ failure_message="Request to create category -> '{}' failed".format(name),
2310
+ )
1976
2311
 
1977
2312
  # end method definition
1978
2313
 
1979
- def get_category_lists(self) -> list:
1980
- """Get All category entty instances id's.
2314
+ def get_categories(self) -> dict | None:
2315
+ """Get all categories entity instances.
1981
2316
 
1982
2317
  Args:
1983
2318
  None
1984
2319
  Returns:
1985
- list: list of category IDs
2320
+ dict | None:
2321
+ Request response (dictionary) or None if the REST call fails.
2322
+
2323
+ Example:
2324
+ {
2325
+ 'page': {
2326
+ 'skip': 0,
2327
+ 'top': 0,
2328
+ 'count': 3,
2329
+ 'ftsEnabled': False
2330
+ },
2331
+ '_links': {
2332
+ 'self': {
2333
+ 'href': '/OpentextCaseManagement/entities/Category/lists/CategoryList'
2334
+ },
2335
+ 'first': {...}
2336
+ },
2337
+ '_embedded': {
2338
+ 'CategoryList': [
2339
+ {
2340
+ '_links': {
2341
+ 'item': {
2342
+ 'href': '/OpentextCaseManagement/entities/Category/items/1'
2343
+ }
2344
+ },
2345
+ 'Properties': {
2346
+ 'Name': 'Short Term Loan',
2347
+ 'Description': 'Short Term Loan',
2348
+ 'CasePrefix': 'LOAN',
2349
+ 'Status': 1
2350
+ }
2351
+ },
2352
+ {
2353
+ '_links': {...},
2354
+ 'Properties': {...}
2355
+ },
2356
+ {
2357
+ '_links': {...},
2358
+ 'Properties': {...}
2359
+ }
2360
+ ]
2361
+ }
2362
+ }
1986
2363
 
1987
2364
  """
1988
2365
 
1989
- category_resp_dict = []
1990
- categoy_resp_dict = self.get_all_categories()
1991
- for item in categoy_resp_dict["_embedded"]["CategoryList"]:
1992
- first_item_href = item["_links"]["item"]["href"]
1993
- integer_value = int(re.search(r"\d+", first_item_href).group())
1994
- self.logger.info("Category created with ID -> %d", integer_value)
1995
- category_resp_dict.append(integer_value)
1996
- self.logger.info("All extracted category IDs -> %s", category_resp_dict)
2366
+ request_url = self.get_categories_list_url()
1997
2367
 
1998
- return category_resp_dict
2368
+ return self.do_request(
2369
+ url=request_url,
2370
+ method="GET",
2371
+ headers=REQUEST_HEADERS_JSON,
2372
+ cookies=self.cookie(),
2373
+ timeout=REQUEST_TIMEOUT,
2374
+ failure_message="Request to get categories failed",
2375
+ )
1999
2376
 
2000
2377
  # end method definition
2001
2378
 
2002
- def get_case_type_lists(self) -> list:
2003
- """Get All CaseType entity instances IDs.
2379
+ def get_category_by_name(self, name: str) -> dict | None:
2380
+ """Get category entity instance by its name.
2381
+
2382
+ The category ID is only provided by the 'href' in '_links' / 'item'.
2004
2383
 
2005
2384
  Args:
2006
- None
2385
+ name (str):
2386
+ The name of the category.
2007
2387
 
2008
2388
  Returns:
2009
- list:
2010
- List of all case type IDs.
2389
+ dict | None:
2390
+ Returns the category item or None if a category with the given name does not exist.
2391
+
2392
+ Example:
2393
+ {
2394
+ '_links': {
2395
+ 'item': {
2396
+ 'href': '/OpentextCaseManagement/entities/Category/items/327681'
2397
+ }
2398
+ },
2399
+ 'Properties': {
2400
+ 'Name': 'Test 1',
2401
+ 'Description': 'Test 1 Description',
2402
+ 'CasePrefix': 'TEST',
2403
+ 'Status': 1
2404
+ }
2405
+ }
2011
2406
 
2012
2407
  """
2013
2408
 
2014
- case_type_list = []
2015
- casetype_resp_dict = self.get_all_case_type()
2016
- for item in casetype_resp_dict["_embedded"]["AllCaseTypes"]:
2017
- first_item_href = item["_links"]["item"]["href"]
2018
- integer_value = int(re.search(r"\d+", first_item_href).group())
2019
- self.logger.info("Case type created with ID -> %d", integer_value)
2020
- case_type_list.append(integer_value)
2021
- self.logger.info("All extracted case type IDs -> %s", case_type_list)
2409
+ categories = self.get_categories()
2022
2410
 
2023
- return case_type_list
2411
+ return self.get_result_item(response=categories, entity_type="CategoryList", key="Name", value=name)
2024
2412
 
2025
2413
  # end method definition
2026
2414
 
2027
- def get_customer_lists(self) -> list:
2028
- """Get all customer entity instances id's.
2415
+ def get_category_ids(self) -> list:
2416
+ """Get All category entity instances IDs.
2029
2417
 
2030
2418
  Args:
2031
2419
  None
2032
2420
  Returns:
2033
- list:
2034
- A list of all customer IDs.
2421
+ list: list of category IDs
2035
2422
 
2036
2423
  """
2037
2424
 
2038
- customer_list = []
2039
- customer_resp_dict = self.get_all_customers()
2040
- for item in customer_resp_dict["_embedded"]["CustomerList"]:
2041
- first_item_href = item["_links"]["item"]["href"]
2042
- integer_value = int(re.search(r"\d+", first_item_href).group())
2043
- self.logger.info("Customer created with ID -> %d", integer_value)
2044
- customer_list.append(integer_value)
2045
- self.logger.info("All extracted Customer IDs -> %s ", customer_list)
2425
+ categories = self.get_categories()
2046
2426
 
2047
- return customer_list
2427
+ return self.get_result_values(response=categories, entity_type="CategoryList", key="id") or []
2048
2428
 
2049
2429
  # end method definition
2050
2430
 
2051
- def get_priority_lists(self) -> list:
2052
- """Get all priority entity instances IDs.
2431
+ def create_sub_category(
2432
+ self,
2433
+ parent_id: int,
2434
+ name: str,
2435
+ description: str = "",
2436
+ status: int = 1,
2437
+ ) -> dict | None:
2438
+ """Create sub categoy entity instances.
2053
2439
 
2054
2440
  Args:
2055
- None
2441
+ parent_id (int):
2442
+ The parent ID of the category.
2443
+ name (str):
2444
+ The name of the sub-category.
2445
+ description (str, optional):
2446
+ The description for the sub-category.
2447
+ status (int, optional):
2448
+ The status ID. Default is 1.
2449
+
2056
2450
  Returns:
2057
- list:
2058
- A list with all priority IDs.
2451
+ dict:
2452
+ Request response (dictionary) or None if the REST call fails.
2059
2453
 
2060
2454
  """
2061
2455
 
2062
- prioity_list = []
2063
- authenticate_dict = self.get_all_priorities()
2064
- for item in authenticate_dict["_embedded"]["PriorityList"]:
2065
- first_item_href = item["_links"]["item"]["href"]
2066
- integer_value = int(re.search(r"\d+", first_item_href).group())
2067
- self.logger.info("Priority created with ID -> %d", integer_value)
2068
- prioity_list.append(integer_value)
2069
- self.logger.info("All extracted priority IDs -> %s ", prioity_list)
2456
+ # Sanity checks as the parameters come directly from payload:
2457
+ if not name:
2458
+ self.logger.error("Cannot create a sub-category without a name!")
2459
+ return None
2460
+ if not parent_id:
2461
+ self.logger.error("Cannot create a sub-category -> '%s' without a parent category ID!", name)
2462
+ return None
2463
+
2464
+ create_sub_category_data = {
2465
+ "Properties": {"Name": name, "Description": description, "Status": status},
2466
+ }
2467
+
2468
+ request_url = (
2469
+ self.config()["categoryUrl"] + "/items/" + str(parent_id) + "/childEntities/SubCategory?defaultinst_ct=abcd"
2470
+ )
2070
2471
 
2071
- return prioity_list
2472
+ return self.do_request(
2473
+ url=request_url,
2474
+ method="POST",
2475
+ headers=REQUEST_HEADERS_JSON,
2476
+ cookies=self.cookie(),
2477
+ json_data=create_sub_category_data,
2478
+ timeout=REQUEST_TIMEOUT,
2479
+ failure_message="Request to create sub-category -> '{}' with parent category ID -> {} failed".format(
2480
+ name, parent_id
2481
+ ),
2482
+ )
2072
2483
 
2073
2484
  # end method definition
2074
2485
 
2075
- def verify_category_exists(self, name: str) -> bool:
2076
- """Verify category entity instance already exists.
2486
+ def get_sub_categories(self, parent_id: int) -> dict | None:
2487
+ """Get all sub categeries entity instances.
2077
2488
 
2078
2489
  Args:
2079
- name (str):
2080
- The name of the category.
2490
+ parent_id (int):
2491
+ The parent ID of the sub categories.
2081
2492
 
2082
2493
  Returns:
2083
- bool:
2084
- Returns True if already record exists with same name, else returns False.
2494
+ dict | None:
2495
+ Request response (dictionary) or None if the REST call fails.
2496
+
2497
+ Example:
2498
+ {
2499
+ 'page': {
2500
+ 'skip': 0,
2501
+ 'top': 10,
2502
+ 'count': 1
2503
+ },
2504
+ '_links': {
2505
+ 'self': {...},
2506
+ 'first': {...}
2507
+ },
2508
+ '_embedded': {
2509
+ 'SubCategory': [
2510
+ {
2511
+ '_links': {...},
2512
+ 'Identity': {'Id': '1'},
2513
+ 'Properties': {'Name': 'Business', 'Description': 'Business', 'Status': 1},
2514
+ 'ParentCategory': {
2515
+ '_links': {
2516
+ 'self': {'href': '/OpentextCaseManagement/entities/Category/items/1/childEntities/SubCategory/items/1'}
2517
+ },
2518
+ 'Properties': {
2519
+ 'CasePrefix': 'LOAN',
2520
+ 'Description': 'Short Term Loan',
2521
+ 'Name': 'Short Term Loan',
2522
+ 'Status': 1
2523
+ }
2524
+ }
2525
+ }
2526
+ ]
2527
+ }
2528
+ }
2085
2529
 
2086
2530
  """
2087
2531
 
2088
- categoy_resp_dict = self.get_all_categories()
2089
- names = [item["Properties"]["Name"] for item in categoy_resp_dict["_embedded"]["CategoryList"]]
2090
- if name in names:
2091
- self.logger.info("Category record -> '%s' already exists", name)
2092
- return True
2093
- self.logger.info("Creating category record -> '%s'", name)
2532
+ request_url = self.config()["categoryUrl"] + "/items/" + str(parent_id) + "/childEntities/SubCategory"
2094
2533
 
2095
- return False
2534
+ return self.do_request(
2535
+ url=request_url,
2536
+ method="GET",
2537
+ headers=REQUEST_HEADERS_JSON,
2538
+ cookies=self.cookie(),
2539
+ timeout=REQUEST_TIMEOUT,
2540
+ failure_message="Request to get sub-categories for parent category with ID -> {} failed".format(parent_id),
2541
+ )
2096
2542
 
2097
2543
  # end method definition
2098
2544
 
2099
- def vverify_case_type_exists(self, name: str) -> bool:
2100
- """Verify case type entity instance already exists.
2545
+ def get_sub_category_by_parent_and_name(self, parent_id: int, name: str) -> dict | None:
2546
+ """Get sub category entity instance by its name.
2101
2547
 
2102
2548
  Args:
2549
+ parent_id (int):
2550
+ The ID of the parent category.
2103
2551
  name (str):
2104
- The name of the case type.
2552
+ The name of the sub category.
2105
2553
 
2106
2554
  Returns:
2107
- bool:
2108
- Returns True if already record exists with same name, else returns False.
2555
+ dict | None:
2556
+ Returns the sub-category item or None if the sub-category with this name
2557
+ does not exist in the parent category with the given ID.
2109
2558
 
2110
2559
  """
2111
2560
 
2112
- casetype_resp_dict = self.get_all_case_type()
2113
- names = [item["Properties"]["Name"] for item in casetype_resp_dict["_embedded"]["AllCaseTypes"]]
2114
- if name in names:
2115
- self.logger.info("Case type record -> '%s' already exists", name)
2116
- return True
2117
- self.logger.info("Creating case type record -> '%s'", name)
2561
+ # Get all sub-categories under a given category provided by the parent ID:
2562
+ sub_categories = self.get_sub_categories(parent_id=parent_id)
2118
2563
 
2119
- return False
2564
+ return self.get_result_item(response=sub_categories, entity_type="SubCategory", key="Name", value=name)
2120
2565
 
2121
2566
  # end method definition
2122
2567
 
2123
- def verify_customer_exists(self, name: str) -> bool:
2124
- """Verify cusomer entty instance already exists.
2568
+ def get_sub_category_id(self, parent_id: int, name: str) -> int | None:
2569
+ """Get the sub category entity instance ID.
2125
2570
 
2126
2571
  Args:
2572
+ parent_id (int):
2573
+ ID of the parent category.
2127
2574
  name (str):
2128
- The name of the customer.
2575
+ The name of the sub-category.
2129
2576
 
2130
2577
  Returns:
2131
- bool:
2132
- Returns True if already record exists with same name, else returns False
2578
+ int | None:
2579
+ Returns the sub-category ID if it exists with the given name in a given parent category.
2580
+ Else returns None.
2133
2581
 
2134
2582
  """
2135
2583
 
2136
- customer_resp_dict = self.get_all_customers()
2137
- names = [item["Properties"]["CustomerName"] for item in customer_resp_dict["_embedded"]["CustomerList"]]
2138
- if name in names:
2139
- self.logger.info("Customer -> '%s' already exists", name)
2140
- return True
2141
- self.logger.info("Creating customer -> '%s'", name)
2142
- return False
2584
+ sub_cat = self.get_sub_category_by_parent_and_name(parent_id=parent_id, name=name)
2585
+ if not sub_cat or "Identity" not in sub_cat:
2586
+ return None
2587
+
2588
+ return sub_cat["Identity"].get("Id")
2143
2589
 
2144
2590
  # end method definition
2145
2591
 
2146
- def verify_priority_exists(self, name: str) -> bool:
2147
- """Verify priority entity instance already exists.
2592
+ def create_case(
2593
+ self,
2594
+ subject: str,
2595
+ description: str,
2596
+ loan_amount: str,
2597
+ loan_duration_in_months: str,
2598
+ category_id: str,
2599
+ sub_category_id: str,
2600
+ priority_id: str,
2601
+ case_type_id: str,
2602
+ customer_id: str,
2603
+ ) -> dict | None:
2604
+ """Create a case entity instance.
2605
+
2606
+ The category, priority, case type and customer entities are
2607
+ referred to with their IDs. These entities need to be created
2608
+ beforehand.
2609
+
2610
+ TODO: This is currently hard-coded for loan cases. Need to be more generic.
2148
2611
 
2149
2612
  Args:
2150
- name (str):
2151
- The name of the priority.
2613
+ subject (str):
2614
+ The subject of the case.
2615
+ description (str):
2616
+ The description of the case.
2617
+ loan_amount (str):
2618
+ The loan amount of the case.
2619
+ loan_duration_in_months (str):
2620
+ The loan duration of the case (in number of months).
2621
+ category_id (str):
2622
+ The category ID of the case.
2623
+ sub_category_id (str):
2624
+ The sub-category ID of the case.
2625
+ priority_id (str):
2626
+ The priority ID of the case.
2627
+ case_type_id (str):
2628
+ The case type (service) ID of the case.
2629
+ customer_id (str):
2630
+ The ID of the customer for the case.
2152
2631
 
2153
2632
  Returns:
2154
- bool:
2155
- Returns True if already record exists with same name, else returns False
2633
+ dict | None:
2634
+ Request response (dictionary) or None if the REST call fails
2156
2635
 
2157
2636
  """
2158
2637
 
2159
- authenticate_dict = self.get_all_priorities()
2160
- names = [item["Properties"]["Name"] for item in authenticate_dict["_embedded"]["PriorityList"]]
2161
- if name in names:
2162
- self.logger.info("Priority -> '%s' already exists", name)
2163
- return True
2164
- self.logger.info("Creating priority -> '%s'", name)
2638
+ # Validation of parameters:
2639
+ required_fields = {
2640
+ "subject": subject,
2641
+ "category ID": category_id,
2642
+ "sub-category ID": sub_category_id,
2643
+ "priority ID": priority_id,
2644
+ "case type ID": case_type_id,
2645
+ "customer ID": customer_id,
2646
+ }
2165
2647
 
2166
- return False
2648
+ for name, value in required_fields.items():
2649
+ if not value:
2650
+ self.logger.error("Cannot create a case without a %s!", name)
2651
+ return None
2652
+
2653
+ create_case_data = f"""
2654
+ <SOAP:Envelope xmlns:SOAP=\"http://schemas.xmlsoap.org/soap/envelope/\">
2655
+ <SOAP:Body>
2656
+ <CreateCase xmlns=\"http://schemas/OpentextCaseManagement/Case/operations\">
2657
+ <ns0:Case-create xmlns:ns0=\"http://schemas/OpentextCaseManagement/Case\">
2658
+ <ns0:Subject>{subject}</ns0:Subject>
2659
+ <ns0:Description>{description}</ns0:Description>
2660
+ <ns0:LoanAmount>{loan_amount}</ns0:LoanAmount>
2661
+ <ns0:LoanDurationInMonths>{loan_duration_in_months}</ns0:LoanDurationInMonths>
2662
+ <ns0:CaseType>
2663
+ <ns1:CaseType-id xmlns:ns1=\"http://schemas/OpentextCaseManagement/CaseType\">
2664
+ <ns1:Id>{case_type_id}</ns1:Id>
2665
+ </ns1:CaseType-id>
2666
+ </ns0:CaseType>
2667
+ <ns0:Category>
2668
+ <ns2:Category-id xmlns:ns2=\"http://schemas/OpentextCaseManagement/Category\">
2669
+ <ns2:Id>{category_id}</ns2:Id>
2670
+ </ns2:Category-id>
2671
+ </ns0:Category>
2672
+ <ns0:SubCategory>
2673
+ <ns5:SubCategory-id xmlns:ns5=\"http://schemas/OpentextCaseManagement/Category.SubCategory\">
2674
+ <ns5:Id>{category_id}</ns5:Id>
2675
+ <ns5:Id1>{sub_category_id}</ns5:Id1>
2676
+ </ns5:SubCategory-id>
2677
+ </ns0:SubCategory>
2678
+ <ns0:Priority>
2679
+ <ns3:Priority-id xmlns:ns3=\"http://schemas/OpentextCaseManagement/Priority\">
2680
+ <ns3:Id>{priority_id}</ns3:Id>
2681
+ </ns3:Priority-id>
2682
+ </ns0:Priority>
2683
+ <ns0:ToCustomer>
2684
+ <ns9:Customer-id xmlns:ns9=\"http://schemas/OpentextCaseManagement/Customer\">
2685
+ <ns9:Id>{customer_id}</ns9:Id>
2686
+ </ns9:Customer-id>
2687
+ </ns0:ToCustomer>
2688
+ </ns0:Case-create>
2689
+ </CreateCase>
2690
+ </SOAP:Body>
2691
+ </SOAP:Envelope>
2692
+ """
2693
+
2694
+ request_url = self.gateway_url()
2695
+
2696
+ self.logger.debug(
2697
+ "Create case with subject -> '%s'; calling -> '%s'",
2698
+ subject,
2699
+ request_url,
2700
+ )
2701
+
2702
+ retries = 0
2703
+ while True:
2704
+ try:
2705
+ response = requests.post(
2706
+ url=request_url,
2707
+ data=create_case_data,
2708
+ headers=REQUEST_HEADERS_XML,
2709
+ cookies=self.cookie(),
2710
+ timeout=REQUEST_TIMEOUT,
2711
+ )
2712
+ except requests.RequestException as req_exception:
2713
+ self.logger.error(
2714
+ "Request to create case with subject -> '%s' failed with error -> %s",
2715
+ subject,
2716
+ str(req_exception),
2717
+ )
2718
+ return None
2719
+
2720
+ if response.ok:
2721
+ return self.parse_xml(response.text)
2722
+ elif response.status_code == 401 and retries == 0:
2723
+ self.logger.warning("Session has expired - try to re-authenticate...")
2724
+ self.authenticate(revalidate=True)
2725
+ retries += 1
2726
+ else:
2727
+ self.logger.error(
2728
+ "Failed to create case with subject -> '%s' for customer with ID -> '%s' with error -> '%s' when calling -> %s!",
2729
+ subject,
2730
+ customer_id,
2731
+ self.get_soap_element(soap_response=response.text, soap_tag="faultstring"),
2732
+ self.get_soap_element(soap_response=response.text, soap_tag="faultactor"),
2733
+ )
2734
+ self.logger.debug("SOAP message -> %s", response.text)
2735
+ return None
2167
2736
 
2168
2737
  # end method definition
2169
2738
 
2170
- def verify_sub_category_exists(
2171
- self,
2172
- name: str,
2173
- index: int,
2174
- category_resp_dict: list,
2175
- ) -> bool:
2176
- """Verify sub category entity instance already exists.
2739
+ def get_cases(self) -> dict | None:
2740
+ """Get all case entity instances.
2177
2741
 
2178
2742
  Args:
2179
- name (str):
2180
- The name of the sub category.
2181
- index (int):
2182
- The index of the sub category.
2183
- category_resp_dict (list):
2184
- TODO: add description
2743
+ None
2185
2744
 
2186
2745
  Returns:
2187
- bool:
2188
- Returns true if record already exists with same name, else returns false.
2746
+ dict:
2747
+ Request response (dictionary) or None if the REST call fails.
2189
2748
 
2190
2749
  """
2191
2750
 
2192
- subcategoy_resp_dict = self.get_all_sub_categeries(category_resp_dict[index])
2193
- names = [item["Properties"]["Name"] for item in subcategoy_resp_dict["_embedded"]["SubCategory"]]
2194
- stl = 0
2195
- if name in names:
2196
- self.logger.info("Sub category -> '%s' already exists", name)
2197
- for item in subcategoy_resp_dict["_embedded"]["SubCategory"]:
2198
- stl = item["Identity"]["Id"]
2199
- self.logger.info("Sub category created with ID -> %s", stl)
2200
- return True
2201
- self.logger.info("Creating sub category -> '%s'", name)
2751
+ request_url = self.get_cases_list_url()
2202
2752
 
2203
- return False
2753
+ return self.do_request(
2754
+ url=request_url,
2755
+ method="GET",
2756
+ headers=REQUEST_HEADERS_JSON,
2757
+ cookies=self.cookie(),
2758
+ timeout=REQUEST_TIMEOUT,
2759
+ failure_message="Request to get cases failed",
2760
+ )
2204
2761
 
2205
2762
  # end method definition
2206
2763
 
2207
- def return_sub_category_exists_id(
2208
- self,
2209
- name: str,
2210
- index: int,
2211
- category_resp_dict: list,
2212
- ) -> int:
2213
- """Verify sub category entity instance id already exists.
2764
+ def get_case_by_name(self, name: str) -> dict | None:
2765
+ """Get case instance by its name.
2214
2766
 
2215
2767
  Args:
2216
2768
  name (str):
2217
- The name of the sub-category.
2218
- index (int):
2219
- TODO: add description
2220
- category_resp_dict (list):
2221
- TODO: add description!
2769
+ The name of the case.
2222
2770
 
2223
2771
  Returns:
2224
- bool:
2225
- Returns true if record already exists with same name, else returns false.
2772
+ dict | None:
2773
+ Returns the category item or None if a category with the given name does not exist.
2226
2774
 
2227
2775
  """
2228
2776
 
2229
- subcategoy_resp_dict = self.get_all_sub_categeries(category_resp_dict[index])
2230
- names = [item["Properties"]["Name"] for item in subcategoy_resp_dict["_embedded"]["SubCategory"]]
2231
- stl = 0
2232
- if name in names:
2233
- self.logger.info("Sub category record -> '%s' already exists", name)
2234
- for item in subcategoy_resp_dict["_embedded"]["SubCategory"]:
2235
- stl = item["Identity"]["Id"]
2236
- self.logger.info("Sub category created with ID -> %s", stl)
2237
- return stl
2777
+ categories = self.get_cases()
2238
2778
 
2239
- return None
2779
+ return self.get_result_item(response=categories, entity_type="AllCasesList", key="Name", value=name)
2240
2780
 
2241
2781
  # end method definition
2242
2782
 
2243
- def create_users_from_config_file(self, otawpsection: str, _otds: OTDS) -> None:
2783
+ def create_users_from_config_file(self, otawpsection: str, otds_object: OTDS) -> None:
2244
2784
  """Read user information from customizer file and call create user method.
2245
2785
 
2246
2786
  Args:
2247
2787
  otawpsection (str):
2248
- YAML block related to appworks
2249
- _otds:
2788
+ Payload section for AppWorks.
2789
+ otds_object (OTDS):
2250
2790
  The OTDS object.
2251
2791
 
2252
2792
  Returns:
@@ -2259,7 +2799,7 @@ class OTAWP:
2259
2799
  users = otds.get("users", [])
2260
2800
  if users is not None:
2261
2801
  for user in users:
2262
- _otds.add_user(
2802
+ otds_object.add_user(
2263
2803
  user.get("partition"),
2264
2804
  user.get("name"),
2265
2805
  user.get("description"),
@@ -2270,32 +2810,32 @@ class OTAWP:
2270
2810
  roles = otds.get("roles", [])
2271
2811
  if roles is not None:
2272
2812
  for role in roles:
2273
- _otds.add_user_to_group(
2813
+ otds_object.add_user_to_group(
2274
2814
  user.get("name") + "@" + user.get("partition"),
2275
2815
  role.get("name"),
2276
2816
  )
2277
2817
  else:
2278
- self.logger.error(
2279
- "Verifying Users section: roles section not presented in yaml for otds users",
2818
+ self.logger.warning(
2819
+ "Roles section not in payload for AppWorks users.",
2280
2820
  )
2281
2821
  else:
2282
2822
  self.logger.error(
2283
- "Verifying Users section: user section not presented in yaml",
2823
+ "User section not in payload for AppWorks users.",
2284
2824
  )
2285
2825
  else:
2286
2826
  self.logger.error(
2287
- "Verifying Users section: otds section not presented in yaml",
2827
+ "OTDS section not in payload for AppWorks users.",
2288
2828
  )
2289
2829
 
2290
2830
  # end method definition
2291
2831
 
2292
- def create_roles_from_config_file(self, otawpsection: str, _otds: OTDS) -> None:
2832
+ def create_roles_from_config_file(self, otawpsection: str, otds_object: OTDS) -> None:
2293
2833
  """Read grop information from customizer file and call create grop method.
2294
2834
 
2295
2835
  Args:
2296
2836
  otawpsection (str):
2297
- YAML block related to appworks.
2298
- _otds (object):
2837
+ Payload section for AppWorks.
2838
+ otds_object (OTDS):
2299
2839
  The OTDS object used to access the OTDS REST API.
2300
2840
 
2301
2841
  Returns:
@@ -2309,56 +2849,23 @@ class OTAWP:
2309
2849
  if roles is not None:
2310
2850
  for role in roles:
2311
2851
  # Add new group if it does not yet exist:
2312
- if not _otds.get_group(group=role.get("name"), show_error=False):
2313
- _otds.add_group(
2852
+ if not otds_object.get_group(group=role.get("name"), show_error=False):
2853
+ otds_object.add_group(
2314
2854
  role.get("partition"),
2315
2855
  role.get("name"),
2316
2856
  role.get("description"),
2317
2857
  )
2318
2858
  else:
2319
2859
  self.logger.error(
2320
- "Verifying roles section: roles section not presented in yaml",
2860
+ "Roles section not in payload for AppWorks roles/groups.",
2321
2861
  )
2322
2862
  else:
2323
2863
  self.logger.error(
2324
- "Verifying roles section: otds section not presented in yaml",
2864
+ "OTDS section not in payload for AppWorks roles/groups.",
2325
2865
  )
2326
2866
 
2327
2867
  # end method definition
2328
2868
 
2329
- def create_loanruntime_from_config_file(self, platform: str) -> None:
2330
- """Verify flag and call loan_management_runtime().
2331
-
2332
- Args:
2333
- platform (str):
2334
- YAML block related to platform.
2335
-
2336
- Returns:
2337
- None
2338
-
2339
- """
2340
-
2341
- runtime = platform.get("runtime", {})
2342
- if runtime is not None:
2343
- app_names = runtime.get("appNames", [])
2344
- if app_names is not None:
2345
- for app_name in app_names:
2346
- if app_name == "loanManagement":
2347
- self.loan_management_runtime()
2348
- else:
2349
- self.logger.error(
2350
- "Verifying runtime section: loanManagement not exits in yaml entry",
2351
- )
2352
- else:
2353
- self.logger.error(
2354
- "Verifying runtime section: App name section is empty in yaml",
2355
- )
2356
- else:
2357
- self.logger.error(
2358
- "Verifying runtime section: Runtime section is empty in yaml",
2359
- )
2360
-
2361
- # end method definition
2362
2869
  def create_cws_config(
2363
2870
  self,
2364
2871
  partition: str,
@@ -2368,18 +2875,21 @@ class OTAWP:
2368
2875
  """Create a workspace configuration in CWS.
2369
2876
 
2370
2877
  Args:
2371
- partition (str): The partition name for the workspace.
2372
- resource_name (str): The resource name.
2373
- otcs_url (str): The OTCS endpoint URL.
2878
+ partition (str):
2879
+ The partition name for the workspace.
2880
+ resource_name (str):
2881
+ The resource name.
2882
+ otcs_url (str):
2883
+ The OTCS endpoint URL.
2374
2884
 
2375
2885
  Returns:
2376
- dict | None: Response dictionary if successful, or None if the request fails.
2886
+ dict | None:
2887
+ Response dictionary if successful, or None if the request fails.
2377
2888
 
2378
2889
  """
2379
- self.logger.info("Creating CWS config for partition '%s' and resource '%s'...", partition, resource_name)
2380
2890
 
2381
2891
  # Construct the SOAP request body
2382
- cws_config_req_xml = f"""
2892
+ cws_config_data = f"""
2383
2893
  <SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
2384
2894
  <SOAP:Header>
2385
2895
  <header xmlns="http://schemas.cordys.com/General/1.0/">
@@ -2419,79 +2929,108 @@ class OTAWP:
2419
2929
  </SOAP:Envelope>
2420
2930
  """
2421
2931
 
2422
- retries = 0
2423
- max_retries = 20 # Maximum retry limit
2424
2932
  error_messages = [
2425
2933
  "Collaborative Workspace Service Container is not able to handle the SOAP request",
2426
2934
  "Service Group Lookup failure",
2427
2935
  ]
2428
2936
 
2429
- while retries < max_retries:
2937
+ request_url = self.gateway_url()
2938
+
2939
+ self.logger.debug(
2940
+ "Create CWS configuration with partition -> '%s', user -> '%s', and OTCS URL -> '%s'; calling -> '%s'",
2941
+ partition,
2942
+ resource_name,
2943
+ otcs_url,
2944
+ request_url,
2945
+ )
2946
+
2947
+ retries = 0
2948
+
2949
+ while retries < REQUEST_MAX_RETRIES:
2430
2950
  try:
2431
2951
  response = requests.post(
2432
- url=self.gateway_url(),
2433
- data=cws_config_req_xml,
2434
- headers=REQUEST_HEADERS,
2952
+ url=request_url,
2953
+ data=cws_config_data,
2954
+ headers=REQUEST_HEADERS_XML,
2435
2955
  cookies=self.cookie(),
2436
2956
  timeout=None,
2437
2957
  )
2438
2958
 
2439
- # Handle successful response
2440
- if response.ok:
2441
- if any(error_message in response.text for error_message in error_messages):
2442
- self.logger.warning(
2443
- "Service error detected, retrying in 60 seconds... (Retry %d of %d)",
2444
- retries + 1,
2445
- max_retries,
2446
- )
2447
- time.sleep(60)
2448
- retries += 1
2449
- else:
2450
- self.logger.info("CWS config created successfully.")
2451
- return response.text
2959
+ except requests.RequestException as req_exception:
2960
+ self.logger.error(
2961
+ "Request to create CWS config for partition -> '%s' failed with error -> %s. Retry in %d seconds...",
2962
+ partition,
2963
+ str(req_exception),
2964
+ REQUEST_RETRY_DELAY,
2965
+ )
2966
+ retries += 1
2967
+ time.sleep(REQUEST_RETRY_DELAY)
2968
+ self.logger.info("Retrying... Attempt %d/%d", retries, REQUEST_MAX_RETRIES)
2969
+ continue
2452
2970
 
2453
- # Handle session expiration
2454
- if response.status_code == 401 and retries == 0:
2455
- self.logger.warning("Session expired. Re-authenticating...")
2456
- self.authenticate(revalidate=True)
2971
+ # Handle successful response
2972
+ if response.ok:
2973
+ if any(error_message in response.text for error_message in error_messages):
2974
+ self.logger.warning(
2975
+ "Service error detected, retrying in %d seconds... (Retry %d of %d)",
2976
+ REQUEST_RETRY_DELAY,
2977
+ retries + 1,
2978
+ REQUEST_MAX_RETRIES,
2979
+ )
2980
+ time.sleep(REQUEST_RETRY_DELAY)
2457
2981
  retries += 1
2458
- continue
2459
-
2460
- # Handle case where object has been changed by another user
2461
- if "Object has been changed by other user" in response.text:
2462
- self.logger.info("CWS config already exists")
2463
- return response.text
2982
+ else:
2983
+ self.logger.info("Successfully created CWS configuration.")
2984
+ return self.parse_xml(response.text)
2464
2985
 
2465
- # Log errors for failed requests
2466
- self.logger.error("Failed to create CWS config: %s", response.text)
2467
- time.sleep(60)
2986
+ # Handle session expiration
2987
+ if response.status_code == 401 and retries == 0:
2988
+ self.logger.warning("Session expired. Re-authenticating...")
2989
+ self.authenticate(revalidate=True)
2468
2990
  retries += 1
2991
+ continue
2469
2992
 
2470
- except requests.RequestException:
2471
- self.logger.error("Request failed during CWS config creation")
2472
- retries += 1
2993
+ # Handle case where object has been changed by another user:
2994
+ if "Object has been changed by other user" in response.text:
2995
+ self.logger.info("CWS config already exists")
2996
+ self.logger.debug("SOAP message -> %s", response.text)
2997
+ return self.parse_xml(response.text)
2998
+
2999
+ # Log errors for failed requests
3000
+ self.logger.error("Failed to create CWS config; error -> %s", response.text)
3001
+ time.sleep(REQUEST_RETRY_DELAY)
3002
+ retries += 1
3003
+ # end while retries < REQUEST_MAX_RETRIES:
2473
3004
 
2474
3005
  # Log when retries are exhausted
2475
3006
  self.logger.error("Retry limit exceeded. CWS config creation failed.")
2476
3007
  return None
2477
3008
 
2478
3009
  # end method definition
3010
+
2479
3011
  def verify_user_having_role(self, organization: str, user_name: str, role_name: str) -> bool:
2480
- """Verify if the user has the specified role.
3012
+ """Verify that the user has the specified role.
2481
3013
 
2482
3014
  Args:
2483
- organization(str): organization name
2484
- user_name (str): The username to verify.
2485
- role_name (str): The role to check for the user.
3015
+ organization (str):
3016
+ The organization name.
3017
+ user_name (str):
3018
+ The username to verify.
3019
+ role_name (str):
3020
+ The role to check for the user.
2486
3021
 
2487
3022
  Returns:
2488
- bool: True if the user has the role, False if not, or None if request fails.
3023
+ bool:
3024
+ True if the user has the role, False if not, or None if request fails.
2489
3025
 
2490
3026
  """
2491
- self.logger.info("Verifying user '%s' has role '%s' started.", user_name, role_name)
3027
+
3028
+ self.logger.info(
3029
+ "Verify user -> '%s' has role -> '%s' in organization -> '%s'...", user_name, role_name, organization
3030
+ )
2492
3031
 
2493
3032
  # Construct the SOAP request body
2494
- cws_config_req_xml = f"""
3033
+ user_role_data = f"""
2495
3034
  <SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
2496
3035
  <SOAP:Header xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
2497
3036
  <header xmlns="http://schemas.cordys.com/General/1.0/">
@@ -2516,69 +3055,89 @@ class OTAWP:
2516
3055
  """
2517
3056
 
2518
3057
  retries = 0
2519
- max_retries = 6 # Maximum retry limit
2520
- while retries < max_retries:
3058
+
3059
+ while retries < REQUEST_MAX_RETRIES:
2521
3060
  try:
2522
3061
  response = requests.post(
2523
3062
  url=self.gateway_url(),
2524
- data=cws_config_req_xml,
2525
- headers=REQUEST_HEADERS,
3063
+ data=user_role_data,
3064
+ headers=REQUEST_HEADERS_XML,
2526
3065
  cookies=self.cookie(),
2527
3066
  timeout=None,
2528
3067
  )
2529
3068
 
2530
- # Handle successful response
2531
- if response.ok:
2532
- if (
2533
- role_name in response.text
2534
- ): # Corrected syntax for checking if 'Developer' is in the response text
2535
- self.logger.info("Verifyied user '%s' already has the role '%s'.", user_name, role_name)
2536
- return True # Assuming the user has the role if the response contains 'Developer'
2537
- else:
2538
- self.logger.info("Verifyied User '%s' not having role '%s'.", user_name, role_name)
2539
- return False
2540
- # Handle session expiration
2541
- if response.status_code == 401 and retries == 0:
2542
- self.logger.warning("Session expired. Re-authenticating...")
2543
- self.authenticate(revalidate=True)
2544
- retries += 1
2545
- continue
2546
-
2547
- # Log errors for failed requests
2548
- self.logger.error("Failed to verify user role: %s", response.text)
2549
- time.sleep(60)
3069
+ except requests.RequestException:
3070
+ self.logger.error(
3071
+ "Request failed during verification of user -> '%s' for role -> '%s'. Retry in %d seconds...",
3072
+ user_name,
3073
+ role_name,
3074
+ REQUEST_RETRY_DELAY,
3075
+ )
2550
3076
  retries += 1
3077
+ time.sleep(REQUEST_RETRY_DELAY)
3078
+ self.logger.info("Retrying... Attempt %d/%d", retries, REQUEST_MAX_RETRIES)
3079
+ continue
2551
3080
 
2552
- except requests.RequestException:
2553
- self.logger.error("Request failed during user role verification")
3081
+ # Handle successful response
3082
+ if response.ok:
3083
+ if role_name in response.text: # Corrected syntax for checking if 'Developer' is in the response text
3084
+ self.logger.info("Verified user -> '%s' already has the role -> '%s'.", user_name, role_name)
3085
+ return True # Assuming the user has the role if the response contains 'Developer'
3086
+ else:
3087
+ self.logger.info("Verified user -> '%s' does not yet have role -> '%s'.", user_name, role_name)
3088
+ return False
3089
+
3090
+ # Handle session expiration
3091
+ if response.status_code == 401 and retries == 0:
3092
+ self.logger.warning("Session expired. Re-authenticating...")
3093
+ self.authenticate(revalidate=True)
2554
3094
  retries += 1
3095
+ continue
3096
+
3097
+ # Log errors for failed requests
3098
+ self.logger.error(
3099
+ "Failed to verify that user -> '%s' has role -> '%s'; error -> %s",
3100
+ user_name,
3101
+ role_name,
3102
+ response.text,
3103
+ )
3104
+ time.sleep(REQUEST_RETRY_DELAY)
3105
+ retries += 1
3106
+ self.logger.info("Retrying... Attempt %d/%d", retries, REQUEST_MAX_RETRIES)
2555
3107
 
2556
3108
  # Log when retries are exhausted
2557
3109
  self.logger.error("Retry limit exceeded. User role verification failed.")
3110
+
2558
3111
  return False # Return False if the retries limit is exceeded
2559
3112
 
2560
3113
  # end method definition
2561
3114
 
2562
- def assign_developer_role_to_user(self, organization: str, user_name: str, role_name: str) -> bool:
2563
- """Assign the 'Developer' role to the user and verify the role assignment.
3115
+ def assign_role_to_user(self, organization: str, user_name: str, role_name: str) -> bool:
3116
+ """Assign a role to a user and verify the role assignment.
2564
3117
 
2565
3118
  Args:
2566
- organization (str): The organization name.
2567
- user_name (str): The username to verify.
2568
- role_name (str): The role to be assigned.
3119
+ organization (str):
3120
+ The organization name.
3121
+ user_name (str):
3122
+ The username to get the role.
3123
+ role_name (str):
3124
+ The role to be assigned.
2569
3125
 
2570
3126
  Returns:
2571
- bool: True if the user has the 'Developer' role, False otherwise.
3127
+ bool:
3128
+ True if the user received the role, False otherwise.
2572
3129
 
2573
3130
  """
2574
- self.logger.info("Assigning 'Developer' role to user '%s' in organization '%s'...", user_name, organization)
3131
+ self.logger.info(
3132
+ "Assign role -> '%s' to user -> '%s' in organization -> '%s'...", role_name, user_name, organization
3133
+ )
2575
3134
 
2576
3135
  # Check if user already has the role before making the request
2577
3136
  if self.verify_user_having_role(organization, user_name, role_name):
2578
3137
  return True
2579
3138
 
2580
3139
  # Construct the SOAP request body
2581
- cws_config_req_xml = f"""\
3140
+ developer_role_data = f"""\
2582
3141
  <SOAP:Envelope xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
2583
3142
  <SOAP:Header xmlns:SOAP="http://schemas.xmlsoap.org/soap/envelope/">
2584
3143
  <header xmlns="http://schemas.cordys.com/General/1.0/">
@@ -2620,7 +3179,7 @@ class OTAWP:
2620
3179
  <string>cn=everyoneIn{organization},cn=organizational roles,o={organization},cn=cordys,cn=defaultInst,o=opentext.net</string>
2621
3180
  <string>cn=Administrator,cn=Cordys@Work,cn=cordys,cn=defaultInst,o=opentext.net</string>
2622
3181
  <string>cn=OTDS Push Service,cn=OpenText OTDS Platform Push Connector,cn=cordys,cn=defaultInst,o=opentext.net</string>
2623
- <string>cn=Developer,cn=Cordys@Work,cn=cordys,cn=defaultInst,o=opentext.net</string>
3182
+ <string>cn={role_name},cn=Cordys@Work,cn=cordys,cn=defaultInst,o=opentext.net</string>
2624
3183
  </role>
2625
3184
  <description>
2626
3185
  <string>{user_name}</string>
@@ -2643,22 +3202,31 @@ class OTAWP:
2643
3202
  </SOAP:Body>
2644
3203
  </SOAP:Envelope>
2645
3204
  """
3205
+
3206
+ request_url = self.gateway_url()
3207
+
3208
+ self.logger.debug(
3209
+ "Assign role -> '%s' to user -> '%s' in organization -> '%s'; calling -> '%s'",
3210
+ role_name,
3211
+ user_name,
3212
+ organization,
3213
+ request_url,
3214
+ )
3215
+
2646
3216
  retries = 0
2647
- max_retries = 6
2648
- retry_delay = 30 # Time in seconds
2649
3217
 
2650
- while retries < max_retries:
3218
+ while retries < REQUEST_MAX_RETRIES:
2651
3219
  try:
2652
3220
  response = requests.post(
2653
- url=self.gateway_url(),
2654
- data=cws_config_req_xml,
2655
- headers=REQUEST_HEADERS,
3221
+ url=request_url,
3222
+ data=developer_role_data,
3223
+ headers=REQUEST_HEADERS_XML,
2656
3224
  cookies=self.cookie(),
2657
- timeout=30,
3225
+ timeout=REQUEST_TIMEOUT,
2658
3226
  )
2659
3227
 
2660
3228
  if response.ok and role_name in response.text:
2661
- self.logger.info("User '%s' successfully assigned the '%s' role.", user_name, role_name)
3229
+ self.logger.info("Successfully assigned the role -> '%s' to user -> '%s'.", role_name, user_name)
2662
3230
  return True
2663
3231
 
2664
3232
  # Handle session expiration
@@ -2670,18 +3238,19 @@ class OTAWP:
2670
3238
 
2671
3239
  # Log failure response
2672
3240
  self.logger.error(
2673
- "Failed to assign role to user '%s': HTTP %s - %s",
3241
+ "Failed to assign role -> '%s' to user -> '%s'; error -> %s (%s)",
3242
+ role_name,
2674
3243
  user_name,
2675
3244
  response.status_code,
2676
3245
  response.text,
2677
3246
  )
2678
3247
 
2679
- except requests.RequestException:
2680
- self.logger.error("Request failed:")
3248
+ except requests.RequestException as req_exception:
3249
+ self.logger.error("Request failed; error -> %s", str(req_exception))
2681
3250
 
2682
3251
  retries += 1
2683
- self.logger.info("Retrying... Attempt %d/%d", retries, max_retries)
2684
- time.sleep(retry_delay)
3252
+ self.logger.info("Retrying... Attempt %d/%d", retries, REQUEST_MAX_RETRIES)
3253
+ time.sleep(REQUEST_RETRY_DELAY)
2685
3254
 
2686
3255
  self.logger.error("Retry limit exceeded. Role assignment failed for user '%s'.", user_name)
2687
3256
  return False