Coverage for suhteita / suhteita.py: 25.52%
225 statements
« prev ^ index » next coverage.py v7.13.2, created at 2026-02-03 22:42:42 +00:00
« prev ^ index » next coverage.py v7.13.2, created at 2026-02-03 22:42:42 +00:00
1"""Load the JIRA instance."""
3import argparse
4import datetime as dti
5import json
6import logging
7import secrets
8from typing import no_type_check
10import suhteita.ticket_system_actions as actions
11from suhteita import (
12 APP_ALIAS,
13 APP_ENV,
14 BASE_URL,
15 COMMA,
16 IDENTITY,
17 IS_CLOUD,
18 NODE_INDICATOR,
19 PROJECT,
20 STORE,
21 TOKEN,
22 TS_FORMAT_PAYLOADS,
23 USER,
24 VERSION,
25 extract_fields,
26 log,
27 two_sentences,
28)
29from suhteita.store import Store
31Context = dict[str, str | dti.datetime]
34@no_type_check
35def setup_twenty_seven(options: argparse.Namespace) -> object:
36 """Set up the scenario adn return the parameters as members of an object."""
38 class Setup:
39 pass
41 setup = Setup()
43 setup.user = options.user if options.user else USER
44 setup.token = options.token if options.token else TOKEN
45 setup.target_url = options.target_url if options.target_url else BASE_URL
46 setup.is_cloud = options.is_cloud if options.is_cloud else IS_CLOUD
47 setup.target_project = options.target_project if options.target_project else PROJECT
48 setup.scenario = options.scenario if options.scenario else 'unknown'
49 setup.identity = options.identity if options.identity else IDENTITY
50 setup.storage_path = options.out_path if options.out_path else STORE
52 log.info('=' * 84)
53 log.info(f'Generator {APP_ALIAS} version {VERSION}')
54 log.info('# Prelude of a 27-steps scenario test execution')
56 setup.c_rand, setup.d_rand = two_sentences()
57 log.info(f'- Setup <01> Random sentence of original ({setup.c_rand})')
58 log.info(f'- Setup <02> Random sentence of duplicate ({setup.d_rand})')
60 setup.random_component = secrets.token_urlsafe()
61 log.info(f'- Setup <03> Random component name ({setup.random_component})')
63 setup.todo, setup.in_progress, setup.done = options.workflow_csv.split(COMMA) # Default: 'to do,in progress,done'
64 log.info(
65 f'- Setup <04> The test workflow assumes the (case insensitive) states'
66 f' ({setup.todo}, {setup.in_progress}, {setup.done})'
67 )
69 setup.ts = dti.datetime.now(tz=dti.timezone.utc).strftime(TS_FORMAT_PAYLOADS)
70 log.info(f'- Setup <05> Timestamp marker in summaries will be ({setup.ts})')
72 setup.desc_core = '... and short description we dictate.'
73 log.info(f'- Setup <06> Common description part - of twin issues / pair - will be ({setup.desc_core})')
75 setup.amendment = 'No, no, no. They duplicated me, help!'
76 log.info(f'- Setup <07> Amendment for original description will be ({setup.amendment})')
78 setup.fake_comment = 'I am the original, surely!'
79 log.info(f'- Setup <08> Fake comment for duplicate will be ({setup.fake_comment})')
81 setup.duplicate_labels = ['du', 'pli', 'ca', 'te']
82 log.info(f'- Setup <09> Labels for duplicate will be ({setup.duplicate_labels})')
84 setup.original_labels = ['for', 'real', 'highlander']
85 log.info(f'- Setup <10> Labels for original will be ({setup.original_labels})')
87 setup.hours_value = 42
88 log.info(f'- Setup <11> Hours value for original estimate will be ({setup.hours_value})')
90 setup.purge_me = 'SUHTEITA_PURGE_ME_ORIGINAL'
91 log.info(f'- Setup <12> Purge indicator comment will be ({setup.purge_me})')
93 setup.node_indicator = NODE_INDICATOR
94 log.info(f'- Setup <13> Node indicator ({setup.node_indicator})')
96 log.info(
97 f'- Setup <14> Connect will be to upstream ({"cloud" if setup.is_cloud else "on-site"})'
98 f' service ({setup.target_url}) per login ({setup.user})'
99 )
100 log.info('-' * 84)
102 return setup
105def main(options: argparse.Namespace) -> int:
106 """Drive the transactions."""
108 if options.version: 108 ↛ 109line 108 didn't jump to line 109 because the condition on line 108 was never true
109 print(VERSION)
110 return 0
112 if not options.token and not TOKEN: 112 ↛ 116line 112 didn't jump to line 116 because the condition on line 112 was always true
113 log.error(f'No secret token or pass phrase given, please set {APP_ENV}_TOKEN accordingly')
114 return 2
116 if options.trace:
117 logging.getLogger().setLevel(logging.DEBUG)
118 elif options.debug:
119 log.setLevel(logging.DEBUG)
120 cfg = setup_twenty_seven(options=options)
122 # Belt and braces:
123 has_failures = False
125 # Here we start the timer for the session:
126 start_time = dti.datetime.now(tz=dti.timezone.utc)
127 start_ts = start_time.strftime(TS_FORMAT_PAYLOADS)
128 context: Context = {
129 'target': cfg.target_url,
130 'mode': f'{"cloud" if cfg.is_cloud else "on-site"}',
131 'project': cfg.target_project,
132 'scenario': cfg.scenario,
133 'identity': cfg.identity,
134 'start_time': start_time,
135 }
136 store = Store(context=context, setup=cfg, folder_path=cfg.storage_path)
137 log.info(f'# Starting 27-steps scenario test execution at at ({start_ts})')
138 log.info('- Step <01> LOGIN')
139 clk, service = actions.login(cfg.target_url, cfg.user, password=cfg.token, is_cloud=cfg.is_cloud)
140 log.info(f'^ Connected to upstream service; CLK={clk}')
141 store.add('LOGIN', True, clk)
143 log.info('- Step <02> SERVER_INFO')
144 clk, server_info = actions.get_server_info(service)
145 log.info(f'^ Retrieved upstream server info cf. [SRV]; CLK={clk}')
146 store.add('SERVER_INFO', True, clk, str(server_info))
148 log.info('- Step <03> PROJECTS')
149 clk, projects = actions.get_all_projects(service)
150 log.info(f'^ Retrieved {len(projects)} unarchived projects; CLK={clk}')
151 store.add('PROJECTS', True, clk, f'count({len(projects)})')
153 proj_env_ok = False
154 if cfg.target_project:
155 proj_env_ok = any((cfg.target_project == project['key'] for project in projects))
157 if not proj_env_ok:
158 log.error('Belt and braces - verify project selection:')
159 log.info(json.dumps(sorted([project['key'] for project in projects]), indent=2))
160 return 1
162 first_proj_key = cfg.target_project if proj_env_ok else projects[0]['key']
163 log.info(
164 f'Verified target project from request ({cfg.target_project}) to be'
165 f' {"" if proj_env_ok else "not "}present and set target project to ({first_proj_key})'
166 )
168 log.info('- Step <04> CREATE_ISSUE')
169 clk, c_key = actions.create_issue(
170 service, first_proj_key, cfg.ts, description=f'{cfg.c_rand}\n{cfg.desc_core}\nCAUSALITY={cfg.node_indicator}'
171 )
172 log.info(f'^ Created original ({c_key}); CLK={clk}')
173 store.add('CREATE_ISSUE', True, clk, 'original')
175 log.info('- Step <05> ISSUE_EXISTS')
176 clk, c_e = actions.issue_exists(service, c_key)
177 log.info(f'^ Existence of original ({c_key}) verified with result ({c_e}); CLK={clk}')
178 store.add('ISSUE_EXISTS', bool(c_e), clk, 'original')
180 log.info('- Step <06> CREATE_ISSUE')
181 clk, d_key = actions.create_issue(
182 service, first_proj_key, cfg.ts, description=f'{cfg.d_rand}\n{cfg.desc_core}\nCAUSALITY={cfg.node_indicator}'
183 )
184 log.info(f'^ Created duplicate ({d_key}); CLK={clk}')
185 store.add('CREATE_ISSUE', True, clk, 'duplicate')
187 log.info('- Step <07> ISSUE_EXISTS')
188 clk, d_e = actions.issue_exists(service, d_key)
189 log.info(f'^ Existence of duplicate ({d_key}) verified with result ({d_e}); CLK={clk}')
190 store.add('ISSUE_EXISTS', bool(d_e), clk, 'duplicate')
192 query = f'issue = {c_key}'
193 log.info('- Step <08> EXECUTE_JQL')
194 clk, c_q = actions.execute_jql(service=service, query=query)
195 log.info(f'^ Executed JQL({query}); CLK={clk}')
196 store.add('EXECUTE_JQL', True, clk, f'query({query.replace(c_key, "original-key")})')
198 log.info('- Step <09> AMEND_ISSUE_DESCRIPTION')
199 try:
200 clk = actions.amend_issue_description(service, c_key, amendment=cfg.amendment, issue_context=c_q)
201 log.info(f'^ Amended description of original {d_key} with ({cfg.amendment}); CLK={clk}')
202 store.add('AMEND_ISSUE_DESCRIPTION', True, clk, 'original')
203 except IndexError as err:
204 log.error(f'^ Failed amending description of original {d_key} with ({cfg.amendment}); CLK={clk}')
205 log.error(f'^^ Detail: {err}')
206 store.add('AMEND_ISSUE_DESCRIPTION', False, clk, 'original')
208 log.info('- Step <10> ADD_COMMENT')
209 clk, _ = actions.add_comment(service=service, issue_key=d_key, comment=cfg.fake_comment)
210 log.info(f'^ Added comment ({cfg.fake_comment}) to duplicate {d_key}; CLK={clk}')
211 store.add('ADD_COMMENT', True, clk, 'duplicate')
213 log.info('- Step <11> UPDATE_ISSUE_FIELD')
214 clk = actions.update_issue_field(service, d_key, labels=cfg.duplicate_labels)
215 log.info(f'^ Updated duplicate {d_key} issue field of labels to ({cfg.duplicate_labels}); CLK={clk}')
216 store.add('UPDATE_ISSUE_FIELD', True, clk, 'duplicate')
218 log.info('- Step <12> UPDATE_ISSUE_FIELD')
219 clk = actions.update_issue_field(service, c_key, labels=cfg.original_labels)
220 log.info(f'^ Updated original {c_key} issue field of labels to ({cfg.original_labels}); CLK={clk}')
221 store.add('UPDATE_ISSUE_FIELD', True, clk, 'original')
223 log.info('- Step <13> CREATE_DUPLICATES_ISSUE_LINK')
224 clk, _ = actions.create_duplicates_issue_link(service, c_key, d_key)
225 log.info(f'^ Created link on duplicate stating it duplicates the original; CLK={clk}')
226 store.add('CREATE_DUPLICATES_ISSUE_LINK', True, clk, 'dublicate duplicates original')
228 log.info('- Step <14> GET_ISSUE_STATUS')
229 clk, d_iss_state = actions.get_issue_status(service, d_key)
230 d_is_todo = d_iss_state.lower() == cfg.todo
231 log.info(
232 f'^ Retrieved status of the duplicate {d_key} as ({d_iss_state})'
233 f' with result (is_todo == {d_is_todo}); CLK={clk}'
234 )
235 store.add('GET_ISSUE_STATUS', d_is_todo, clk, f'duplicate({d_iss_state})')
237 log.info('- Step <15> SET_ISSUE_STATUS')
238 clk, _ = actions.set_issue_status(service, d_key, cfg.in_progress)
239 log.info(f'^ Transitioned the duplicate {d_key} to ({cfg.in_progress}); CLK={clk}')
240 store.add('SET_ISSUE_STATUS', True, clk, f'duplicate ({cfg.todo})->({cfg.in_progress})')
242 log.info('- Step <16> SET_ISSUE_STATUS')
243 try:
244 clk, _ = actions.set_issue_status(service, d_key, cfg.done)
245 log.info(f'^ Transitioned the duplicate {d_key} to ({cfg.done}); CLK={clk}')
246 store.add('SET_ISSUE_STATUS', True, clk, f'duplicate ({cfg.in_progress})->({cfg.done})')
247 except Exception as err: # noqa
248 log.error(f'^ Failed transitioning the duplicate {d_key} to ({cfg.done}); CLK={clk}')
249 log.error(f'^^ Detail: {err}')
250 store.add('SET_ISSUE_STATUS', False, clk, f'duplicate ({cfg.in_progress})->({cfg.done})')
252 log.info('- Step <17> GET_ISSUE_STATUS')
253 clk, d_iss_state_done = actions.get_issue_status(service, d_key)
254 d_is_done = d_iss_state_done.lower() == cfg.done
255 log.info(
256 f'^ Retrieved status of the duplicate {d_key} as ({d_iss_state_done})'
257 f' with result (d_is_done == {d_is_done}); CLK={clk}'
258 )
259 store.add('GET_ISSUE_STATUS', d_is_done, clk, f'duplicate({d_iss_state_done})')
261 log.info('- Step <18> ADD_COMMENT')
262 clk, response_step_18_add_comment = actions.add_comment(service, d_key, 'Closed as duplicate.')
263 log.info(f'^ Added comment on {d_key} with response extract cf. [RESP-STEP-18]; CLK={clk}')
264 store.add('ADD_COMMENT', True, clk, f'duplicate({response_step_18_add_comment["body"]})')
266 log.info('- Step <19> SET_ORIGINAL_ESTIMATE')
267 clk, ok = actions.set_original_estimate(service, c_key, hours=cfg.hours_value)
268 log.info(
269 f'^ Added ({cfg.hours_value}) hours as original estimate to original {c_key} with result ({ok}); CLK={clk}'
270 )
271 store.add('SET_ORIGINAL_ESTIMATE', ok, clk, 'original')
273 log.info('- Step <20> GET_ISSUE_STATUS')
274 clk, c_iss_state = actions.get_issue_status(service, c_key)
275 c_is_todo = c_iss_state.lower() == cfg.todo
276 log.info(
277 f'^ Retrieved status of the original {c_key} as ({c_iss_state})'
278 f' with result (c_is_todo == {c_is_todo}); CLK={clk}'
279 )
280 store.add('GET_ISSUE_STATUS', c_is_todo, clk, f'original({c_iss_state})')
282 log.info('- Step <21> SET_ISSUE_STATUS')
283 clk, _ = actions.set_issue_status(service, c_key, cfg.in_progress)
284 log.info(f'^ Transitioned the original {c_key} to ({cfg.in_progress}); CLK={clk}')
285 store.add('SET_ISSUE_STATUS', True, clk, f'original ({cfg.todo})->({cfg.in_progress})')
287 log.info('- Step <22> GET_ISSUE_STATUS')
288 clk, c_iss_state_in_progress = actions.get_issue_status(service, c_key)
289 c_is_in_progress = c_iss_state_in_progress.lower() == cfg.in_progress
290 log.info(
291 f'^ Retrieved status of the original {c_key} as ({c_iss_state_in_progress})'
292 f' with result (c_is_in_progress == {c_is_in_progress}); CLK={clk}'
293 )
294 store.add('GET_ISSUE_STATUS', c_is_in_progress, clk, f'original({c_iss_state_in_progress})')
296 log.info('- Step <23> CREATE_COMPONENT')
297 clk, comp_id, a_component, comp_resp = actions.create_component(
298 service=service, project=first_proj_key, name=cfg.random_component, description=cfg.c_rand
299 )
300 log.info(f'^ Created component ({a_component}) with response extract cf. [RESP-STEP-23]; CLK={clk}')
301 store.add('CREATE_COMPONENT', True, clk, f'component({comp_resp["description"]})') # type: ignore
303 log.info('- Step <24> RELATE_ISSUE_TO_COMPONENT')
304 clk, ok = actions.relate_issue_to_component(service, c_key, comp_id, a_component)
305 log.info(
306 f'^ Attempted relation of original {c_key} issue to component ({a_component}) with result ({ok}); CLK={clk}'
307 )
308 store.add('RELATE_ISSUE_TO_COMPONENT', ok, clk, 'original')
309 if not ok:
310 has_failures = True
312 log.info('- Step <25> LOAD_ISSUE')
313 clk, x_iss = actions.load_issue(service, c_key)
314 log.info(f'^ Loaded issue {c_key}; CLK={clk}')
315 log.debug(json.dumps(x_iss, indent=2))
316 store.add('LOAD_ISSUE', True, clk, 'original')
318 log.info('- Step <26> ADD_COMMENT')
319 clk, response_step_26_add_comment = actions.add_comment(service=service, issue_key=c_key, comment=cfg.purge_me)
320 log.info(f'^ Added purge tag comment on original {c_key} with response extract cf. [RESP-STEP-26]; CLK={clk}')
321 store.add('ADD_COMMENT', True, clk, f'original({response_step_26_add_comment["body"]})')
323 log.info('- Step <27> ADD_COMMENT')
324 clk, response_step_27_add_comment = actions.add_comment(service=service, issue_key=d_key, comment=cfg.purge_me)
325 log.info(
326 f'^ Added purge tag comment on duplicate issue {d_key} with response extract cf. [RESP-STEP-27]; CLK={clk}'
327 )
328 store.add('ADD_COMMENT', True, clk, f'duplicate({response_step_27_add_comment["body"]})')
330 # Here we stop the timer for the session:
331 end_time = dti.datetime.now(tz=dti.timezone.utc)
332 end_ts = end_time.strftime(TS_FORMAT_PAYLOADS)
333 log.info(f'# Ended execution of 27-steps scenario test at ({end_ts})')
334 log.info(f'Execution of 27-steps scenario test took {(end_time - start_time)} h:mm:ss.uuuuuu')
335 log.info('-' * 84)
337 log.info('# References:')
338 log.info(f'[SRV] Server info is ({server_info})')
339 log.info(
340 f'[RESP-STEP-18] Add comment response is'
341 f' ({extract_fields(response_step_18_add_comment, fields=("self", "body"))})'
342 )
343 log.info(
344 f'[RESP-STEP-23] Create component response is ({extract_fields(comp_resp, fields=("self", "description"))})'
345 )
346 log.info(
347 f'[RESP-STEP-26] Add comment response is'
348 f' ({extract_fields(response_step_26_add_comment, fields=("self", "body"))})'
349 )
350 log.info(
351 f'[RESP-STEP-27] Add comment response is'
352 f' ({extract_fields(response_step_27_add_comment, fields=("self", "body"))})'
353 )
354 log.info('-' * 84)
356 log.info('Dumping records to store...')
357 store.dump(end_time=end_time, has_failures=has_failures)
358 log.info('-' * 84)
360 log.info('OK')
361 log.info('=' * 84)
363 return 0