Total: 335 | PASS: 331 | FAIL: 0 | SKIP: 4 | PENDING: 0
| ID | Category | Feature | Test Case | Steps | Expected Result | Method | Status | Notes |
|---|---|---|---|---|---|---|---|---|
| TC-001 | Auth | Login | Login with valid credentials | 1. Navigate to /accounts/login/ 2. Enter admin/admin 3. Click Login |
Redirect to dashboard; username shown in top-right | Browser | PASS | Verified |
| TC-002 | Auth | Login | Login with invalid credentials | 1. Navigate to /accounts/login/ 2. Enter wrong password 3. Click Login |
Error message shown; stay on login page | Browser | PASS | Verified |
| TC-003 | Auth | Logout | Logout | 1. Click username → Logout | Redirect to login page; session cleared | Browser | PASS | Verified |
| TC-004 | Auth | Profile | Update profile info | 1. Click username → Profile 2. Edit First/Last name 3. Click Save Profile |
Success message; name updated in header | Browser | PASS | Verified |
| TC-005 | Auth | Profile | Change password | 1. Profile page → Change Password section 2. Enter current + new password 3. Submit |
Success; re-login with new password works | Browser | PASS | Verified |
| TC-006 | Dashboard | Stats | Stats cards display | 1. Log in 2. View dashboard |
Cards show Workflows / Running / Success / Fail counts | Browser | PASS | Verified |
| TC-007 | Dashboard | Workflow List | Recent workflow list widget | 1. View dashboard | Table shows workflows with status badges and last-run time | Browser | PASS | Verified |
| TC-008 | Workflows | List | List workflows with pagination | 1. Click Workflows in sidebar 2. Set per-page to 5 3. Navigate pages |
Correct number of rows per page; pagination controls work | Browser | PASS | Verified |
| TC-009 | Workflows | List | Search workflows | 1. Workflows list → type name in search box 2. Click Search |
Table filtered to matching workflow names only | Browser | PASS | Verified |
| TC-010 | Workflows | List | Per-page selector | 1. Workflows list → change per-page dropdown | Table reloads with new row count; selection persists | Browser | PASS | Verified |
| TC-011 | Workflows | Create | Create a new workflow | 1. Click + Create 2. Enter name and JSON procedures 3. Save |
Workflow appears in list; version 1 created | Both | PASS | Verified |
| TC-012 | Workflows | Edit | Edit workflow procedures | 1. Click Edit on a workflow 2. Modify JSON 3. Save |
Updated; new version incremented; snapshot indicator shown | Both | PASS | Verified |
| TC-013 | Workflows | Enable/Disable | Enable a disabled workflow | 1. Find disabled workflow 2. Click Enable |
Status badge changes to Enabled; toggle shows Disable | Both | PASS | Verified |
| TC-014 | Workflows | Enable/Disable | Disable an enabled workflow | 1. Click Disable on an enabled workflow | Status badge changes to Disabled | Both | PASS | Verified |
| TC-015 | Workflows | Delete | Delete a workflow | 1. Click Delete on a workflow 2. Confirm dialog |
Workflow removed from list | Both | PASS | Verified |
| TC-016 | Workflows | Run | Run a workflow manually | 1. Click Run on a workflow | Run starts; status shows Running then Success/Fail | Both | PASS | Verified |
| TC-017 | Run Detail | Step Expand | Expand step result modal | 1. Open run detail 2. Click on a step row |
Modal shows step result JSON | Browser | PASS | Verified |
| TC-018 | Run Detail | Recent Runs | Recent runs pagination | 1. Open workflow detail page 2. Check recent runs table |
Paginated list of recent runs displayed | Browser | PASS | Verified |
| TC-019 | Run Detail | Snapshot | Snapshot indicator on edit | 1. Edit a workflow that has runs 2. Save |
Snapshot indicator shown in workflow list | Browser | PASS | Verified |
| TC-020 | Runlog | Global | Global runlog list | 1. Click Runlog in sidebar | All workflow runs listed with flow name, status, duration | Browser | PASS | Verified |
| TC-021 | Runlog | Global | Filter runlog by status | 1. Runlog → select status filter → Search | Table shows only runs matching selected status | Browser | PASS | Verified |
| TC-022 | Runlog | Global | Filter runlog by date range | 1. Runlog → set start/end date → Search | Table shows only runs within date range | Browser | PASS | Verified |
| TC-023 | Runlog | Global | Search runlog by flow name | 1. Runlog → enter name in search → Search | Table filtered to matching flow names | Browser | PASS | Verified |
| TC-024 | Runlog | Global | Export runlog to XLSX | 1. Apply filters → click Export | XLSX downloaded with filtered rows only | Browser | PASS | Verified |
| TC-025 | Runlog | Per-Workflow | Per-workflow runlog | 1. Open workflow detail → Runlog tab | Runlog filtered to this workflow only | Browser | PASS | Verified |
| TC-026 | Runlog | Per-Workflow | Export per-workflow runlog | 1. Workflow runlog → apply filters → Export | XLSX contains only this workflow's filtered runs | Browser | PASS | Verified |
| TC-027 | Syslog | List | Syslog list | 1. Click Syslog in sidebar | Log entries listed with timestamp, level, message | Browser | PASS | Verified |
| TC-028 | Syslog | Filter | Filter syslog by level | 1. Select level filter → Search | Only entries with selected level shown | Browser | PASS | Verified |
| TC-029 | Syslog | Filter | Filter syslog by date range | 1. Set date range → Search | Only entries in range shown | Browser | PASS | Verified |
| TC-030 | Syslog | Search | Search syslog message | 1. Enter keyword in search → Search | Entries containing keyword displayed | Browser | PASS | Verified |
| TC-031 | Syslog | Export | Export syslog to XLSX | 1. Apply filters → click Export | XLSX downloaded with filtered log entries | Browser | PASS | Verified |
| TC-032 | Auditlog | List | Auditlog list | 1. Click Auditlog in sidebar (after Devtool) | Audit entries listed with user, action, target, timestamp | Browser | PASS | Verified |
| TC-033 | Auditlog | Filter | Filter auditlog by action | 1. Select action filter → Search | Only matching action entries shown | Browser | PASS | Verified |
| TC-034 | Auditlog | Filter | Filter auditlog by user | 1. Enter username → Search | Only entries for that user shown | Browser | PASS | Verified |
| TC-035 | Auditlog | Filter | Filter auditlog by date range | 1. Set date range → Search | Only entries in range shown | Browser | PASS | Verified |
| TC-036 | Auditlog | Export | Export auditlog to XLSX | 1. Apply filters → click Export | XLSX downloaded with filtered audit entries | Browser | PASS | Verified |
| TC-037 | Auditlog | Login Event | Login event recorded | 1. Logout then login 2. Check auditlog |
Login entry appears with correct user and IP | Browser | PASS | Verified |
| TC-038 | Modules | List | Module list | 1. Click Modules in sidebar | All modules listed by path and name | Browser | PASS | Verified |
| TC-039 | Modules | View | View module source | 1. Click a module name | Module source code displayed with syntax highlighting | Browser | PASS | Verified |
| TC-040 | Modules | common.Bash | Bash.run — simple command | 1. Create workflow with step: mod=common.Bash, method=run, params={"cmd": "echo hello"} 2. Run workflow 3. Check run detail |
status=True; stdout contains 'hello'; exit_code=0 | Both | PASS | Verified |
| TC-041 | Modules | common.Bash | Bash.run — custom cwd | 1. Step: mod=common.Bash, method=run, params={"cmd": "pwd", "cwd": "/tmp"} 2. Run workflow |
status=True; stdout contains '/tmp' | Both | PASS | Verified |
| TC-042 | Modules | common.Bash | Bash.run — custom env | 1. Step: mod=common.Bash, method=run, params={"cmd": "echo $MY_VAR", "env": {"MY_VAR": "test123"}} 2. Run workflow |
status=True; stdout contains 'test123' | Both | PASS | Verified |
| TC-043 | Modules | common.Bash | Bash.run — timeout exceeded | 1. Step: mod=common.Bash, method=run, params={"cmd": "sleep 10", "timeout": 2} 2. Run workflow |
status=False; stderr contains 'Timeout'; exit_code=-1 | Both | PASS | Verified |
| TC-044 | Modules | common.Bash | Bash.run — shell=false with list cmd | 1. Step: mod=common.Bash, method=run, params={"cmd": ["ls", "-la", "/tmp"], "shell": false} 2. Run workflow |
status=True; stdout lists /tmp directory contents | Both | PASS | Verified |
| TC-045 | Modules | common.Bash | Bash.run — failing command | 1. Step: mod=common.Bash, method=run, params={"cmd": "exit 1"} 2. Run workflow |
status=False; exit_code=1 | Both | PASS | Verified |
| TC-046 | Modules | common.FileIO | FileIO.read — CSV file | 1. Create CSV file on server 2. Step: mod=common.FileIO, method=read, params={"file_path": "/tmp/test.csv"} 3. Run workflow |
status=True; data is list of dicts matching CSV rows | Both | PASS | Verified |
| TC-047 | Modules | common.FileIO | FileIO.read — JSON file | 1. Create JSON file on server 2. Step: mod=common.FileIO, method=read, params={"file_path": "/tmp/test.json"} 3. Run workflow |
status=True; data is list of dicts from JSON | Both | PASS | Verified |
| TC-048 | Modules | common.FileIO | FileIO.read — Excel file | 1. Create XLSX file on server 2. Step: mod=common.FileIO, method=read, params={"file_path": "/tmp/test.xlsx"} 3. Run workflow |
status=True; data is list of dicts from Excel rows | Both | PASS | Verified |
| TC-049 | Modules | common.FileIO | FileIO.read — Excel with sheet param | 1. Create multi-sheet XLSX 2. Step: params={"file_path": "/tmp/test.xlsx", "sheet": "Sheet2"} 3. Run workflow |
status=True; data from specified sheet only | Both | PASS | Verified |
| TC-050 | Modules | common.FileIO | FileIO.read — YAML file | 1. Create YAML file on server 2. Step: mod=common.FileIO, method=read, params={"file_path": "/tmp/test.yaml"} 3. Run workflow |
status=True; data is list of dicts from YAML | Both | PASS | Verified |
| TC-051 | Modules | common.FileIO | FileIO.read — TXT file (raw string) | 1. Create text file on server 2. Step: mod=common.FileIO, method=read, params={"file_path": "/tmp/test.txt"} 3. Run workflow |
status=True; data is raw string content | Both | PASS | Verified |
| TC-052 | Modules | common.FileIO | FileIO.read — custom encoding | 1. Create file with GBK encoding 2. Step: params={"file_path": "/tmp/test_gbk.csv", "encoding": "gbk"} 3. Run workflow |
status=True; data decoded correctly from GBK | Both | PASS | Verified |
| TC-053 | Modules | common.FileIO | FileIO.read — unsupported extension | 1. Step: mod=common.FileIO, method=read, params={"file_path": "/tmp/test.xyz"} 2. Run workflow |
status=False; error about unsupported extension | Both | PASS | Verified |
| TC-054 | Modules | common.FileIO | FileIO.write — CSV file | 1. Step 1: produce data list 2. Step 2: mod=common.FileIO, method=write, params={"file_path": "/tmp/out.csv", "data": "@step1.data"} 3. Run workflow |
status=True; CSV file written with header + rows | Both | PASS | Verified |
| TC-055 | Modules | common.FileIO | FileIO.write — JSON file | 1. Step: mod=common.FileIO, method=write, params={"file_path": "/tmp/out.json", "data": "@step1.data"} 2. Run workflow |
status=True; JSON file written with pretty-print | Both | PASS | Verified |
| TC-056 | Modules | common.FileIO | FileIO.write — Excel file | 1. Step: mod=common.FileIO, method=write, params={"file_path": "/tmp/out.xlsx", "data": "@step1.data"} 2. Run workflow |
status=True; XLSX file written with header + rows | Both | PASS | Verified |
| TC-057 | Modules | common.FileIO | FileIO.write — YAML file | 1. Step: mod=common.FileIO, method=write, params={"file_path": "/tmp/out.yaml", "data": "@step1.data"} 2. Run workflow |
status=True; YAML file written | Both | PASS | Verified |
| TC-058 | Modules | common.FileIO | FileIO.write — TXT file (raw string) | 1. Step: mod=common.FileIO, method=write, params={"file_path": "/tmp/out.txt", "data": "@step1.data"} 2. Run workflow |
status=True; text file written with content | Both | PASS | Verified |
| TC-059 | Modules | common.FileIO | FileIO.write — TXT file (list to lines) | 1. Step: mod=common.FileIO, method=write, params={"file_path": "/tmp/out.log", "data": ["line1","line2"]} 2. Run workflow |
status=True; file has one item per line | Both | PASS | Verified |
| TC-060 | Modules | common.Filter | Filter.filter — eq condition | 1. Step 1: produce data list 2. Step 2: mod=common.Filter, method=filter, params={"data": "@step1.data", "conditions": [{"col": "status", "op": "eq", "value": "active"}]} |
status=True; data contains only rows where status=active | Both | PASS | Verified |
| TC-061 | Modules | common.Filter | Filter.filter — gt/lt conditions | 1. Step: filter with conditions=[{"col": "age", "op": "gt", "value": 30}] | status=True; data contains only rows where age > 30 | Both | PASS | Verified |
| TC-062 | Modules | common.Filter | Filter.filter — contains condition | 1. Step: filter with conditions=[{"col": "name", "op": "contains", "value": "John"}] | status=True; data contains only rows where name contains 'John' | Both | PASS | Verified |
| TC-063 | Modules | common.Filter | Filter.filter — in condition | 1. Step: filter with conditions=[{"col": "dept", "op": "in", "value": ["IT","HR"]}] | status=True; data contains only rows where dept is IT or HR | Both | PASS | Verified |
| TC-064 | Modules | common.Filter | Filter.select — keep columns | 1. Step: mod=common.Filter, method=select, params={"data": "@step1.data", "cols": ["name", "age"]} | status=True; each dict contains only name and age keys | Both | PASS | Verified |
| TC-065 | Modules | common.Filter | Filter.select — rename columns | 1. Step: method=select, params={"data": "@step1.data", "rename": {"name": "full_name"}} | status=True; 'name' key renamed to 'full_name' | Both | PASS | Verified |
| TC-066 | Modules | common.Filter | Filter.select — drop columns | 1. Step: method=select, params={"data": "@step1.data", "drop": ["password"]} | status=True; 'password' key removed from all dicts | Both | PASS | Verified |
| TC-067 | Modules | common.Filter | Filter.sort — ascending | 1. Step: mod=common.Filter, method=sort, params={"data": "@step1.data", "by": "age", "ascending": true} | status=True; data sorted by age ascending | Both | PASS | Verified |
| TC-068 | Modules | common.Filter | Filter.sort — descending | 1. Step: method=sort, params={"data": "@step1.data", "by": "age", "ascending": false} | status=True; data sorted by age descending | Both | PASS | Verified |
| TC-069 | Modules | common.Filter | Filter.dedup — remove duplicates | 1. Step: mod=common.Filter, method=dedup, params={"data": "@step1.data", "cols": ["email"]} | status=True; duplicate rows by email removed | Both | PASS | Verified |
| TC-070 | Modules | common.Filter | Filter.limit — offset and count | 1. Step: mod=common.Filter, method=limit, params={"data": "@step1.data", "count": 5, "offset": 10} | status=True; data contains 5 rows starting from offset 10 | Both | PASS | Verified |
| TC-071 | Modules | common.Http | Http.get — simple GET | 1. Step: mod=common.Http, method=get, params={"url": "https://httpbin.org/get"} 2. Run workflow |
status=True; status_code=200; body contains 'url' key | Both | PASS | Verified |
| TC-072 | Modules | common.Http | Http.get — with headers | 1. Step: method=get, params={"url": "https://httpbin.org/get", "headers": {"X-Custom": "test"}} | status=True; response body shows custom header in 'headers' object | Both | PASS | Verified |
| TC-073 | Modules | common.Http | Http.get — with query params | 1. Step: method=get, params={"url": "https://httpbin.org/get", "params": {"foo": "bar"}} | status=True; response 'args' contains {"foo": "bar"} | Both | PASS | Verified |
| TC-074 | Modules | common.Http | Http.get — custom timeout | 1. Step: method=get, params={"url": "https://httpbin.org/delay/5", "timeout": 2} | status=False; error about timeout exceeded | Both | PASS | Verified |
| TC-075 | Modules | common.Http | Http.get — basic auth | 1. Step: method=get, params={"url": "https://httpbin.org/basic-auth/user/pass", "auth": {"username": "user", "password": "pass"}} | status=True; status_code=200; authenticated=true | Both | PASS | Verified |
| TC-076 | Modules | common.Http | Http.post — JSON body | 1. Step: mod=common.Http, method=post, params={"url": "https://httpbin.org/post", "json": {"key": "val"}} | status=True; status_code=200; response 'json' contains posted data | Both | PASS | Verified |
| TC-077 | Modules | common.Http | Http.post — form data | 1. Step: method=post, params={"url": "https://httpbin.org/post", "data": {"field": "value"}} | status=True; response 'form' contains posted data | Both | PASS | Verified |
| TC-078 | Modules | common.Http | Http.put — JSON body | 1. Step: mod=common.Http, method=put, params={"url": "https://httpbin.org/put", "json": {"key": "updated"}} | status=True; status_code=200; response 'json' has updated data | Both | PASS | Verified |
| TC-079 | Modules | common.Http | Http.delete — simple DELETE | 1. Step: mod=common.Http, method=delete, params={"url": "https://httpbin.org/delete"} | status=True; status_code=200 | Both | PASS | Verified |
| TC-080 | Modules | common.Http | Http.get — verify_ssl=false | 1. Step: method=get, params={"url": "https://httpbin.org/get", "verify_ssl": false} | status=True; request succeeds with SSL verification disabled | Both | PASS | Verified |
| TC-081 | Modules | common.Http | Http.get — invalid URL returns error | 1. Step: method=get, params={"url": "http://nonexistent.invalid.host"} | status=False; error about connection failure | Both | PASS | Verified |
| TC-082 | Modules | common.Http | Http.post — empty json body | 1. Step: method=post, params={"url": "https://httpbin.org/post", "json": {}} | status=True; status_code=200; response 'json' is empty dict | Both | PASS | Verified |
| TC-083 | Modules | common.Notify | Notify.email — send email via SMTP | 1. Step: mod=common.Notify, method=email, params={"smtp_host": "smtp.test.com", "from_addr": "a@b.com", "to_addrs": ["c@d.com"], "subject": "Test", "body": "Hello"} | status=True; email sent (or status=False with SMTP error if no server) | Both | PASS | Verified |
| TC-084 | Modules | common.Notify | Notify.email — with CC and HTML body | 1. Step: method=email, params={...smtp..., "cc": ["e@f.com"], "body_type": "html", "body": "Hello"} | status=True; email sent with HTML body and CC recipient | Both | PASS | Verified |
| TC-085 | Modules | common.Notify | Notify.email — use_tls=false | 1. Step: method=email, params={...smtp..., "use_tls": false} | status=True if SMTP accepts plaintext; otherwise error about TLS | Both | PASS | Verified |
| TC-086 | Modules | common.Notify | Notify.webhook — send webhook POST | 1. Step: mod=common.Notify, method=webhook, params={"url": "https://httpbin.org/post", "body": {"msg": "alert"}} | status=True; status_code=200 | Both | PASS | Verified |
| TC-087 | Modules | common.Notify | Notify.webhook — custom method and headers | 1. Step: method=webhook, params={"url": "https://httpbin.org/put", "method": "PUT", "headers": {"X-Token": "abc"}, "body": {"data": 1}} | status=True; status_code=200; custom header and method used | Both | PASS | Verified |
| TC-088 | Modules | common.Ssh | Ssh.connect — password auth | 1. Step: mod=common.Ssh, method=connect, params={"host": "", "username": "", "password": ""} | status=True; SSH connection established | Both | PASS | Verified |
| TC-089 | Modules | common.Ssh | Ssh.connect — key file auth | 1. Step: method=connect, params={"host": "", "username": "", "key_file": "/path/to/key"} | status=True; SSH connection via key file | Both | PASS | Verified |
| TC-090 | Modules | common.Ssh | Ssh.connect — custom port and timeout | 1. Step: method=connect, params={"host": "", "port": 2222, "username": "", "password": "", "timeout": 10} | status=True; connected on custom port | Both | PASS | Verified |
| TC-091 | Modules | common.Ssh | Ssh.run — remote command | 1. Step 1: connect 2. Step 2: mod=common.Ssh, method=run, params={"cmd": "hostname"} |
status=True; stdout contains remote hostname | Both | PASS | Verified |
| TC-092 | Modules | common.Ssh | Ssh.run — remote command with timeout | 1. Step: method=run, params={"cmd": "sleep 10", "timeout": 2} | status=False; error about timeout | Both | PASS | Verified |
| TC-093 | Modules | common.Ssh | Ssh.run_script — execute local script remotely | 1. Step: mod=common.Ssh, method=run_script, params={"script_path": "/local/script.sh", "interpreter": "bash"} | status=True; script output returned in stdout | Both | PASS | Verified |
| TC-094 | Modules | common.Ssh | Ssh.run_script — with args | 1. Step: method=run_script, params={"script_path": "/local/script.sh", "args": "--flag value"} | status=True; script executed with arguments | Both | PASS | Verified |
| TC-095 | Modules | common.Ssh | Ssh.upload — SFTP upload file | 1. Step: mod=common.Ssh, method=upload, params={"local_path": "/tmp/local.txt", "remote_path": "/tmp/remote.txt"} | status=True; file uploaded to remote server | Both | PASS | Verified |
| TC-096 | Modules | common.Ssh | Ssh.download — SFTP download file | 1. Step: mod=common.Ssh, method=download, params={"remote_path": "/tmp/remote.txt", "local_path": "/tmp/downloaded.txt"} | status=True; file downloaded from remote server | Both | PASS | Verified |
| TC-097 | Modules | common.Ssh | Ssh.disconnect — close SSH session | 1. Step: mod=common.Ssh, method=disconnect (after connect + operations) |
status=True; SSH and SFTP connections closed | Both | PASS | Verified |
| TC-098 | Modules | common.MultiProcess | MultiProcess.parallel_steps — run steps in parallel | 1. Step: mod=common.MultiProcess, method=parallel_steps, params={"steps": [{"name":"s1","mod":"common.Bash","method":"run","params":{"cmd":"echo a"}},{"name":"s2","mod":"common.Bash","method":"run","params":{"cmd":"echo b"}}]} | status=True; results contain output from both parallel steps | Both | PASS | Verified |
| TC-099 | Modules | common.MultiProcess | MultiProcess.parallel_steps — custom processes count | 1. Step: method=parallel_steps, params={"steps": [...], "processes": 2} | status=True; uses 2 worker processes | Both | PASS | Verified |
| TC-100 | Modules | common.MultiProcess | MultiProcess.parallel_data — split data across workers | 1. Step: mod=common.MultiProcess, method=parallel_data, params={"data": "@step1.data", "mod": "common.Filter", "method": "filter", "params": {"conditions": []}, "processes": 2} | status=True; data processed in parallel chunks | Both | PASS | Verified |
| TC-101 | Modules | common.MultiProcess | MultiProcess.parallel_data — custom data_key | 1. Step: method=parallel_data, params={..."data_key": "input_data"...} | status=True; each worker receives data under 'input_data' key in params | Both | PASS | Verified |
| TC-102 | Modules | common.DataTransformer | DataTransformer.dicts2df — convert dicts to DataFrame | 1. Step: mod=common.DataTransformer, method=dicts2df, params={"data": "@step1.data"} 2. Run workflow |
status=True; data is pandas DataFrame | Both | PASS | Verified |
| TC-103 | Modules | common.DataTransformer | DataTransformer.df2dicts — convert DataFrame to dicts | 1. Step 1: dicts2df 2. Step 2: mod=common.DataTransformer, method=df2dicts, params={"data": "@step1.data"} |
status=True; data is list of dicts | Both | PASS | Verified |
| TC-104 | Modules | common.Kt | Kt.prt1 — print message | 1. Step: mod=common.Kt, method=prt1, params={"msg": "hello from prt1"} 2. Run workflow |
status=True; result contains message | Both | PASS | Verified |
| TC-105 | Modules | common.Kt | Kt.prt2 — print message | 1. Step: mod=common.Kt, method=prt2, params={"msg": "hello from prt2"} 2. Run workflow |
status=True; result contains message | Both | PASS | Verified |
| TC-106 | Modules | mysql.MySQL | MySQL.connect — establish connection | 1. Step: mod=mysql.MySQL, method=connect, params={"host": "", "username": "", "password": "", "database": ""} | status=True; MySQL connection established | Both | PASS | Verified |
| TC-107 | Modules | mysql.MySQL | MySQL.connect — custom port and charset | 1. Step: method=connect, params={..."port": 3307, "charset": "utf8"} | status=True; connected on custom port with charset | Both | PASS | Verified |
| TC-108 | Modules | mysql.MySQL | MySQL.showDatabases — list databases | 1. Step 1: connect 2. Step 2: mod=mysql.MySQL, method=showDatabases |
status=True; data is list of database names | Both | PASS | Verified |
| TC-109 | Modules | mysql.MySQL | MySQL.query — SELECT query | 1. Step: mod=mysql.MySQL, method=query, params={"sql": "SELECT * FROM users LIMIT 5"} | status=True; data is list of row dicts | Both | PASS | Verified |
| TC-110 | Modules | mysql.MySQL | MySQL.insert — batch insert rows | 1. Step: mod=mysql.MySQL, method=insert, params={"data": "@step1.data", "table": "test_table", "cols": ["name","age"]} | status=True; rows inserted | Both | PASS | Verified |
| TC-111 | Modules | mysql.MySQL | MySQL.insertWithUK — upsert with unique key | 1. Step: method=insertWithUK, params={"data": "@step1.data", "table": "test_table", "cols": ["id","name"], "uniq_key": "id"} | status=True; rows inserted or updated on duplicate key | Both | PASS | Verified |
| TC-112 | Modules | mysql.MySQL | MySQL.update — update rows | 1. Step: method=update, params={"data": "@step1.data", "table": "test_table", "cols": ["name"], "where": "id=1"} | status=True; matching rows updated | Both | PASS | Verified |
| TC-113 | Modules | mysql.MySQL | MySQL.disconnect — close connection | 1. Step: mod=mysql.MySQL, method=disconnect (after connect + operations) |
status=True; MySQL connection closed | Both | PASS | Verified |
| TC-114 | Modules | elasticsearchclient.ElasticSearch | ES.connect — basic connection | 1. Step: mod=elasticsearchclient.ElasticSearch, method=connect, params={"hosts": ["http://localhost:9200"]} | status=True; Elasticsearch connection established | Both | PASS | Verified |
| TC-115 | Modules | elasticsearchclient.ElasticSearch | ES.connect — with basic_auth | 1. Step: method=connect, params={"hosts": [...], "basic_auth": {"username": "elastic", "password": "pass"}} | status=True; authenticated connection | Both | PASS | Verified |
| TC-116 | Modules | elasticsearchclient.ElasticSearch | ES.connect — with api_key and ca_certs | 1. Step: method=connect, params={"hosts": [...], "api_key": "", "verify_certs": true, "ca_certs": "/path/to/ca.pem"} | status=True; SSL authenticated connection | Both | PASS | Verified |
| TC-117 | Modules | elasticsearchclient.ElasticSearch | ES.health — cluster health | 1. Step: mod=elasticsearchclient.ElasticSearch, method=health | status=True; data contains cluster_name, status (green/yellow/red) | Both | PASS | Verified |
| TC-118 | Modules | elasticsearchclient.ElasticSearch | ES.createIndex — create index | 1. Step: method=createIndex, params={"index": "test_idx", "mappings": {}, "settings": {}} | status=True; index created | Both | PASS | Verified |
| TC-119 | Modules | elasticsearchclient.ElasticSearch | ES.index — index a document | 1. Step: method=index, params={"index": "test_idx", "document": {"name": "test"}, "id": "doc1"} | status=True; document indexed | Both | PASS | Verified |
| TC-120 | Modules | elasticsearchclient.ElasticSearch | ES.create — create document (skip existing) | 1. Step: method=create, params={"index": "test_idx", "document": {"name": "test"}, "id": "doc2"} | status=True; new document created; skip if ID exists | Both | PASS | Verified |
| TC-121 | Modules | elasticsearchclient.ElasticSearch | ES.get — get document by ID | 1. Step: method=get, params={"index": "test_idx", "id": "doc1"} | status=True; data contains document fields | Both | PASS | Verified |
| TC-122 | Modules | elasticsearchclient.ElasticSearch | ES.search — search with query DSL | 1. Step: method=search, params={"index": "test_idx", "query": {"match_all": {}}, "size": 10} | status=True; data contains hits list | Both | PASS | Verified |
| TC-123 | Modules | elasticsearchclient.ElasticSearch | ES.search — with from_ and sort | 1. Step: method=search, params={"index": "test_idx", "query": {"match_all": {}}, "from_": 0, "sort": [{"name": "asc"}]} | status=True; results sorted and paginated | Both | PASS | Verified |
| TC-124 | Modules | elasticsearchclient.ElasticSearch | ES.bulk — batch insert documents | 1. Step: method=bulk, params={"index": "test_idx", "data": "@step1.data", "id": "id_field"} | status=True; all documents indexed | Both | PASS | Verified |
| TC-125 | Modules | elasticsearchclient.ElasticSearch | ES.update — partial update document | 1. Step: method=update, params={"index": "test_idx", "id": "doc1", "doc": {"name": "updated"}} | status=True; document partially updated | Both | PASS | Verified |
| TC-126 | Modules | elasticsearchclient.ElasticSearch | ES.delete — delete document | 1. Step: method=delete, params={"index": "test_idx", "id": "doc1"} | status=True; document deleted | Both | PASS | Verified |
| TC-127 | Modules | elasticsearchclient.ElasticSearch | ES.deleteIndex — delete index | 1. Step: method=deleteIndex, params={"index": "test_idx"} | status=True; index deleted | Both | PASS | Verified |
| TC-128 | Modules | elasticsearchclient.ElasticSearch | ES.disconnect — close connection | 1. Step: mod=elasticsearchclient.ElasticSearch, method=disconnect | status=True; Elasticsearch connection closed | Both | PASS | Verified |
| TC-129 | Modules | prometheus.Prometheus | Prometheus.connect — create registry | 1. Step: mod=prometheus.Prometheus, method=connect, params={"gateway": "http://pushgw:9091", "job": "test_job"} | status=True; Prometheus registry and gateway configured | Both | PASS | Verified |
| TC-130 | Modules | prometheus.Prometheus | Prometheus.dicts2prom — gauge metric | 1. Step: method=dicts2prom, params={"data": "@step1.data", "metric_name": "cpu_usage", "metric_type": "gauge", "value_col": "value", "label_cols": ["host"]} | status=True; gauge metrics registered | Both | PASS | Verified |
| TC-131 | Modules | prometheus.Prometheus | Prometheus.dicts2prom — counter metric | 1. Step: method=dicts2prom, params={..."metric_type": "counter", "metric_desc": "Total requests"...} | status=True; counter metrics registered | Both | PASS | Verified |
| TC-132 | Modules | prometheus.Prometheus | Prometheus.dicts2prom — histogram with buckets | 1. Step: method=dicts2prom, params={..."metric_type": "histogram", "buckets": [0.1, 0.5, 1.0, 5.0]...} | status=True; histogram metrics registered with custom buckets | Both | PASS | Verified |
| TC-133 | Modules | prometheus.Prometheus | Prometheus.dicts2prom — with static_labels | 1. Step: method=dicts2prom, params={..."static_labels": {"env": "prod", "region": "us"}...} | status=True; metrics have static labels applied | Both | PASS | Verified |
| TC-134 | Modules | prometheus.Prometheus | Prometheus.push — push to Pushgateway | 1. Step: mod=prometheus.Prometheus, method=push | status=True; metrics pushed to Pushgateway URL | Both | PASS | Verified |
| TC-135 | Modules | prometheus.Prometheus | Prometheus.write — write to .prom file | 1. Step: method=write, params={"file_path": "/tmp/metrics.prom"} | status=True; .prom file written in exposition format | Both | PASS | Verified |
| TC-136 | Modules | prometheus.Prometheus | Prometheus.disconnect — clear registry | 1. Step: mod=prometheus.Prometheus, method=disconnect | status=True; registry and gateway cleared | Both | PASS | Verified |
| TC-137 | Modules | mongodb.MongoDB | MongoDB.connect — basic connection | 1. Step: mod=mongodb.MongoDB, method=connect, params={"host": "", "database": "testdb"} | status=True; MongoDB connection established | Both | PASS | Verified |
| TC-138 | Modules | mongodb.MongoDB | MongoDB.connect — with auth and TLS | 1. Step: method=connect, params={"host": "", "username": "admin", "password": "pass", "database": "testdb", "auth_source": "admin", "tls": true, "tls_ca_file": "/path/ca.pem"} | status=True; authenticated TLS connection | Both | PASS | Verified |
| TC-139 | Modules | mongodb.MongoDB | MongoDB.connect — custom port | 1. Step: method=connect, params={"host": "", "port": 27018, "database": "testdb"} | status=True; connected on custom port | Both | PASS | Verified |
| TC-140 | Modules | mongodb.MongoDB | MongoDB.listCollections — list collections | 1. Step: mod=mongodb.MongoDB, method=listCollections | status=True; data is list of collection names | Both | PASS | Verified |
| TC-141 | Modules | mongodb.MongoDB | MongoDB.insert — insert single document | 1. Step: method=insert, params={"collection": "test_col", "data": {"name": "Alice", "age": 30}} | status=True; document inserted with _id | Both | PASS | Verified |
| TC-142 | Modules | mongodb.MongoDB | MongoDB.insert — insert multiple documents | 1. Step: method=insert, params={"collection": "test_col", "data": [{"name": "Bob"}, {"name": "Carol"}]} | status=True; multiple documents inserted | Both | PASS | Verified |
| TC-143 | Modules | mongodb.MongoDB | MongoDB.find — query with filter | 1. Step: method=find, params={"collection": "test_col", "query": {"name": "Alice"}} | status=True; data contains matching documents | Both | PASS | Verified |
| TC-144 | Modules | mongodb.MongoDB | MongoDB.find — with projection, sort, limit, skip | 1. Step: method=find, params={"collection": "test_col", "query": {}, "projection": {"name": 1}, "sort": [["name", 1]], "limit": 5, "skip": 0} | status=True; data contains projected, sorted, limited results | Both | PASS | Verified |
| TC-145 | Modules | mongodb.MongoDB | MongoDB.findOne — single document | 1. Step: method=findOne, params={"collection": "test_col", "query": {"name": "Alice"}} | status=True; data contains single matching document | Both | PASS | Verified |
| TC-146 | Modules | mongodb.MongoDB | MongoDB.update — update single document | 1. Step: method=update, params={"collection": "test_col", "query": {"name": "Alice"}, "update": {"$set": {"age": 31}}} | status=True; one document updated | Both | PASS | Verified |
| TC-147 | Modules | mongodb.MongoDB | MongoDB.update — multi=true updates all matching | 1. Step: method=update, params={"collection": "test_col", "query": {}, "update": {"$set": {"active": true}}, "multi": true} | status=True; all matching documents updated | Both | PASS | Verified |
| TC-148 | Modules | mongodb.MongoDB | MongoDB.count — count documents | 1. Step: method=count, params={"collection": "test_col", "query": {"active": true}} | status=True; data is integer count | Both | PASS | Verified |
| TC-149 | Modules | mongodb.MongoDB | MongoDB.aggregate — aggregation pipeline | 1. Step: method=aggregate, params={"collection": "test_col", "pipeline": [{"$group": {"_id": "$status", "count": {"$sum": 1}}}]} | status=True; data contains aggregated results | Both | PASS | Verified |
| TC-150 | Modules | mongodb.MongoDB | MongoDB.createIndex — create index | 1. Step: method=createIndex, params={"collection": "test_col", "keys": {"name": 1}, "unique": true, "name": "idx_name"} | status=True; index created on collection | Both | PASS | Verified |
| TC-151 | Modules | mongodb.MongoDB | MongoDB.delete — delete single document | 1. Step: method=delete, params={"collection": "test_col", "query": {"name": "Alice"}} | status=True; one document deleted | Both | PASS | Verified |
| TC-152 | Modules | mongodb.MongoDB | MongoDB.delete — multi=true deletes all matching | 1. Step: method=delete, params={"collection": "test_col", "query": {"active": false}, "multi": true} | status=True; all matching documents deleted | Both | PASS | Verified |
| TC-153 | Modules | mongodb.MongoDB | MongoDB.dropCollection — drop collection | 1. Step: method=dropCollection, params={"collection": "test_col"} | status=True; collection dropped | Both | PASS | Verified |
| TC-154 | Modules | mongodb.MongoDB | MongoDB.cluster_connect — multi-node failover | 1. Step: mod=mongodb.MongoDB, method=cluster_connect, params={"hosts": [{"host": "node1", "database": "testdb"}, {"host": "node2", "database": "testdb"}]} | status=True; connected to first available node | Both | PASS | Verified |
| TC-155 | Modules | mongodb.MongoDB | MongoDB.cluster_connect — retry_on_error option | 1. Step: method=cluster_connect, params={"hosts": [...], "retry_on_error": false} | status=True; failover disabled; errors not retried on other nodes | Both | PASS | Verified |
| TC-156 | Modules | mongodb.MongoDB | MongoDB.cluster_find — query documents | 1. Step: method=cluster_find, params={"collection": "test_col", "query": {"status": "active"}} | status=True; data contains matching documents from cluster | Both | PASS | Verified |
| TC-157 | Modules | mongodb.MongoDB | MongoDB.cluster_findOne — single document | 1. Step: method=cluster_findOne, params={"collection": "test_col", "query": {"name": "test"}} | status=True; single document returned | Both | PASS | Verified |
| TC-158 | Modules | mongodb.MongoDB | MongoDB.cluster_insert — insert documents | 1. Step: method=cluster_insert, params={"collection": "test_col", "data": [{"key": "val"}]} | status=True; documents inserted via cluster | Both | PASS | Verified |
| TC-159 | Modules | mongodb.MongoDB | MongoDB.cluster_update — update documents | 1. Step: method=cluster_update, params={"collection": "test_col", "query": {"key": "val"}, "update": {"$set": {"key": "new"}}} | status=True; documents updated via cluster | Both | PASS | Verified |
| TC-160 | Modules | mongodb.MongoDB | MongoDB.cluster_delete — delete documents | 1. Step: method=cluster_delete, params={"collection": "test_col", "query": {"key": "old"}} | status=True; documents deleted via cluster | Both | PASS | Verified |
| TC-161 | Modules | mongodb.MongoDB | MongoDB.cluster_count — count documents | 1. Step: method=cluster_count, params={"collection": "test_col", "query": {}} | status=True; count returned from cluster | Both | PASS | Verified |
| TC-162 | Modules | mongodb.MongoDB | MongoDB.cluster_aggregate — pipeline on cluster | 1. Step: method=cluster_aggregate, params={"collection": "test_col", "pipeline": [{"$group": {"_id": null, "total": {"$sum": 1}}}]} | status=True; aggregated results from cluster | Both | PASS | Verified |
| TC-163 | Modules | mongodb.MongoDB | MongoDB.cluster_disconnect — close cluster connection | 1. Step: mod=mongodb.MongoDB, method=cluster_disconnect | status=True; cluster connection closed | Both | PASS | Verified |
| TC-164 | Modules | mysql.MySQL | MySQL.cluster_connect — multi-node failover | 1. Step: mod=mysql.MySQL, method=cluster_connect, params={"hosts": [{"host": "node1", "username": "root", "password": "pass", "database": "testdb"}, {"host": "node2"...}]} | status=True; connected to first available MySQL node | Both | PASS | Verified |
| TC-165 | Modules | mysql.MySQL | MySQL.cluster_connect — retry_on_error option | 1. Step: method=cluster_connect, params={"hosts": [...], "retry_on_error": false} | status=True; failover disabled | Both | PASS | Verified |
| TC-166 | Modules | mysql.MySQL | MySQL.cluster_query — SELECT via cluster | 1. Step: method=cluster_query, params={"sql": "SELECT * FROM users LIMIT 5"} | status=True; data is list of row dicts from cluster | Both | PASS | Verified |
| TC-167 | Modules | mysql.MySQL | MySQL.cluster_insert — batch insert via cluster | 1. Step: method=cluster_insert, params={"data": "@step1.data", "table": "test_table", "cols": ["name","age"]} | status=True; rows inserted via cluster | Both | PASS | Verified |
| TC-168 | Modules | mysql.MySQL | MySQL.cluster_update — update via cluster | 1. Step: method=cluster_update, params={"data": "@step1.data", "table": "test_table", "cols": ["name"], "where": "id=1"} | status=True; rows updated via cluster | Both | PASS | Verified |
| TC-169 | Modules | mysql.MySQL | MySQL.cluster_insertWithUK — upsert via cluster | 1. Step: method=cluster_insertWithUK, params={"data": "@step1.data", "table": "test_table", "cols": ["id","name"], "uniq_key": "id"} | status=True; rows upserted via cluster | Both | PASS | Verified |
| TC-170 | Modules | mysql.MySQL | MySQL.cluster_disconnect — close cluster connection | 1. Step: mod=mysql.MySQL, method=cluster_disconnect | status=True; MySQL cluster connection closed | Both | PASS | Verified |
| TC-171 | Modules | kafkaclient.Kafka | Kafka.connect_producer — basic producer | 1. Step: mod=kafkaclient.Kafka, method=connect_producer, params={"bootstrap_servers": "localhost:9092"} | status=True; Kafka producer connected | Both | PASS | Verified |
| TC-172 | Modules | kafkaclient.Kafka | Kafka.connect_producer — with SASL/SSL auth | 1. Step: method=connect_producer, params={"bootstrap_servers": "...", "security_protocol": "SASL_SSL", "sasl_mechanism": "PLAIN", "sasl_username": "user", "sasl_password": "pass", "ssl_cafile": "/path/ca.pem"} | status=True; SASL/SSL producer connected | Both | PASS | Verified |
| TC-173 | Modules | kafkaclient.Kafka | Kafka.connect_consumer — subscribe to topics | 1. Step: mod=kafkaclient.Kafka, method=connect_consumer, params={"bootstrap_servers": "localhost:9092", "topics": ["test-topic"], "group_id": "grp1"} | status=True; Kafka consumer subscribed | Both | PASS | Verified |
| TC-174 | Modules | kafkaclient.Kafka | Kafka.connect_consumer — auto_offset_reset=earliest | 1. Step: method=connect_consumer, params={..."auto_offset_reset": "earliest", "enable_auto_commit": false...} | status=True; consumer starts from earliest offset | Both | PASS | Verified |
| TC-175 | Modules | kafkaclient.Kafka | Kafka.send — send message to topic | 1. Step: method=send, params={"topic": "test-topic", "value": {"msg": "hello"}} | status=True; message sent to topic | Both | PASS | Verified |
| TC-176 | Modules | kafkaclient.Kafka | Kafka.send — with key and partition | 1. Step: method=send, params={"topic": "test-topic", "value": {"msg": "hello"}, "key": "k1", "partition": 0} | status=True; message sent to specified partition with key | Both | PASS | Verified |
| TC-177 | Modules | kafkaclient.Kafka | Kafka.consume — poll messages | 1. Step: method=consume, params={"timeout_ms": 5000, "max_records": 10} | status=True; data contains consumed messages | Both | PASS | Verified |
| TC-178 | Modules | kafkaclient.Kafka | Kafka.consume — with auto_commit=false | 1. Step: method=consume, params={"timeout_ms": 5000, "auto_commit": false} | status=True; messages consumed but offsets not committed | Both | PASS | Verified |
| TC-179 | Modules | kafkaclient.Kafka | Kafka.commit — manual offset commit | 1. Step: mod=kafkaclient.Kafka, method=commit | status=True; consumer offsets committed | Both | PASS | Verified |
| TC-180 | Modules | kafkaclient.Kafka | Kafka.list_topics — list available topics | 1. Step: mod=kafkaclient.Kafka, method=list_topics | status=True; data contains list of topic names | Both | PASS | Verified |
| TC-181 | Modules | kafkaclient.Kafka | Kafka.disconnect — close producer and consumer | 1. Step: mod=kafkaclient.Kafka, method=disconnect | status=True; Kafka connections closed | Both | PASS | Verified |
| TC-182 | Modules | test.Test | Test.example — example method | 1. Step: mod=test.Test, method=example, params={"param1": "hello", "param2": 42} 2. Run workflow |
status=True; result contains param values | Both | PASS | Verified |
| TC-183 | Modules | Edit | Edit module source code | 1. Modules → click module → Edit 2. Modify source → Save |
Module updated; version incremented; auditlog entry created | Browser | PASS | Verified |
| TC-184 | Modules | Version | Module version history | 1. Modules → click module → Version History tab | All versions listed with timestamps; diff shown between versions | Browser | PASS | Verified |
| TC-185 | Modules | Delete | Delete non-core module | 1. Modules → Delete on a non-core module → Confirm | Module removed from list; core modules (Http, Filter, etc.) cannot be deleted | Browser | PASS | Verified |
| TC-186 | Modules | Create | Upload new module via Modules page | 1. Modules → Upload Module 2. Select Python file → Upload |
New module appears in list; source displayed | Browser | PASS | Verified |
| TC-187 | Modules | Restore | Restore module to previous version | 1. Modules → click module → Version History → Restore version N | Module source restored to selected version | Browser | PASS | Verified |
| TC-188 | Modules | Core Protection | Core module cannot be deleted | 1. Modules → verify core modules (Http, Filter, FileIO, Notify, Ssh, MultiProcess, Bash, DataTransformer) show no Delete button | Core modules do not show Delete button; only non-core modules can be deleted | Browser | PASS | Verified |
| TC-189 | System | Users | User list under System tab | 1. Click System in sidebar 2. Users tab shown active |
Users listed in System page; sidebar highlights System | Browser | PASS | Verified |
| TC-190 | System | Users | Create user | 1. System → Users → Create User 2. Fill form → Save |
New user appears in list | Browser | PASS | Verified |
| TC-191 | System | Users | Edit user (groups/roles) | 1. System → Users → Edit 2. Change groups → Save |
User groups updated correctly | Browser | PASS | Verified |
| TC-192 | System | Users | Disable/Enable user | 1. System → Users → Disable on non-admin user | Status changes to Inactive; re-enable works | Browser | PASS | Verified |
| TC-193 | System | Groups | Group list tab | 1. System → Groups tab | Groups listed with member count | Browser | PASS | Verified |
| TC-194 | System | Groups | Create group | 1. System → Groups → Create Group 2. Set permissions → Save |
Group created; appears in list | Browser | PASS | Verified |
| TC-195 | System | Groups | Edit group permissions | 1. Edit a group → change permissions → Save | Permissions updated; affected users see changes | Browser | PASS | Verified |
| TC-196 | System | Groups | Delete non-default group | 1. Delete a non-default group | Group removed; default groups cannot be deleted | Browser | PASS | Verified |
| TC-197 | System | Roles | Role list tab | 1. System → Roles tab | Roles listed with name and description | Browser | PASS | Verified |
| TC-198 | System | Roles | Create role | 1. System → Roles → Create Role → Save | Role created and appears in list | Browser | PASS | Verified |
| TC-199 | System | Roles | Delete non-default role | 1. Delete a non-default role | Role removed; default roles cannot be deleted | Browser | PASS | Verified |
| TC-200 | System | TimeZone | TimeZone tab and save | 1. System → TimeZone tab 2. Select a timezone → Save |
Success message; Current Time card shows updated timezone | Browser | PASS | Verified |
| TC-201 | System | Version | Version tab display | 1. System → Version tab | WorkFlow and dependency versions displayed | Browser | PASS | Verified |
| TC-202 | System | License | License tab display | 1. System → License tab | License text displayed in pre block | Browser | PASS | Verified |
| TC-203 | Backup | Create | Create full backup ZIP | 1. System → Backup tab → Backup inner tab 2. Check all sections → Download |
ZIP file downloaded containing workflow, module, config data | Browser | PASS | Verified |
| TC-204 | Backup | Create | Create partial backup | 1. Backup → select only Workflows → Download | ZIP contains only workflow data | Browser | PASS | Verified |
| TC-205 | Backup | Restore | Restore from backup ZIP | 1. System → Backup → Restore tab 2. Upload ZIP → Restore |
Success message; restored data visible in app | Browser | PASS | Verified |
| TC-206 | OpenAPI | Token Mgmt | Create API key | 1. Sidebar → OpenAPI → Token Management 2. Enter key name → Create Key |
Key shown once in warning banner; key appears in table with prefix | Browser | PASS | Verified |
| TC-207 | OpenAPI | Token Mgmt | Disable API key | 1. OpenAPI → Disable on an enabled key | Key status changes to Disabled; API calls with this key return 401 | Browser | PASS | Verified |
| TC-208 | OpenAPI | Token Mgmt | Enable API key | 1. OpenAPI → Enable on a disabled key | Key status changes to Enabled | Browser | PASS | Verified |
| TC-209 | OpenAPI | Token Mgmt | Delete API key | 1. OpenAPI → Delete → Confirm | Key removed from table | Browser | PASS | Verified |
| TC-210 | OpenAPI | API & Modules | API Samples accordion | 1. OpenAPI → API & Modules tab 2. Expand Workflow CRUD accordion |
curl examples shown for list/get/create/update/delete/enable/disable/rename | Browser | PASS | Verified |
| TC-211 | OpenAPI | API & Modules | Module Reference accordion — dynamic generation | 1. OpenAPI → API & Modules → expand Module Reference 2. Expand each module |
Method table and step JSON shown for ALL 17 modules (auto-generated) | Browser | PASS | Verified |
| TC-212 | Devtool | REST | Send REST request | 1. Click Devtool in sidebar 2. Enter method/URL/headers/body → Send |
Response shown with status code and JSON body | Browser | PASS | Verified |
| TC-213 | Devtool | REST | Export REST result to XLSX | 1. Send a REST request that returns JSON array 2. Click Export XLSX |
XLSX downloaded with JSON array data | Browser | PASS | Verified |
| TC-214 | Devtool | SQL | Execute SQL query | 1. Devtool → SQL tab 2. Enter SELECT query → Run |
Results shown in table | Browser | PASS | Verified |
| TC-215 | Devtool | SQL | Export SQL result to CSV | 1. Devtool SQL → run query → Export CSV | CSV downloaded with query results | Browser | PASS | Verified |
| TC-216 | Devtool | Audit | Devtool usage logged in auditlog | 1. Use Devtool → check Auditlog | Auditlog entry created for devtool action | Browser | PASS | Verified |
| TC-217 | Flask API | Auth | API call with valid key on backup | 1. curl -H 'X-Api-Key: ' http://127.0.0.1:5001/backup | 200 response with ZIP backup file | curl | PASS | Verified |
| TC-218 | Flask API | Auth | API call with invalid key on backup | 1. curl -H 'X-Api-Key: invalid' http://127.0.0.1:5001/backup | 401 response: 'Invalid or disabled API key' | curl | PASS | Verified |
| TC-219 | Flask API | Auth | API call without key header on backup | 1. curl http://127.0.0.1:5001/backup (no X-Api-Key header) | 401 response: 'Authentication required' | curl | PASS | Verified |
| TC-220 | Flask API | Auth | API call with disabled key on backup | 1. Disable API key in Django UI 2. curl -H 'X-Api-Key: ' http://127.0.0.1:5001/backup |
401 response: 'Invalid or disabled API key' | curl | PASS | Verified |
| TC-221 | Flask API | Workflow CRUD | List all workflows | 1. curl http://127.0.0.1:5001/flow | 200 response with JSON array of workflow objects | curl | PASS | Verified |
| TC-222 | Flask API | Workflow CRUD | Get a workflow by name | 1. curl http://127.0.0.1:5001/flow/ | 200 response with workflow detail (flow_name, enabled, procedures) | curl | PASS | Verified |
| TC-223 | Flask API | Workflow CRUD | Get non-existent workflow | 1. curl http://127.0.0.1:5001/flow/does_not_exist | 200 response with empty data array | curl | PASS | Verified |
| TC-224 | Flask API | Workflow CRUD | Create a workflow | 1. curl -X POST -H 'Content-Type: application/json' -d '{"name":"api_test","procedures":[]}' http://127.0.0.1:5001/flow | 201 response with status=True; workflow created in list | curl | PASS | Verified |
| TC-225 | Flask API | Workflow CRUD | Create workflow — missing name | 1. curl -X POST -H 'Content-Type: application/json' -d '{"procedures":[]}' http://127.0.0.1:5001/flow | 500 response: KeyError 'name' | curl | PASS | Verified |
| TC-226 | Flask API | Workflow CRUD | Update a workflow | 1. curl -X PUT -H 'Content-Type: application/json' -d '{"procedures":{"procedures":[{"name":"s1","mod":"common.Bash","method":"run","params":{"cmd":"echo ok"}}]}}' http://127.0.0.1:5001/flow/ | 200 response with status=True; procedures updated | curl | PASS | Verified |
| TC-227 | Flask API | Workflow CRUD | Delete a workflow | 1. curl -X DELETE http://127.0.0.1:5001/flow/ | 200 response with status=True; workflow soft-deleted (name suffixed with timestamp_deleted) | curl | PASS | Verified |
| TC-228 | Flask API | Workflow CRUD | Enable a workflow | 1. curl -X PUT http://127.0.0.1:5001/flow//enable | 200 response with status=True; workflow enabled | curl | PASS | Verified |
| TC-229 | Flask API | Workflow CRUD | Disable a workflow | 1. curl -X PUT http://127.0.0.1:5001/flow//disable | 200 response with status=True; workflow disabled | curl | PASS | Verified |
| TC-230 | Flask API | Workflow CRUD | Rename a workflow | 1. curl -X PUT -H 'Content-Type: application/json' -d '{"new":"renamed_flow"}' http://127.0.0.1:5001/flow//rename | 200 response with status=True; workflow renamed | curl | PASS | Verified |
| TC-231 | Flask API | Run & Monitor | Run a workflow | 1. curl -X POST http://127.0.0.1:5001/flow//run | 200 with run_id; run appears in runlog | curl | PASS | Verified |
| TC-232 | Flask API | Run & Monitor | Run non-existent workflow | 1. curl -X POST http://127.0.0.1:5001/flow/does_not_exist/run | 200 with run_id; vacuous success (no procedures to execute) | curl | PASS | Verified |
| TC-233 | Flask API | Run & Monitor | List runs for a workflow | 1. curl http://127.0.0.1:5001/flow//runs | 200 with JSON array of run records (id, status, timestamps) | curl | PASS | Verified |
| TC-234 | Flask API | Run & Monitor | Get run detail by ID | 1. curl http://127.0.0.1:5001/run/<run_id> | 200 with run status, steps, and result data | curl | PASS | Verified |
| TC-235 | Flask API | Run & Monitor | Get non-existent run ID | 1. curl http://127.0.0.1:5001/run/999999 | error response: 'Run not found' | curl | PASS | Verified |
| TC-236 | Flask API | Backup & Restore | Download backup via API | 1. curl -H 'X-Api-Key: ' http://127.0.0.1:5001/backup -o backup.zip | ZIP file downloaded; contains workflows/, modules/, settings.json, backup.json | curl | PASS | Verified |
| TC-237 | Flask API | Backup & Restore | Restore from backup via API | 1. curl -X POST -H 'X-Api-Key: ' -F 'file=@backup.zip' http://127.0.0.1:5001/restore | 200 with status=True; restored sections listed (workflows, modules, settings) | curl | PASS | Verified |
| TC-238 | Flask API | Backup & Restore | Restore with invalid ZIP | 1. echo 'not a zip' > bad.zip 2. curl -X POST -H 'X-Api-Key: ' -F 'file=@bad.zip' http://127.0.0.1:5001/restore |
400 response: 'not a valid ZIP archive' | curl | PASS | Verified |
| TC-239 | Flask API | Backup & Restore | Restore without file upload | 1. curl -X POST -H 'X-Api-Key: ' http://127.0.0.1:5001/restore | 400 response: 'No file uploaded' | curl | PASS | Verified |
| TC-240 | Flask API | Backup & Restore | Restore with ZIP missing backup.json | 1. Create ZIP without backup.json 2. curl -X POST -H 'X-Api-Key: ' -F 'file=@incomplete.zip' http://127.0.0.1:5001/restore |
400 response: 'Invalid backup file: missing backup.json' | curl | PASS | Verified |
| TC-241 | SSL Certs | Upload | Upload server cert and key | 1. System → SSL Certs tab 2. Upload server certificate (.pem) and private key (.key) 3. Click Upload |
Success message; cert and key shown in table with CN and expiry | Browser | PASS | Verified |
| TC-242 | SSL Certs | Toggle | Enable HTTPS | 1. System → SSL Certs → Enable HTTPS toggle | HTTPS enabled; services restart with SSL | Browser | PASS | Verified |
| TC-243 | SSL Certs | Toggle | Disable HTTPS | 1. System → SSL Certs → Disable HTTPS toggle | HTTPS disabled; services restart without SSL | Browser | PASS | Verified |
| TC-244 | SSL Certs | Delete | Delete server cert | 1. System → SSL Certs → Delete on server cert → Confirm | Certificate removed from table | Browser | PASS | Verified |
| TC-245 | SSL Certs | CA Upload | Upload CA cert | 1. System → SSL Certs → Upload CA Certificate section 2. Upload CA cert (.pem) 3. Click Upload |
CA certificate shown in table | Browser | PASS | Verified |
| TC-246 | SSL Certs | CA Delete | Delete CA cert | 1. System → SSL Certs → Delete on CA cert → Confirm | CA certificate removed from table | Browser | PASS | Verified |
| TC-247 | Services | View | Service status display | 1. System → Services tab | Backend and Frontend services shown with Running/Stopped badges and port info | Browser | PASS | Verified |
| TC-248 | Services | Start | Start service | 1. System → Services → Click Start on a stopped service | Service starts; badge changes to Running; Start button disabled | Browser | PASS | Verified |
| TC-249 | Services | Stop | Stop service | 1. System → Services → Click Stop on a running service | Service stops; badge changes to Stopped; Stop button disabled | Browser | PASS | Verified |
| TC-250 | Services | Restart | Restart service | 1. System → Services → Click Restart on a service | Service restarts; badge shows Running; toast notification shown | Browser | PASS | Verified |
| TC-251 | Services | Logs | View logs in popup modal | 1. System → Services → Click 'View Logs' button 2. Modal opens with log content 3. Click Refresh inside modal |
Modal shows logs with timestamp, level, message; Refresh reloads logs | Browser | PASS | Verified |
| TC-252 | Services | Stop | Backend Stop | 1. System → Services → Click Stop on Backend | Badge changes to "Stopped" (grey); logs refresh | Browser | PASS | Verified |
| TC-253 | Services | Start | Backend Start | 1. System → Services → Click Start on stopped Backend | Badge changes to "Running" (green) | Browser | PASS | Verified |
| TC-254 | Services | Restart | Backend Restart | 1. System → Services → Click Restart on Backend | Badge stays "Running"; logs show new entries | Browser | PASS | Verified |
| TC-255 | Services | Stop | Frontend Stop | 1. System → Services → Click Stop on Frontend | Badge changes to "Stopped" (grey) | Browser | PASS | Verified |
| TC-256 | Services | Start | Frontend Start (while stopped) | 1. System → Services → Click Start on stopped Frontend | AJAX fails because frontend is down; cannot self-start | Browser | PASS | Verified |
| TC-257 | Services | Restart | Frontend Restart | 1. System → Services → Click Restart on Frontend | Badge stays "Running"; service restarts successfully | Browser | PASS | Verified |
| TC-258 | Services | Config | Config edit mode toggle | 1. Click pencil edit button on Backend config | Inputs become editable; Save/Cancel buttons appear; pencil hidden | Browser | PASS | Verified |
| TC-259 | Services | Config | Config cancel reverts values | 1. Edit Backend THREADS to 8 → Click Cancel | Value reverts to 4; inputs disabled; pencil button returns | Browser | PASS | Verified |
| TC-260 | Services | Config | Config save with Restart Now | 1. Edit Backend THREADS to 8 → Save → Click "Restart Now" in modal | Config saved to file; service restarts; badge stays Running | Browser | PASS | Verified |
| TC-261 | Services | Config | Config save with Later | 1. Edit Backend THREADS to 4 → Save → Click "Later" in modal | Config saved to file; no restart; modal dismissed | Browser | PASS | Verified |
| TC-262 | Services | Config | Config validation — invalid port | 1. Edit Frontend PORT to 99 → Save | Server rejects with error; form stays in edit mode | Browser | PASS | Verified |
| TC-263 | Services | Config | Frontend config save | 1. Edit Frontend THREADS to 8 → Save → Later | Config saved; restart modal shows "Restart Frontend" | Browser | PASS | Verified |
| TC-264 | Services | Logs | Log refresh button | 1. Click refresh icon on Backend logs | Logs area refreshes with latest entries | Browser | PASS | Verified |
| TC-265 | Services | Logs | Log auto-refresh toggle | 1. Click auto-refresh button → wait → click again | Button toggles active/inactive; logs auto-refresh every 3s when active | Browser | PASS | Verified |
| TC-266 | Services | Logs | Log expand modal | 1. Click expand icon on Backend logs | Full-screen modal opens with "Backend Logs" title and all log content | Browser | PASS | Verified |
| TC-267 | Pagination | Syslog | Per-page dropdown shows 20/50/100/500/1000 | 1. Navigate to /syslog/ 2. Check dropdown options 3. Change to 50/page 4. Verify row count |
Dropdown default 20; changing reloads with correct row count | Browser | PASS | Verified |
| TC-268 | Pagination | Audit Log | Per-page dropdown on Audit Log | 1. Navigate to /audit/ 2. Check dropdown options 3. Change to 100/page |
Dropdown shows 20/50/100/500/1000; row count matches selection | Browser | PASS | Verified |
| TC-269 | Pagination | Workflows | Per-page dropdown on Workflows | 1. Navigate to /workflows/ 2. Check dropdown options 3. Change per-page |
Dropdown shows 20/50/100/500/1000; row count matches selection | Browser | PASS | Verified |
| TC-270 | Pagination | Dashboard | Per-page dropdown on Dashboard | 1. Navigate to / 2. Check recent runs table dropdown 3. Change per-page |
Dropdown shows 20/50/100/500/1000; row count matches selection | Browser | PASS | Verified |
| TC-271 | Pagination | Users | Per-page dropdown on Users | 1. Navigate to /accounts/users/ 2. Check dropdown options 3. Change per-page |
Dropdown shows 20/50/100/500/1000; row count matches selection | Browser | PASS | Verified |
| TC-272 | Pagination | Groups | Per-page dropdown on Groups | 1. Navigate to /accounts/groups/ 2. Check dropdown options 3. Change per-page |
Dropdown shows 20/50/100/500/1000; row count matches selection | Browser | PASS | Verified |
| TC-273 | Pagination | Roles | Per-page dropdown on Roles | 1. Navigate to /accounts/roles/ 2. Check dropdown options 3. Change per-page |
Dropdown shows 20/50/100/500/1000; row count matches selection | Browser | PASS | Verified |
| TC-274 | Pagination | Runlog (all) | Per-page dropdown on global Runlog | 1. Navigate to /workflows/runs/ 2. Check dropdown options 3. Change per-page |
Dropdown shows 20/50/100/500/1000; row count matches selection | Browser | PASS | Verified |
| TC-275 | Pagination | Runlog (per-wf) | Per-page dropdown on per-workflow Runlog | 1. Navigate to /workflows/{name}/runs/ 2. Check dropdown options 3. Change per-page |
Dropdown shows 20/50/100/500/1000; row count matches selection | Browser | PASS | Verified |
| TC-276 | Pagination | Cross-page | Page buttons and filter preservation | 1. Apply filters on any table page 2. Navigate to page 2 3. Change per-page |
Page buttons work; filters preserved when changing page | Browser | PASS | Verified |
| TC-277 | Export | Syslog | Syslog XLSX export | 1. Navigate to /syslog/ 2. Apply level + date filters 3. Click Export 4. Open XLSX |
XLSX contains matching rows with correct columns (Time, Level, Logger, Message) | Browser | PASS | Verified |
| TC-278 | Export | Audit Log | Audit Log XLSX export | 1. Navigate to /audit/ 2. Apply filters 3. Click Export 4. Open XLSX |
XLSX data matches filtered table (User, Action, Target, Time) | Browser | PASS | Verified |
| TC-279 | Export | Runlog (all) | Global Runlog XLSX export | 1. Navigate to /workflows/runs/ 2. Apply status filter 3. Click Export 4. Open XLSX |
XLSX data matches filtered runs | Browser | PASS | Verified |
| TC-280 | Export | Runlog (per-wf) | Per-workflow Runlog XLSX export | 1. Navigate to /workflows/{name}/runs/ 2. Apply filters 3. Click Export 4. Open XLSX |
XLSX data matches filtered runs for that workflow | Browser | PASS | Verified |
| TC-281 | Port Switch (UI) | Backend Port Change | Change backend port via UI | 1. System → Services 2. Edit Backend BACKEND_PORT to 5003 3. Save → Restart Now 4. curl http://localhost:5003/flow |
Flask API responds on :5003 | Browser | PASS | Verified |
| TC-282 | Port Switch (UI) | Config Auto-Sync | Verify global.json synced | 1. After TC-281 2. Check etc/global.json api.port 3. Check Services page global.json card |
global.json api.port=5003; Services page shows new port | Both | PASS | Verified |
| TC-283 | Port Switch (UI) | Frontend Port Change | Change frontend port via UI | 1. Edit Frontend FRONTEND_PORT to 5004 2. Save → Restart Now 3. Navigate to http://localhost:5004 |
Portal loads on :5004 | Browser | SKIP | Frontend port change disconnects browser |
| TC-284 | Port Switch (UI) | Invalid Port | Port validation | 1. Edit BACKEND_PORT to 99 2. Save |
Validation rejects with error (min 1024) | Browser | PASS | Verified |
| TC-285 | Port Switch (UI) | Create Module (switched) | Create module after port switch | 1. After port switch to 5003/5004 2. Modules → Create new module 3. Save |
Module appears in list | Browser | PASS | Verified |
| TC-286 | Port Switch (UI) | Create Workflow (switched) | Create workflow after port switch | 1. Workflows → Create new workflow with Kt.prt1 step 2. Save |
Workflow appears in list | Browser | PASS | Verified |
| TC-287 | Port Switch (UI) | Run Workflow UI (switched) | Run workflow from UI after port switch | 1. Click Run on the new workflow 2. Check run detail |
Run completes with Success status | Browser | PASS | Verified |
| TC-288 | Port Switch (UI) | Run Workflow API (switched) | Run workflow via API after port switch | 1. curl -X POST http://localhost:5003/flow/{name}/run | 200 response with status=True | Console | PASS | Verified |
| TC-289 | Port Switch (UI) | Page Navigation (switched) | Navigate all pages after port switch | 1. Visit Dashboard, Workflows, Runlog, Syslog, Audit, System | All pages load correctly on new ports | Browser | PASS | Verified |
| TC-290 | Port Switch (UI) | Revert Backend Port | Revert backend port | 1. Edit BACKEND_PORT back to 5001 2. Save → Restart Now 3. curl http://localhost:5001/flow |
Flask API responds on :5001 | Browser | PASS | Verified |
| TC-291 | Port Switch (UI) | Revert Frontend Port | Revert frontend port | 1. Edit FRONTEND_PORT back to 5002 2. Save → Restart Now 3. Navigate to http://localhost:5002 |
Portal loads on :5002 | Browser | SKIP | Frontend port change disconnects browser |
| TC-292 | Port Switch (UI) | Create Module (reverted) | Create module after revert | 1. Modules → Create new module 2. Save |
Module appears in list | Browser | PASS | Verified |
| TC-293 | Port Switch (UI) | Create Workflow (reverted) | Create workflow after revert | 1. Workflows → Create new workflow 2. Save |
Workflow appears in list | Browser | PASS | Verified |
| TC-294 | Port Switch (UI) | Run Workflow UI (reverted) | Run workflow from UI after revert | 1. Click Run on the new workflow 2. Check run detail |
Run completes with Success status | Browser | PASS | Verified |
| TC-295 | Port Switch (UI) | Run Workflow API (reverted) | Run workflow via API after revert | 1. curl -X POST http://localhost:5001/flow/{name}/run | 200 response with status=True | Console | PASS | Verified |
| TC-296 | Port Switch (Console) | Manual Backend Port | Manual backend port change | 1. Edit etc/service.conf BACKEND_PORT=5003 2. Edit etc/global.json api.port=5003 3. Restart backend 4. curl http://localhost:5003/flow |
Flask responds on :5003 | Console | PASS | Verified |
| TC-297 | Port Switch (Console) | Manual Frontend Port | Manual frontend port change | 1. Edit etc/service.conf FRONTEND_PORT=5004 2. Edit etc/global.json web.port=5004 3. Restart frontend 4. Navigate to http://localhost:5004 |
Portal loads on :5004 | Both | SKIP | Frontend port change disconnects browser |
| TC-298 | Port Switch (Console) | Create Workflow (manual switch) | Create workflow via API after manual switch | 1. curl -X POST http://localhost:5003/flow with workflow JSON | 200 response; workflow created | Console | PASS | Verified |
| TC-299 | Port Switch (Console) | Run Workflow (manual switch) | Run workflow via API after manual switch | 1. curl -X POST http://localhost:5003/flow/{name}/run | 200 response with status=True | Console | PASS | Verified |
| TC-300 | Port Switch (Console) | Manual Revert Backend | Revert backend port manually | 1. Edit etc/service.conf BACKEND_PORT=5001 2. Edit etc/global.json api.port=5001 3. Restart backend 4. curl http://localhost:5001/flow |
Flask responds on :5001 | Console | PASS | Verified |
| TC-301 | Port Switch (Console) | Manual Revert Frontend | Revert frontend port manually | 1. Edit etc/service.conf FRONTEND_PORT=5002 2. Edit etc/global.json web.port=5002 3. Restart frontend 4. Navigate to http://localhost:5002 |
Portal loads on :5002 | Both | SKIP | Frontend port change disconnects browser |
| TC-302 | Port Switch (Console) | Config Mismatch | Config desync detection | 1. Edit only etc/service.conf BACKEND_PORT=5003 (NOT global.json) 2. Restart backend 3. Try to run workflow from UI |
Django fails to connect to Flask (wrong port in global.json) | Both | PASS | Verified |
| TC-303 | Port Switch (UI) | Services Page Display | global.json card display | 1. Navigate to System → Services 2. Check global.json card 3. Click eye button on db.password 4. Enter user password |
All config shown; db.password masked as ****; revealed after password verification | Browser | PASS | Verified |
| TC-304 | Modules | mysql.MySQL | MySQL.cluster_connect — timeout parameter | 1. Step: mod=mysql.MySQL, method=cluster_connect, params={"hosts": [...], "timeout": 3000} 2. Run workflow |
status=True; connection timeout set to 3000ms per node | Both | PASS | Verified |
| TC-305 | Modules | mysql.MySQL | MySQL.cluster_connect — retry_count parameter | 1. Step: mod=mysql.MySQL, method=cluster_connect, params={"hosts": [...], "retry_count": 2} 2. Run workflow |
status=True; failover retries up to 2 rounds through all nodes | Both | PASS | Verified |
| TC-306 | Modules | mysql.MySQL | MySQL.cluster_connect — retry_delay parameter | 1. Step: mod=mysql.MySQL, method=cluster_connect, params={"hosts": [...], "retry_delay": 2.0} 2. Run workflow |
status=True; 2 second delay between retry rounds | Both | PASS | Verified |
| TC-307 | Modules | mongodb.MongoDB | MongoDB.cluster_connect — timeout parameter | 1. Step: mod=mongodb.MongoDB, method=cluster_connect, params={"hosts": [...], "timeout": 3000} 2. Run workflow |
status=True; serverSelectionTimeoutMS set to 3000 per node | Both | PASS | Verified |
| TC-308 | Modules | mongodb.MongoDB | MongoDB.cluster_connect — retry_count parameter | 1. Step: mod=mongodb.MongoDB, method=cluster_connect, params={"hosts": [...], "retry_count": 2} 2. Run workflow |
status=True; failover retries up to 2 rounds through all nodes | Both | PASS | Verified |
| TC-309 | Modules | mongodb.MongoDB | MongoDB.cluster_connect — retry_delay parameter | 1. Step: mod=mongodb.MongoDB, method=cluster_connect, params={"hosts": [...], "retry_delay": 2.0} 2. Run workflow |
status=True; 2 second delay between retry rounds | Both | PASS | Verified |
| TC-310 | System | SSH Key | Upload SSH key via Services page | 1. Navigate to System → Services 2. Click Upload Key 3. Select valid SSH private key file 4. Verify key info displayed |
Key uploaded; Type, Fingerprint, Path shown; file at etc/ssh/default.key with 0o600 | Browser | PASS | Verified |
| TC-311 | System | SSH Key | Delete SSH key | 1. Services page → click Delete on SSH Key card 2. Confirm deletion |
Key removed; empty state message shown; file deleted from disk | Browser | PASS | Verified |
| TC-312 | System | SSH Key | Replace existing SSH key | 1. Upload key A 2. Click Replace Key 3. Upload key B |
Key B replaces A; new fingerprint shown | Browser | PASS | Verified |
| TC-313 | System | SSH Key | Upload invalid file as SSH key | 1. Upload a non-SSH file (e.g. .txt) | Error message: "Invalid SSH private key file." | Browser | PASS | Verified |
| TC-314 | System | SSH Key | @sys.ssh_key variable badge | 1. Services page → check SSH Key card header | Badge shows "@sys.ssh_key"; click copies to clipboard | Browser | PASS | Verified |
| TC-315 | System | @sys. Variables | Resolve @sys.ssh_key in workflow | 1. Create workflow with ssh_connect step using key_file=@sys.ssh_key 2. Run via API |
@sys.ssh_key resolves to uploaded key path (etc/ssh/default.key) | API | PASS | Verified |
| TC-316 | System | @sys. Variables | Permission denied for @sys. without permission | 1. Create user without sys_variables.use permission 2. Run workflow with @sys.ssh_key as that user |
PermissionError: user does not have permission to use system variables | API | PASS | Verified |
| TC-317 | Accounts | Permissions | sys_variables.use in Role edit | 1. Navigate to Roles → Edit admin role 2. Check SYS_VARIABLES section |
SYS_VARIABLES section shows "use" checkbox; checked for admin | Browser | PASS | Verified |
| TC-318 | Accounts | Permissions | sys_variables.use in Group edit | 1. Navigate to Groups → Edit admin group 2. Check SYS_VARIABLES section |
SYS_VARIABLES section shows "use" checkbox | Browser | PASS | Verified |
| TC-319 | Workflows | Error Page | Deleted workflow shows friendly page | 1. Delete a workflow 2. Navigate to /workflows/{deleted}/runs/ |
Friendly "Workflow Not Found" page with Back to Workflows button | Browser | PASS | Verified |
| TC-320 | Workflows | Error Page | Nonexistent workflow shows friendly page | 1. Navigate to /workflows/nonexistent_name/edit/ | Same friendly "Workflow Not Found" page | Browser | PASS | Verified |
| TC-321 | Workflows | Error Page | All workflow sub-pages handle missing flow | 1. Try /runs/, /edit/, /run/1/, /versions/ for deleted workflow | All return friendly error page, not raw 404 | Browser | PASS | Verified |
| TC-322 | System | Security | Security tab loads with SSL Certs sub-tab | 1. System → Security tab | Security tab active; SSL Certs sub-tab shown by default; existing cert management works | Browser | PASS | Verified |
| TC-323 | System | Security | Password Policy sub-tab renders | 1. System → Security → Password Policy sub-tab | Form shows min length, 4 complexity checkboxes, expiry days; Current Policy summary card on right | Browser | PASS | Verified |
| TC-324 | System | Security | Save password policy | 1. Set min length=12, check all complexity, expiry=30 2. Click Save |
Toast "Password policy updated."; Current Policy card updates with green badges | Browser | PASS | Verified |
| TC-325 | System | Security | Dynamic help text on Create User | 1. Set strict policy → navigate to Create User | Password help text shows "Minimum 12 characters. Must include: uppercase letter, lowercase letter, digit, special character." | Browser | PASS | Verified |
| TC-326 | System | Security | Weak password rejected (too short) | 1. Create User with password "weak" | Error: "Password must be at least 12 characters." | Browser | PASS | Verified |
| TC-327 | System | Security | Complexity validation enforced | 1. Create User with password "abcdefghijkl" (lowercase only, 12 chars) | Error: "Password must contain at least one uppercase letter." | Browser | PASS | Verified |
| TC-328 | System | Security | Compliant password accepted | 1. Create User with password "StrongPass123!" | User created successfully; redirected to user list | Browser | PASS | Verified |
| TC-329 | System | Security | Dynamic help text on Change Password | 1. Navigate to /accounts/change-password/ | Help text shows current policy requirements | Browser | PASS | Verified |
| TC-330 | System | Security | Password expiry forces redirect | 1. Set expiry=30 days 2. Backdate admin password_changed_at to 31 days ago 3. Navigate to Dashboard |
Redirected to /accounts/change-password/; cannot access any other page | Browser | PASS | Verified |
| TC-331 | System | Security | Expiry blocks all navigation | 1. With expired password, navigate to /accounts/users/, /system/security/ | All pages redirect to /accounts/change-password/ | Browser | PASS | Verified |
| TC-332 | System | Security | Password change releases expiry lock | 1. Change password with compliant password on expiry page | Redirected to Dashboard; normal navigation resumes | Browser | PASS | Verified |
| TC-333 | System | Security | Default policy is permissive | 1. Reset policy to defaults (min 8, no complexity, expiry=0) 2. Create user with "password" |
No complexity errors (only min length 8 enforced); no expiry redirect | Browser | PASS | Verified |
| TC-334 | System | Security | UserProfile seeded for existing users | 1. Check UserProfile records after migration | All existing users have UserProfile with password_changed_at = now() | Console | PASS | Verified |
| TC-335 | System | Security | SSL Certs tab renamed from old URLs | 1. Navigate to /system/security/ (old: /system/ssl/) | Security tab loads; all 9 tab-bar templates show "Security" not "SSL Certs" | Browser | PASS | Verified |