Compare commits

...

235 Commits

Author SHA1 Message Date
Faruk AYDIN
40636119fb feat: Move canConnect logic into the database column for AppConfig 2024-03-28 00:49:20 +01:00
Faruk AYDIN
beb3b2cf45 chore: Correct the folder of get auth client mock 2024-03-27 14:04:16 +01:00
Faruk AYDIN
c2375ed3d4 chore: Remove old app auth client routers 2024-03-27 14:01:20 +01:00
Faruk AYDIN
4942cf8dae feat: Implement new admin get app auth client API endpoint 2024-03-27 14:00:16 +01:00
Faruk AYDIN
8f444eafa7 chore: Remove old admin app auth clients API endpoint 2024-03-27 13:44:01 +01:00
Faruk AYDIN
2484a0e631 feat: Implement new admin get auth clients API endpoint 2024-03-27 13:42:41 +01:00
Faruk AYDIN
d3747ad050 fix: Typo for the get auth clients test file 2024-03-27 13:42:18 +01:00
Faruk AYDIN
bb68a75636 feat: Implement new get app auth clients API endpoint 2024-03-26 23:58:35 +01:00
Faruk AYDIN
98131d633e refactor: Remove redundant appConfigId from get auth clients mock 2024-03-26 21:27:38 +01:00
Faruk AYDIN
e8193e0e17 feat: Make appKey column of app auth clients not nullable 2024-03-26 21:23:22 +01:00
Faruk AYDIN
74b7dd8f34 chore: Remove old app auth clients API endpoint 2024-03-26 21:19:44 +01:00
Faruk AYDIN
4f500e2d04 feat: Implement new get auth clients api endpoint 2024-03-26 21:17:58 +01:00
Faruk AYDIN
b53ddca8ce feat: Remove app config relation from app auth clients 2024-03-26 21:00:26 +01:00
Faruk AYDIN
70f30034ab feat: Remove app auth clients relation from app configs 2024-03-26 20:59:33 +01:00
Faruk AYDIN
fcd83909f7 feat: Remove app config id from app auth clients 2024-03-26 20:58:51 +01:00
Faruk AYDIN
eadb472af9 feat: Migrate app config id to app key 2024-03-26 20:56:50 +01:00
Faruk AYDIN
600316577e feat: Add appKey to app auth clients 2024-03-26 20:49:53 +01:00
Ali BARIN
9aa48c20e4 Merge pull request #1764 from automatisch/AUT-872
refactor: rewrite useDynamicFields with RQ
2024-03-26 16:58:43 +01:00
Rıdvan Akca
1b5d3beeca refactor: rewrite useDynamicFields with RQ 2024-03-26 18:50:36 +03:00
Ömer Faruk Aydın
00115d313e Merge pull request #1769 from automatisch/rest-logger
refactor: Use additional logger line only for graphQL
2024-03-26 16:25:12 +01:00
Faruk AYDIN
190f1a205f refactor: Use additional logger line only for graphQL 2024-03-26 15:39:02 +01:00
Ali BARIN
8ab6f0c3fe Merge pull request #1767 from automatisch/fix-trial-badge
fix: show trial status badge if user has trial
2024-03-26 13:52:40 +01:00
Rıdvan Akca
13eea263c0 fix: show trial status badge if user has trial 2024-03-26 15:45:26 +03:00
Ömer Faruk Aydın
b52b40962e Merge pull request #1768 from automatisch/rest-create-access-token
feat: Implement create access token API endpoint
2024-03-26 13:31:18 +01:00
Faruk AYDIN
7d1fa2e40c feat: Implement create access token API endpoint 2024-03-26 13:14:33 +01:00
Faruk AYDIN
93b2098829 refactor: Extract token generation logic to User model 2024-03-26 13:14:10 +01:00
Faruk AYDIN
a2acdc6b12 feat: Add draft version of renderError to renderer helper 2024-03-26 13:13:37 +01:00
Ali BARIN
38b2c1e30f Merge pull request #1765 from automatisch/refactor-get-app-config
refactor: Move app config endpoint to apps namespace
2024-03-26 12:38:42 +01:00
Rıdvan Akca
e07f579f3c refactor: update endpoint in useAppConfig 2024-03-25 19:12:45 +03:00
Faruk AYDIN
df3297b6ca refactor: Move app config endpoint to apps namespace 2024-03-25 17:01:16 +01:00
Ömer Faruk Aydın
fc4eeed764 Merge pull request #1760 from automatisch/rest-app-auth-clients
feat: Implement get app auth clients API endpoint
2024-03-22 15:20:00 +01:00
Faruk AYDIN
3596d13be1 feat: Implement get app auth clients API endpoint 2024-03-22 15:05:37 +01:00
Ömer Faruk Aydın
104d49ea1c Merge pull request #1759 from automatisch/rest-admin-app-auth-clients
feat: Implement admin get app auth clients API endpoint
2024-03-22 15:05:30 +01:00
Faruk AYDIN
7057317446 refactor: Use ee extension for admin app auth clients 2024-03-22 14:48:46 +01:00
Faruk AYDIN
280575df88 refactor: Move app auth client mock to correct folder 2024-03-22 14:46:43 +01:00
Faruk AYDIN
d2cb434b7b refactor: Move admin get app auth client mock to correct folder 2024-03-22 14:44:35 +01:00
Faruk AYDIN
2ecb802a2e feat: Implement admin get app auth clients API endpoint 2024-03-22 14:42:48 +01:00
Ali BARIN
46e706c415 Merge pull request #1756 from automatisch/AUT-687
refactor: rewrite useConfig with RQ
2024-03-22 10:24:52 +01:00
kasia.oczkowska
3a57349d8a refactor: rewrite useConfig with RQ 2024-03-22 09:14:29 +00:00
Ali BARIN
754c3269ec Merge pull request #1739 from automatisch/dependabot/npm_and_yarn/follow-redirects-1.15.6
chore(deps): bump follow-redirects from 1.15.3 to 1.15.6
2024-03-22 10:07:35 +01:00
Ömer Faruk Aydın
a079842408 Merge pull request #1757 from automatisch/create-dynamic-fields-endpoint
feat: Implement create dynamic fields API endpoint
2024-03-22 03:05:11 +01:00
Faruk AYDIN
7664b58553 feat: Implement create dynamic fields API endpoint 2024-03-22 02:55:23 +01:00
Ali BARIN
de77488f7e Merge pull request #1754 from automatisch/AUT-697
refactor: rewrite useNotifications with RQ
2024-03-21 15:05:35 +01:00
kasia.oczkowska
d808afd21b refactor: rewrite useNotifications with RQ 2024-03-21 14:56:54 +01:00
Ömer Faruk Aydın
b68aff76a1 Merge pull request #1755 from automatisch/get-previous-steps
feat: Implement get previous steps rest API endpoint
2024-03-21 14:56:16 +01:00
Faruk AYDIN
6da7fe158f feat: Implement get previous steps rest API endpoint 2024-03-21 14:40:18 +01:00
Faruk AYDIN
4dbc7fdc7d feat: Extend step serializers to include execution steps 2024-03-21 14:39:22 +01:00
Ali BARIN
ad1e1f7eca Merge pull request #1750 from automatisch/make-respond-with-flexible
feat(webhooks/respond-with): accept custom headers
2024-03-21 12:03:51 +01:00
Ömer Faruk Aydın
9c3f7a3823 Merge pull request #1753 from automatisch/use-objection-for-factories
refactor: Use objection instead of knex for factories
2024-03-20 17:31:37 +01:00
Faruk AYDIN
86f4cb7701 refactor: Use objection instead of knex for factories 2024-03-20 17:24:44 +01:00
Ali BARIN
359a90245d Merge pull request #1734 from automatisch/AUT-845
refactor: rewrite useUsers with RQ
2024-03-20 16:08:07 +01:00
kasia.oczkowska
d8d7d86359 feat: invalidate queries on user related actions 2024-03-20 15:00:54 +00:00
Ali BARIN
7189b629c0 Merge pull request #1746 from automatisch/AUT-856
refactor: rewrite useFlows as useConnectionFlows and useAppFlows with RQ
2024-03-20 15:13:36 +01:00
kasia.oczkowska
55c9b5566c feat: rename hooks 2024-03-20 14:23:33 +01:00
kasia.oczkowska
ab671ccbf7 refactor: rewrite useUsers with RQ 2024-03-20 13:14:25 +00:00
Rıdvan Akca
316bda8c3f refactor: rewrite useFlows as useConnectionFlows and useAppFlows with RQ 2024-03-20 11:45:26 +03:00
Ömer Faruk Aydın
76f77e8a4c Merge pull request #1752 from automatisch/fix-step-factory
fix: Adjust step factory to use objection instead of knex
2024-03-20 02:15:13 +01:00
Faruk AYDIN
4a99d5eab7 fix: Adjust step factory to use objection instead of knex 2024-03-20 02:03:31 +01:00
Ali BARIN
473d287c6d feat(webhooks/respond-with): accept custom headers 2024-03-19 19:21:20 +00:00
Ömer Faruk Aydın
bddd9896e4 Merge pull request #1749 from automatisch/fix/docs-change
fix: Do not explicitly define github and context for CI actions
2024-03-19 20:08:08 +01:00
Faruk AYDIN
95eb115965 fix: Do not explicitly define github and context for CI actions 2024-03-19 17:49:05 +01:00
Ömer Faruk Aydın
9a63b213b0 Merge pull request #1747 from automatisch/docs-changes
feat: Add docs change CI workflow to detect changes
2024-03-19 17:37:56 +01:00
Faruk AYDIN
90b00d88f1 feat: Add docs change CI workflow to detect changes 2024-03-19 17:32:36 +01:00
Rıdvan Akca
ec87c7f21c feat: introduce useLazyFlows with RQ 2024-03-19 16:56:53 +03:00
Ali BARIN
452f45cac6 Merge pull request #1744 from QAComet/QAComet/manage-users-roles-update
test: fix flakiness in manage roles and users tests
2024-03-19 12:57:28 +01:00
Ali BARIN
c644b3d384 Merge pull request #1745 from automatisch/fix-unused-queries
fix: pass all params to enabled of RQ if necessary
2024-03-19 11:25:32 +01:00
Rıdvan Akca
68160c20e8 fix: pass all params to enabled of RQ if necessary 2024-03-19 13:16:08 +03:00
QAComet
ad144206dd fix(e2e): update expect to web-first assertion, wait for edit form to load 2024-03-18 12:22:56 -06:00
Ali BARIN
f3d20ab769 Merge pull request #1738 from automatisch/AUT-849
refactor: rewrite useAdminSamlAuthProviderRoleMappings with RQ
2024-03-18 15:19:33 +01:00
Ömer Faruk Aydın
9767ca7116 Merge pull request #1741 from automatisch/guard-actions-triggers
fix: Guard actions and triggers properties of apps
2024-03-17 13:26:59 +01:00
Faruk AYDIN
73a5b8553f fix: Guard actions and triggers properties of apps 2024-03-17 12:47:18 +01:00
dependabot[bot]
5c684cd499 chore(deps): bump follow-redirects from 1.15.3 to 1.15.6
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.3 to 1.15.6.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.3...v1.15.6)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-16 20:12:03 +00:00
Ali BARIN
479f3e3172 Merge pull request #1737 from automatisch/fix-admin-applications
fix: control variables parameter in useApps hook
2024-03-15 17:26:27 +01:00
Rıdvan Akca
6a1350fd00 refactor: rewrite useAdminSamlAuthProviderRoleMappings with RQ 2024-03-15 19:03:54 +03:00
Rıdvan Akca
563784da1c fix: control variables parameter in useApps hook 2024-03-15 18:57:43 +03:00
Ali BARIN
347f0ed3a5 Merge pull request #1736 from automatisch/fix-loading-and-undefined-requests
fix: convert loading to isLoading in useUser hook
2024-03-15 16:50:07 +01:00
Rıdvan Akca
1db9f5b2c2 fix: convert loading to isLoading in useUser hook 2024-03-15 18:42:07 +03:00
Ömer Faruk Aydın
f2a3e26188 Merge pull request #1735 from automatisch/delete-step-fix
fix: Use correct model file to delete step
2024-03-15 16:42:06 +01:00
Faruk AYDIN
1c0897bfb6 fix: Use correct model file to delete step 2024-03-15 16:34:38 +01:00
Ali BARIN
f0793992a6 Merge pull request #1726 from automatisch/AUT-839
refactor: rewrite usePermissionCatalog with RQ
2024-03-15 15:28:33 +01:00
Ali BARIN
393205ba2f Merge pull request #1725 from automatisch/AUT-838
refactor: rewrite useSamlAuthProviders with RQ
2024-03-15 15:28:01 +01:00
Rıdvan Akca
ab49535b6c refactor: rewrite useSamlAuthProviders with RQ 2024-03-15 17:27:48 +03:00
Ali BARIN
8191b48548 Merge pull request #1722 from automatisch/AUT-832
refactor: rewrite useUserTrial with RQ
2024-03-15 15:19:58 +01:00
Rıdvan Akca
925dd06432 refactor: rewrite useUserTrial with RQ 2024-03-15 16:56:31 +03:00
Rıdvan Akca
0d525e056a refactor: rewrite usePermissionCatalog with RQ 2024-03-15 16:51:22 +03:00
Ali BARIN
3aa86eebf2 Merge pull request #1733 from automatisch/AUT-844
refactor: rewrite useUser with RQ
2024-03-15 14:03:20 +01:00
Ali BARIN
d7a93abec0 Merge pull request #1717 from automatisch/AUT-829
refactor: rewrite useCurrentUser with RQ
2024-03-15 14:02:09 +01:00
Ali BARIN
6448d28a18 Merge pull request #1732 from automatisch/AUT-842
refactor: rewrite useSamlAuthProvider with RQ
2024-03-15 13:59:22 +01:00
kasia.oczkowska
449976483c refactor: rewrite useUser with RQ 2024-03-15 12:55:23 +00:00
Ali BARIN
e468b762ef Merge pull request #1723 from automatisch/remove-unused-payloads
refactor: remove unused payloads from RQ
2024-03-15 13:53:45 +01:00
Rıdvan Akca
b578e73cc4 refactor: rewrite useSamlAuthProvider with RQ 2024-03-15 15:52:39 +03:00
Ali BARIN
e381f95b95 Merge pull request #1719 from automatisch/AUT-831
refactor: rewrite useSubscription with RQ
2024-03-15 13:23:06 +01:00
Ali BARIN
5685afae63 Merge pull request #1718 from automatisch/AUT-830
refactor: rewrite useInvoices with RQ
2024-03-15 13:22:21 +01:00
Ali BARIN
53a473422b Merge pull request #1731 from automatisch/AUT-841
refactor: rewrite useRole with RQ
2024-03-15 13:19:41 +01:00
Rıdvan Akca
3f9f17f584 refactor: rewrite useRole with RQ 2024-03-15 15:09:36 +03:00
QAComet
2c410bf318 docs(e2e-tests): write basic README for setting up the tests (#1730) 2024-03-15 13:08:04 +01:00
Ali BARIN
40934a2c77 Merge pull request #1727 from automatisch/AUT-840
refactor: rewrite useRoles with RQ
2024-03-15 13:03:44 +01:00
Rıdvan Akca
ecc9379d7e refactor: remove get-roles GQL query 2024-03-15 14:51:18 +03:00
Ömer Faruk Aydın
f8d27342dc Merge pull request #1729 from automatisch/rest-plan-and-usage
feat: Implement plan and usage API endpoint
2024-03-15 00:56:42 +01:00
Faruk AYDIN
17bd2bf2ba feat: Implement plan and usage API endpoint 2024-03-15 00:49:15 +01:00
Ömer Faruk Aydın
d984a3f275 Merge pull request #1728 from automatisch/rest-saml-auth-providers
feat: Implement get role mappings API endpoint
2024-03-14 16:01:34 +01:00
Faruk AYDIN
64049bd546 feat: Implement get role mappings API endpoint 2024-03-14 15:49:14 +01:00
Rıdvan Akca
9218091c33 refactor: rewrite useRoles with RQ 2024-03-14 16:33:08 +03:00
Rıdvan Akca
75df7d6413 refactor: rewrite parameters of RQ hooks 2024-03-14 13:54:51 +03:00
Ömer Faruk Aydın
29341f81e1 Merge pull request #1724 from automatisch/rest-saml-auth-providers
feat: Implement saml auth providers API endpoint
2024-03-14 10:36:33 +01:00
Faruk AYDIN
68c5a3dca7 feat: Implement saml auth providers API endpoint 2024-03-14 10:27:24 +01:00
Ömer Faruk Aydın
b1e2e370c8 Merge pull request #1720 from automatisch/rest-get-step-connection
feat: Implement get step connection API endpoint
2024-03-14 10:27:18 +01:00
Rıdvan Akca
ba9d3afc88 refactor: remove unused payloads from RQ 2024-03-14 12:25:26 +03:00
Faruk AYDIN
9a0434be32 feat: Implement get step connection API endpoint 2024-03-13 19:51:36 +01:00
Faruk AYDIN
d6923a2ff0 fix: Use insertAndFetch to get record with after find modifications 2024-03-13 19:51:36 +01:00
Faruk AYDIN
8f7f6dc19e feat: Add connection serializer 2024-03-13 19:51:36 +01:00
Rıdvan Akca
70b8817643 refactor: rewrite useSubscription with RQ 2024-03-13 18:03:29 +03:00
Rıdvan Akca
87c25cbbfe refactor: rewrite useInvoices with RQ 2024-03-13 18:01:32 +03:00
Rıdvan Akca
082e905014 refactor: remove get-current-user GQL query 2024-03-13 17:58:37 +03:00
Ali BARIN
e3e598b208 Merge pull request #1721 from automatisch/fix-public-layout
fix: introduce a fix for the content container height in the PublicLayout component
2024-03-13 15:46:21 +01:00
kasia.oczkowska
6cf92d4ea6 fix: introduce a fix for the content container height in the PublicLayout component 2024-03-13 14:28:16 +00:00
Ali BARIN
89ad685f3a Merge pull request #1716 from automatisch/AUT-828
refactor: rewrite useVersion and healthcheck with RQ
2024-03-13 11:02:38 +01:00
Rıdvan Akca
1e868dc802 refactor: rewrite useCurrentUser with RQ 2024-03-13 12:33:31 +03:00
Rıdvan Akca
58fcfd9a34 refactor: rewrite useVersion and healthcheck with RQ 2024-03-13 11:39:52 +03:00
Ali BARIN
c849afbc11 Merge pull request #1715 from automatisch/AUT-698
refactor: rewrite usePaddleInfo and usePaymentPlans with RQ
2024-03-13 09:20:39 +01:00
Rıdvan Akca
2ebe71ddd0 refactor: rewrite usePaymentPlans with RQ 2024-03-13 11:05:21 +03:00
Rıdvan Akca
7a8e8c1f3e refactor: rewrite usePaddleInfo with RQ 2024-03-12 17:21:44 +03:00
Ali BARIN
5e897ad1c2 Merge pull request #1460 from automatisch/AUT-493
feat: introduce asterisk for all the required fields
2024-03-12 14:23:53 +01:00
Ali BARIN
ce07907f85 Merge pull request #1700 from automatisch/AUT-782
feat: make flow editor topbar sticky
2024-03-12 14:21:37 +01:00
Ali BARIN
1e38aa7b53 Merge pull request #1714 from automatisch/AUT-693
refactor: rewrite get executions using useExecutions with RQ
2024-03-12 14:17:15 +01:00
Rıdvan Akca
3bc0c23e5a refactor: rewrite get executions using useExecutions with RQ 2024-03-12 15:45:04 +03:00
Ali BARIN
f07b6d105a Merge pull request #1712 from automatisch/AUT-692
refactor: rewrite get execution using useExecution with RQ
2024-03-12 12:34:48 +01:00
Rıdvan Akca
46491269e3 refactor: rewrite get execution using useExecution with RQ 2024-03-12 14:24:23 +03:00
Ali BARIN
8d9c43af6a Merge pull request #1711 from automatisch/AUT-691
refactor: rewrite get execution steps using useExecutionSteps with RQ
2024-03-12 12:23:19 +01:00
Rıdvan Akca
c3568354aa feat: use useInfinityQuery instead of useQuery in useExecutionSteps 2024-03-12 13:55:58 +03:00
Rıdvan Akca
e68696ccd4 refactor: rewrite get execution steps using useExecutionSteps with RQ 2024-03-12 12:12:09 +03:00
Ali BARIN
35d8b2e790 Merge pull request #1713 from automatisch/fix-auth-client-auth-fields
fix: use useAppAuth for getting auth fields
2024-03-11 16:18:03 +01:00
Rıdvan Akca
1f83573206 fix: use useAppAuth for getting auth fields 2024-03-11 18:07:15 +03:00
Ali BARIN
2887e76514 Merge pull request #1702 from automatisch/AUT-683
refactor: rewrite useAppConfig with RQ
2024-03-11 15:43:04 +01:00
Rıdvan Akca
63b9943203 refactor: rewrite useAppConfig with RQ 2024-03-11 17:32:55 +03:00
Ali BARIN
bd5aedd83f Merge pull request #1696 from automatisch/AUT-685
refactor: implement rest API endpoint for get app and get apps
2024-03-11 15:26:54 +01:00
Rıdvan Akca
c9ff6d7bb9 fix: pass enabled to useTriggers and useActions hooks 2024-03-11 17:17:09 +03:00
Rıdvan Akca
5835def5d0 refactor: move abort controller into useLazyApps hook 2024-03-11 16:16:12 +03:00
Ali BARIN
6a2694ce3b Merge pull request #1644 from automatisch/sync-webhook
feat(webhook/catch-raw-webhook): add sync support and custom response
2024-03-11 14:05:35 +01:00
Ali BARIN
65ae7bce79 chore(webhook/catch-raw-webhook): update description 2024-03-11 13:55:43 +01:00
Ali BARIN
57ce8da0ee feat(webhook): add respondWith action 2024-03-11 13:54:00 +01:00
Ali BARIN
7484bf7403 feat(webhook/catch-raw-webhook): add sync support 2024-03-11 13:54:00 +01:00
Ömer Faruk Aydın
f6b2312c49 Merge pull request #1710 from automatisch/rest-get-connection-flows
feat: Implement get connection flows API endpoint
2024-03-11 12:50:53 +01:00
Faruk AYDIN
6027cb7cb0 feat: Implement get connection flows API endpoint 2024-03-10 16:11:07 +01:00
Ömer Faruk Aydın
c1740aae6c Merge pull request #1708 from automatisch/rest-app-flows
feat: Implement API endpoint to get flows of the specified app
2024-03-09 15:16:01 +01:00
Faruk AYDIN
22ce29e86c feat: Implement API endpoint to get flows of the specified app 2024-03-09 15:09:12 +01:00
Ömer Faruk Aydın
251d1b5b2e Merge pull request #1707 from automatisch/rest-get-flows
feat: Implement get flows API endpoint
2024-03-08 23:21:34 +01:00
Faruk AYDIN
ea64708c69 feat: Implement get flows API endpoint 2024-03-08 23:11:42 +01:00
Ömer Faruk Aydın
209ec27a29 Merge pull request #1706 from automatisch/rest-config-api
feat: Implement automatisch config API endpoint
2024-03-08 22:49:54 +01:00
Faruk AYDIN
ceee495525 feat: Implement automatisch config API endpoint 2024-03-08 22:42:24 +01:00
Ömer Faruk Aydın
751a2347aa Merge pull request #1703 from automatisch/rest-get-subscription
feat: Implement get subscription API endpoint
2024-03-08 14:46:13 +01:00
Faruk AYDIN
efd96d5fdf feat: Implement get subscription API endpoint 2024-03-08 14:33:47 +01:00
Rıdvan Akca
2ee5af8bfb feat: add useAppAuth with RQ 2024-03-07 16:53:22 +03:00
Rıdvan Akca
a4c0edf493 refactor: remove getApp query from web 2024-03-07 16:53:22 +03:00
Rıdvan Akca
28c8be97b6 feat: add useLazyApps hook 2024-03-07 16:53:22 +03:00
Rıdvan Akca
f320a44d45 refactor: rewrite get-app queries with RQ 2024-03-07 16:53:22 +03:00
Rıdvan Akca
c0cc6cc176 refactor: rewrite get-apps queries with RQ 2024-03-07 16:53:22 +03:00
Rıdvan Akca
be62c09d06 refactor: rewrite useApp with RQ 2024-03-07 16:53:22 +03:00
Rıdvan Akca
5fe3546d2a feat: add useTriggerSubsteps and useActionSubsteps with RQ 2024-03-07 16:53:22 +03:00
Rıdvan Akca
c4b2ea125c feat: add useActions with RQ 2024-03-07 16:53:22 +03:00
Rıdvan Akca
3301b038fe feat: add useTriggers with RQ 2024-03-07 16:53:22 +03:00
Rıdvan Akca
6a58d1e3da fix(useAdminAppAuthClient): pass signal 2024-03-07 16:53:22 +03:00
Rıdvan Akca
e5670d820d refactor: rewrite useApps with RQ 2024-03-07 16:53:22 +03:00
Ömer Faruk Aydın
1870aead73 Merge pull request #1701 from automatisch/rest-get-app-config
feat: Implement get app config API endpoint
2024-03-07 14:26:46 +01:00
Faruk AYDIN
95db6cca2c feat: Implement get app config API endpoint 2024-03-07 14:18:21 +01:00
Faruk AYDIN
79a792ac62 feat: Add app config serializer 2024-03-07 14:11:00 +01:00
Kasia
6406f9eb86 feat: make flow editor topbar sticky 2024-03-07 12:20:22 +01:00
Kasia
8f8ec496f8 feat: introduce asterisk for all the required fields 2024-03-07 10:15:53 +01:00
Ali BARIN
3d6847a3a2 Merge pull request #1558 from automatisch/AUT-606
fix: remove unnecessery styling causing the problem with scrolling Edit/Create role pages
2024-03-07 00:38:20 +01:00
Ali BARIN
613abaec1b Merge pull request #1550 from automatisch/AUT-563
feat: render AdminSettingsLayout once for all child routes
2024-03-06 18:56:59 +01:00
Ömer Faruk Aydın
b51ae9ac38 Merge pull request #1698 from automatisch/rest-get-execution-steps
feat: Implement get execution steps API endpoint
2024-03-06 17:30:33 +01:00
Faruk AYDIN
c4fd03542b feat: Implement get execution steps API endpoint 2024-03-06 17:23:56 +01:00
Faruk AYDIN
e40d6c5ef0 feat: Implement execution step serializer 2024-03-06 17:23:56 +01:00
kasia.oczkowska
9f7dee3baa test: introuce test fix, select role by exact name 2024-03-06 16:28:17 +01:00
kasia.oczkowska
d3da62c04a feat: render AdminSettingsLayout once for all child routes 2024-03-06 16:28:17 +01:00
kasia.oczkowska
aa7bb3f8c9 fix: remove unnecessery styling causing the problem 2024-03-06 15:55:03 +01:00
Ali BARIN
3a9dfe339a Merge pull request #1689 from automatisch/fix-auth-in-rq
fix: initialize auth in RQ client
2024-03-06 15:52:17 +01:00
Ali BARIN
08830003a3 Merge pull request #1560 from automatisch/AUT-619
fix: add optional chaining to data.getTrialStatus check
2024-03-06 15:34:04 +01:00
kasia.oczkowska
efeeb6cb02 fix: add optional chaining to data.getTrialStatus check and introduce minor code improvements 2024-03-06 15:12:02 +01:00
Ömer Faruk Aydın
ac59ce2deb Merge pull request #1697 from automatisch/rest-get-executions
feat: Implement get executions API endpoint
2024-03-06 15:08:35 +01:00
Faruk AYDIN
3ff89a03ac feat: Implement get executions API endpoint 2024-03-06 14:46:19 +01:00
Faruk AYDIN
25e231cd7c feat: Extend execution serializer with status 2024-03-06 14:46:10 +01:00
Ömer Faruk Aydın
f4d8d909b0 Merge pull request #1695 from automatisch/sentry-enhancement
chore: Do not enable sentry for dev and test environments
2024-03-05 15:53:36 +01:00
Faruk AYDIN
35ea18a117 chore: Do not enable sentry for dev and test environments 2024-03-05 15:47:07 +01:00
Ömer Faruk Aydın
ee9433261b Merge pull request #1693 from automatisch/refactor-get-executions-tests
refactor: Add steps to get execution tests
2024-03-04 16:23:09 +01:00
Faruk AYDIN
1cb48c7760 refactor: Add steps to get execution tests 2024-03-04 16:14:11 +01:00
Ömer Faruk Aydın
172bf4bd51 Merge pull request #1692 from automatisch/remove-empty-associations
refactor: Remove empty associations from serializers
2024-03-04 16:14:00 +01:00
Faruk AYDIN
fe1039cfbc test: Add permissions to get current user tests 2024-03-04 16:07:18 +01:00
Faruk AYDIN
a9de79546b refactor: Remove empty associations from serializers 2024-03-04 16:06:50 +01:00
kasia.oczkowska
7afdf43872 feat: introduce propTypes 2024-03-04 15:21:07 +01:00
Rıdvan Akca
bfc7d5d0dd refactor(web): rewrite useAdminAppAuthClient with react-query (#1690) 2024-03-04 13:09:42 +01:00
Ömer Faruk Aydın
690832052a Merge pull request #1691 from automatisch/error-handler
feat: Send rest API errors to Sentry
2024-03-04 13:01:20 +01:00
Faruk AYDIN
f7c1a47d52 feat: Send rest API errors to Sentry 2024-03-04 12:19:53 +01:00
Ali BARIN
930843d065 fix: initialize auth in RQ client 2024-03-04 10:48:55 +00:00
Ömer Faruk Aydın
a9ee609502 Merge pull request #1688 from automatisch/rest-get-execution
feat: Implement get execution API endpoint
2024-03-03 19:06:10 +01:00
Faruk AYDIN
9fd2125923 test: Add tests for execution serializer 2024-03-03 18:58:54 +01:00
Faruk AYDIN
ede8703f9d feat: Implement get execution API endpoint 2024-03-03 18:53:14 +01:00
Faruk AYDIN
6d85623d9b feat: Add execution serializer 2024-03-03 18:52:32 +01:00
Faruk AYDIN
6236ee8f6d feat: Add authorizedExecutions to user model 2024-03-03 18:51:50 +01:00
Ömer Faruk Aydın
92665d80d6 Merge pull request #1687 from automatisch/get-action-substeps
feat: Implement get action substeps API endpoint
2024-03-02 16:30:43 +01:00
Faruk AYDIN
70ae0bc77e feat: Implement get action substeps API endpoint 2024-03-02 16:24:20 +01:00
Faruk AYDIN
e28c757352 feat: Add find action substeps method to App model 2024-03-02 16:24:20 +01:00
Ömer Faruk Aydın
7cdcf7ebab Merge pull request #1686 from automatisch/get-app-actions
feat: Implement get app actions API endpoint
2024-03-02 16:19:57 +01:00
Faruk AYDIN
7c368af5ed feat: Implement get app actions API endpoint 2024-03-02 16:13:13 +01:00
Faruk AYDIN
df2fbbabc6 feat: Implement action serializer 2024-03-02 16:12:51 +01:00
Faruk AYDIN
48141eb199 feat: Add find actions by key method to App model 2024-03-02 16:09:57 +01:00
Ömer Faruk Aydın
343fbb282c Merge pull request #1685 from automatisch/get-trigger-substeps
feat: Implement get trigger substeps API endpoint
2024-03-02 14:44:06 +01:00
Faruk AYDIN
cea9ed056b feat: Implement get trigger substeps API endpoint 2024-03-01 16:35:46 +01:00
Ali BARIN
73bd90c555 refactor(web): utilize REACT_APP_BACKEND_URL for GQL and REST API URLs 2024-03-01 15:14:22 +01:00
Ali BARIN
917de46c45 chore(web): build with API URL 2024-03-01 15:14:22 +01:00
Ali BARIN
b592092923 chore: update building web in e2e tests 2024-03-01 15:14:22 +01:00
Ali BARIN
760bc1c22a fix(useAutomatischInfo): pass signal correctly 2024-03-01 15:14:22 +01:00
Ali BARIN
1f8b81ee78 refactor: remove GetAutomatischInfo GQL query 2024-03-01 15:14:22 +01:00
Ali BARIN
70af7d05e3 refactor: remove AutomatischInfo context 2024-03-01 15:14:22 +01:00
Ali BARIN
14c04ee4ac refactor: rewrite useAutomatischInfo with RQ and REST API 2024-03-01 15:14:22 +01:00
Ali BARIN
83815d3caa feat: introduce react-query and react-query-devtools 2024-03-01 15:14:22 +01:00
Ali BARIN
487c621fa5 chore: update react and react-dom to 18 2024-03-01 15:14:22 +01:00
Ömer Faruk Aydın
304eab801c Merge pull request #1684 from automatisch/rest-get-triggers
feat: Implement get triggers API endpoint
2024-03-01 15:10:31 +01:00
Faruk AYDIN
dfe3add1cc feat: Implement get triggers API endpoint 2024-03-01 15:03:53 +01:00
Faruk AYDIN
a32bf5539e feat: Add find triggers by key method to App model 2024-03-01 15:02:56 +01:00
Faruk AYDIN
e944333e5f feat: Implement trigger serializer 2024-03-01 15:02:21 +01:00
Ömer Faruk Aydın
dad23a52b0 Merge pull request #1683 from automatisch/rest-get-apps
feat: Implement get apps API endpoint
2024-03-01 14:27:59 +01:00
Faruk AYDIN
53606c306d feat: Implement get apps API endpoint 2024-03-01 14:21:41 +01:00
Ömer Faruk Aydın
53b03f8231 Merge pull request #1680 from automatisch/available-apps
docs: Add datastore to available apps
2024-03-01 12:03:02 +01:00
Faruk AYDIN
ac5f6dc024 docs: Add datastore to available apps 2024-03-01 11:50:42 +01:00
Ali BARIN
2c15e0dd32 Merge pull request #1665 from automatisch/ts-removal-in-web
refactor(web): remove typescript
2024-02-29 11:43:07 +01:00
Ömer Faruk Aydın
007334fef0 Merge pull request #1671 from automatisch/helix-integration
feat: Use new API endpoint from Helix
2024-02-29 11:41:19 +01:00
Ali BARIN
b3ae2d2748 refactor(web): remove typescript 2024-02-29 09:38:32 +00:00
Ömer Faruk Aydın
7149c766d0 Merge pull request #1675 from automatisch/get-app-auth
feat: Implement get app auth API endpoint
2024-02-28 16:25:40 +01:00
Faruk AYDIN
5dca0191d2 feat: Implement get app auth API endpoint 2024-02-28 16:16:13 +01:00
Faruk AYDIN
356668a68d feat: Implement auth serializer 2024-02-28 16:16:13 +01:00
Faruk AYDIN
63c9442126 feat: Add find by auth method to App model 2024-02-28 16:16:13 +01:00
Faruk AYDIN
0031d7911d fix: Adjust description of app serializer test 2024-02-28 16:16:13 +01:00
Faruk AYDIN
31c92b43b4 feat: Add optional system prompt variable to Helix app 2024-02-28 14:18:55 +01:00
Faruk AYDIN
2667548041 feat: Introduce optional session ID for helix app 2024-02-28 13:44:04 +01:00
587 changed files with 8400 additions and 8641 deletions

View File

@@ -28,7 +28,7 @@ cd packages/web
rm -rf .env
echo "
PORT=$WEB_PORT
REACT_APP_GRAPHQL_URL=http://localhost:$BACKEND_PORT/graphql
REACT_APP_BACKEND_URL=http://localhost:$BACKEND_PORT
" >> .env
cd $CURRENT_DIR

View File

@@ -1,18 +0,0 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'prettier',
],
overrides: [
{
files: ['**/*.test.ts', '**/test/**/*.ts'],
rules: {
'@typescript-eslint/ban-ts-comment': ['off'],
},
},
],
};

View File

@@ -22,7 +22,7 @@ jobs:
- run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
- run: yarn --frozen-lockfile
- run: yarn lint
- run: cd packages/backend && yarn lint
- run: echo "🍏 This job's status is ${{ job.status }}."
start-backend-server:
runs-on: ubuntu-latest

32
.github/workflows/docs-change.yml vendored Normal file
View File

@@ -0,0 +1,32 @@
name: Automatisch Docs Change
on:
pull_request:
paths:
- 'packages/docs/**'
jobs:
label:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Label PR
uses: actions/github-script@v6
with:
script: |
const { pull_request } = context.payload;
const label = 'documentation-change';
const hasLabel = pull_request.labels.some(({ name }) => name === label);
if (!hasLabel) {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pull_request.number,
labels: [label],
});
console.log(`Label "${label}" added to PR #${pull_request.number}`);
} else {
console.log(`Label "${label}" already exists on PR #${pull_request.number}`);
}

View File

@@ -62,8 +62,9 @@ jobs:
run: yarn && yarn lerna bootstrap
- name: Install Playwright Browsers
run: yarn playwright install --with-deps
- name: Build Automatisch
run: yarn lerna run --scope=@*/{web,cli} build
- name: Build Automatisch web
working-directory: ./packages/web
run: yarn build
env:
# Keep this until we clean up warnings in build processes
CI: false

View File

@@ -6,7 +6,6 @@
"start": "lerna run --stream --parallel --scope=@*/{web,backend} dev",
"start:web": "lerna run --stream --scope=@*/web dev",
"start:backend": "lerna run --stream --scope=@*/backend dev",
"lint": "lerna run --no-bail --stream --parallel --scope=@*/{web,backend} lint",
"build:docs": "cd ./packages/docs && yarn install && yarn build"
},
"workspaces": {
@@ -21,8 +20,6 @@
]
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.9.1",
"@typescript-eslint/parser": "^5.9.1",
"eslint": "^8.13.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",

View File

@@ -11,7 +11,7 @@
"start:worker": "node src/worker.js",
"pretest": "APP_ENV=test node ./test/setup/prepare-test-env.js",
"test": "APP_ENV=test vitest run",
"lint": "eslint . --ignore-path ../../.eslintignore",
"lint": "eslint .",
"db:create": "node ./bin/database/create.js",
"db:seed:user": "node ./bin/database/seed-user.js",
"db:drop": "node ./bin/database/drop.js",
@@ -95,7 +95,6 @@
"url": "https://github.com/automatisch/automatisch/issues"
},
"devDependencies": {
"@typescript-eslint/utils": "^7.0.2",
"nodemon": "^2.0.13",
"supertest": "^6.3.3",
"vitest": "^1.1.3"

View File

@@ -5,19 +5,38 @@ export default defineAction({
key: 'newChat',
description: 'Create a new chat session for Helix AI.',
arguments: [
{
label: 'Session ID',
key: 'sessionId',
type: 'string',
required: false,
description:
'ID of the chat session to continue. Leave empty to start a new chat.',
variables: true,
},
{
label: 'System Prompt',
key: 'systemPrompt',
type: 'string',
required: false,
description:
'Optional system prompt to start the chat with. It will be used only for new chat sessions.',
variables: true,
},
{
label: 'Input',
key: 'input',
type: 'string',
required: true,
description: 'Prompt to start the chat with.',
description: 'User input to start the chat with.',
variables: true,
},
],
async run($) {
const response = await $.http.post('/api/v1/sessions/chat', {
session_id: '',
session_id: $.step.parameters.sessionId,
system: $.step.parameters.systemPrompt,
messages: [
{
role: 'user',

View File

@@ -0,0 +1,3 @@
import respondWith from './respond-with/index.js';
export default [respondWith];

View File

@@ -0,0 +1,69 @@
import defineAction from '../../../../helpers/define-action.js';
export default defineAction({
name: 'Respond with',
key: 'respondWith',
description: 'Respond with defined JSON body.',
arguments: [
{
label: 'Status code',
key: 'statusCode',
type: 'string',
required: true,
variables: true,
value: '200',
},
{
label: 'Headers',
key: 'headers',
type: 'dynamic',
required: false,
description: 'Add or remove headers as needed',
fields: [
{
label: 'Key',
key: 'key',
type: 'string',
required: true,
description: 'Header key',
variables: true,
},
{
label: 'Value',
key: 'value',
type: 'string',
required: true,
description: 'Header value',
variables: true,
},
],
},
{
label: 'Body',
key: 'body',
type: 'string',
required: true,
description: 'The content of the response body.',
variables: true,
},
],
async run($) {
const statusCode = parseInt($.step.parameters.statusCode, 10);
const body = $.step.parameters.body;
const headers = $.step.parameters.headers.reduce((result, entry) => {
return {
...result,
[entry.key]: entry.value,
};
}, {});
$.setActionItem({
raw: {
headers,
body,
statusCode,
},
});
},
});

View File

@@ -1,4 +1,5 @@
import defineApp from '../../helpers/define-app.js';
import actions from './actions/index.js';
import triggers from './triggers/index.js';
export default defineApp({
@@ -10,5 +11,6 @@ export default defineApp({
baseUrl: '',
apiBaseUrl: '',
primaryColor: '0059F7',
actions,
triggers,
});

View File

@@ -7,7 +7,20 @@ export default defineTrigger({
key: 'catchRawWebhook',
type: 'webhook',
showWebhookUrl: true,
description: 'Triggers when the webhook receives a request.',
description:
'Triggers (immediately if configured) when the webhook receives a request.',
arguments: [
{
label: 'Wait until flow is done',
key: 'workSynchronously',
type: 'dropdown',
required: true,
options: [
{ label: 'Yes', value: true },
{ label: 'No', value: false },
],
},
],
async run($) {
const dataItem = {

View File

@@ -0,0 +1,13 @@
import User from '../../../../models/user.js';
import { renderObject, renderError } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const { email, password } = request.body;
const token = await User.authenticate(email, password);
if (token) {
return renderObject(response, { token });
}
renderError(response, [{ general: ['Incorrect email or password.'] }]);
};

View File

@@ -0,0 +1,39 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
import { createUser } from '../../../../../test/factories/user';
describe('POST /api/v1/access-tokens', () => {
beforeEach(async () => {
await createUser({
email: 'user@automatisch.io',
password: 'password',
});
});
it('should return the token data with correct credentials', async () => {
const response = await request(app)
.post('/api/v1/access-tokens')
.send({
email: 'user@automatisch.io',
password: 'password',
})
.expect(200);
expect(response.body.data.token.length).toBeGreaterThan(0);
});
it('should return error with incorrect credentials', async () => {
const response = await request(app)
.post('/api/v1/access-tokens')
.send({
email: 'incorrect@email.com',
password: 'incorrectpassword',
})
.expect(422);
expect(response.body.errors.general).toEqual([
'Incorrect email or password.',
]);
});
});

View File

@@ -1,52 +0,0 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../../test/factories/user.js';
import getAdminAppAuthClientMock from '../../../../../../test/mocks/rest/api/v1/admin/get-app-auth-client.js';
import { createAppAuthClient } from '../../../../../../test/factories/app-auth-client.js';
import { createRole } from '../../../../../../test/factories/role.js';
import * as license from '../../../../../helpers/license.ee.js';
describe('GET /api/v1/admin/app-auth-clients/:appAuthClientId', () => {
let currentUser, currentUserRole, currentAppAuthClient, token;
describe('with valid license key', () => {
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
currentUserRole = await createRole({ key: 'admin' });
currentUser = await createUser({ roleId: currentUserRole.id });
currentAppAuthClient = await createAppAuthClient();
token = createAuthTokenByUserId(currentUser.id);
});
it('should return specified app auth client info', async () => {
const response = await request(app)
.get(`/api/v1/admin/app-auth-clients/${currentAppAuthClient.id}`)
.set('Authorization', token)
.expect(200);
const expectedPayload = getAdminAppAuthClientMock(currentAppAuthClient);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for not existing app auth client UUID', async () => {
const notExistingAppAuthClientUUID = Crypto.randomUUID();
await request(app)
.get(`/api/v1/admin/app-auth-clients/${notExistingAppAuthClientUUID}`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await request(app)
.get('/api/v1/admin/app-auth-clients/invalidAppAuthClientUUID')
.set('Authorization', token)
.expect(400);
});
});
});

View File

@@ -4,6 +4,7 @@ import AppAuthClient from '../../../../../models/app-auth-client.js';
export default async (request, response) => {
const appAuthClient = await AppAuthClient.query()
.findById(request.params.appAuthClientId)
.where({ app_key: request.params.appKey })
.throwIfNotFound();
renderObject(response, appAuthClient);

View File

@@ -0,0 +1,55 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../../test/factories/user.js';
import { createRole } from '../../../../../../test/factories/role.js';
import getAppAuthClientMock from '../../../../../../test/mocks/rest/api/v1/admin/apps/get-auth-client.js';
import { createAppAuthClient } from '../../../../../../test/factories/app-auth-client.js';
import * as license from '../../../../../helpers/license.ee.js';
describe('GET /api/v1/admin/apps/:appKey/auth-clients/:appAuthClientId', () => {
let currentUser, adminRole, currentAppAuthClient, token;
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
adminRole = await createRole({ key: 'admin' });
currentUser = await createUser({ roleId: adminRole.id });
currentAppAuthClient = await createAppAuthClient({
appKey: 'deepl',
});
token = createAuthTokenByUserId(currentUser.id);
});
it('should return specified app auth client', async () => {
const response = await request(app)
.get(`/api/v1/admin/apps/deepl/auth-clients/${currentAppAuthClient.id}`)
.set('Authorization', token)
.expect(200);
const expectedPayload = getAppAuthClientMock(currentAppAuthClient);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for not existing app auth client ID', async () => {
const notExistingAppAuthClientUUID = Crypto.randomUUID();
await request(app)
.get(
`/api/v1/admin/apps/deepl/auth-clients/${notExistingAppAuthClientUUID}`
)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await request(app)
.get('/api/v1/admin/apps/deepl/auth-clients/invalidAppAuthClientUUID')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -0,0 +1,10 @@
import { renderObject } from '../../../../../helpers/renderer.js';
import AppAuthClient from '../../../../../models/app-auth-client.js';
export default async (request, response) => {
const appAuthClients = await AppAuthClient.query()
.where({ app_key: request.params.appKey })
.orderBy('created_at', 'desc');
renderObject(response, appAuthClients);
};

View File

@@ -0,0 +1,44 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../../test/factories/user.js';
import { createRole } from '../../../../../../test/factories/role.js';
import getAuthClientsMock from '../../../../../../test/mocks/rest/api/v1/admin/apps/get-auth-clients.js';
import { createAppAuthClient } from '../../../../../../test/factories/app-auth-client.js';
import * as license from '../../../../../helpers/license.ee.js';
describe('GET /api/v1/admin/apps/:appKey/auth-clients', () => {
let currentUser, adminRole, token;
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
adminRole = await createRole({ key: 'admin' });
currentUser = await createUser({ roleId: adminRole.id });
token = createAuthTokenByUserId(currentUser.id);
});
it('should return specified app auth client info', async () => {
const appAuthClientOne = await createAppAuthClient({
appKey: 'deepl',
});
const appAuthClientTwo = await createAppAuthClient({
appKey: 'deepl',
});
const response = await request(app)
.get('/api/v1/admin/apps/deepl/auth-clients')
.set('Authorization', token)
.expect(200);
const expectedPayload = getAuthClientsMock([
appAuthClientTwo,
appAuthClientOne,
]);
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -0,0 +1,14 @@
import { renderObject } from '../../../../../helpers/renderer.js';
import SamlAuthProvider from '../../../../../models/saml-auth-provider.ee.js';
export default async (request, response) => {
const samlAuthProvider = await SamlAuthProvider.query()
.findById(request.params.samlAuthProviderId)
.throwIfNotFound();
const roleMappings = await samlAuthProvider
.$relatedQuery('samlAuthProvidersRoleMappings')
.orderBy('remote_role_name', 'asc');
renderObject(response, roleMappings);
};

View File

@@ -0,0 +1,51 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import { createRole } from '../../../../../../test/factories/role.js';
import { createUser } from '../../../../../../test/factories/user.js';
import { createSamlAuthProvider } from '../../../../../../test/factories/saml-auth-provider.ee.js';
import { createRoleMapping } from '../../../../../../test/factories/role-mapping.js';
import getRoleMappingsMock from '../../../../../../test/mocks/rest/api/v1/admin/saml-auth-providers/get-role-mappings.ee.js';
import * as license from '../../../../../helpers/license.ee.js';
describe('GET /api/v1/admin/saml-auth-providers/:samlAuthProviderId/role-mappings', () => {
let roleMappingOne, roleMappingTwo, samlAuthProvider, currentUser, token;
beforeEach(async () => {
const role = await createRole({ key: 'admin' });
currentUser = await createUser({ roleId: role.id });
samlAuthProvider = await createSamlAuthProvider();
roleMappingOne = await createRoleMapping({
samlAuthProviderId: samlAuthProvider.id,
remoteRoleName: 'Admin',
});
roleMappingTwo = await createRoleMapping({
samlAuthProviderId: samlAuthProvider.id,
remoteRoleName: 'User',
});
token = createAuthTokenByUserId(currentUser.id);
});
it('should return role mappings', async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
const response = await request(app)
.get(
`/api/v1/admin/saml-auth-providers/${samlAuthProvider.id}/role-mappings`
)
.set('Authorization', token)
.expect(200);
const expectedPayload = await getRoleMappingsMock([
roleMappingOne,
roleMappingTwo,
]);
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -6,5 +6,7 @@ export default async (request, response) => {
.findById(request.params.samlAuthProviderId)
.throwIfNotFound();
renderObject(response, samlAuthProvider);
renderObject(response, samlAuthProvider, {
serializer: 'AdminSamlAuthProvider',
});
};

View File

@@ -7,5 +7,7 @@ export default async (request, response) => {
'desc'
);
renderObject(response, samlAuthProviders);
renderObject(response, samlAuthProviders, {
serializer: 'AdminSamlAuthProvider',
});
};

View File

@@ -0,0 +1,11 @@
import App from '../../../../models/app.js';
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const substeps = await App.findActionSubsteps(
request.params.appKey,
request.params.actionKey
);
renderObject(response, substeps);
};

View File

@@ -0,0 +1,52 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import App from '../../../../models/app';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import getActionSubstepsMock from '../../../../../test/mocks/rest/api/v1/apps/get-action-substeps.js';
describe('GET /api/v1/apps/:appKey/actions/:actionKey/substeps', () => {
let currentUser, exampleApp, token;
beforeEach(async () => {
currentUser = await createUser();
token = createAuthTokenByUserId(currentUser.id);
exampleApp = await App.findOneByKey('github');
});
it('should return the app auth info', async () => {
const actions = await App.findActionsByKey('github');
const exampleAction = actions.find(
(action) => action.key === 'createIssue'
);
const endpointUrl = `/api/v1/apps/${exampleApp.key}/actions/${exampleAction.key}/substeps`;
const response = await request(app)
.get(endpointUrl)
.set('Authorization', token)
.expect(200);
const expectedPayload = getActionSubstepsMock(exampleAction.substeps);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for invalid app key', async () => {
await request(app)
.get('/api/v1/apps/invalid-app-key/actions/invalid-actions-key/substeps')
.set('Authorization', token)
.expect(404);
});
it('should return empty array for invalid action key', async () => {
const endpointUrl = `/api/v1/apps/${exampleApp.key}/actions/invalid-action-key/substeps`;
const response = await request(app)
.get(endpointUrl)
.set('Authorization', token)
.expect(200);
expect(response.body.data).toEqual([]);
});
});

View File

@@ -0,0 +1,8 @@
import App from '../../../../models/app.js';
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const actions = await App.findActionsByKey(request.params.appKey);
renderObject(response, actions, { serializer: 'Action' });
};

View File

@@ -0,0 +1,35 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import App from '../../../../models/app';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import getActionsMock from '../../../../../test/mocks/rest/api/v1/apps/get-actions.js';
describe('GET /api/v1/apps/:appKey/actions', () => {
let currentUser, token;
beforeEach(async () => {
currentUser = await createUser();
token = createAuthTokenByUserId(currentUser.id);
});
it('should return the app actions', async () => {
const exampleApp = await App.findOneByKey('github');
const response = await request(app)
.get(`/api/v1/apps/${exampleApp.key}/actions`)
.set('Authorization', token)
.expect(200);
const expectedPayload = getActionsMock(exampleApp.actions);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for invalid app key', async () => {
await request(app)
.get('/api/v1/apps/invalid-app-key/actions')
.set('Authorization', token)
.expect(404);
});
});

View File

@@ -0,0 +1,16 @@
import App from '../../../../models/app.js';
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
let apps = await App.findAll(request.query.name);
if (request.query.onlyWithTriggers) {
apps = apps.filter((app) => app.triggers?.length);
}
if (request.query.onlyWithActions) {
apps = apps.filter((app) => app.actions?.length);
}
renderObject(response, apps, { serializer: 'App' });
};

View File

@@ -0,0 +1,63 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import App from '../../../../models/app';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import getAppsMock from '../../../../../test/mocks/rest/api/v1/apps/get-apps.js';
describe('GET /api/v1/apps', () => {
let currentUser, apps, token;
beforeEach(async () => {
currentUser = await createUser();
token = createAuthTokenByUserId(currentUser.id);
apps = await App.findAll();
});
it('should return all apps', async () => {
const response = await request(app)
.get('/api/v1/apps')
.set('Authorization', token)
.expect(200);
const expectedPayload = getAppsMock(apps);
expect(response.body).toEqual(expectedPayload);
});
it('should return all apps filtered by name', async () => {
const appsWithNameGit = apps.filter((app) => app.name.includes('Git'));
const response = await request(app)
.get('/api/v1/apps?name=Git')
.set('Authorization', token)
.expect(200);
const expectedPayload = getAppsMock(appsWithNameGit);
expect(response.body).toEqual(expectedPayload);
});
it('should return only the apps with triggers', async () => {
const appsWithTriggers = apps.filter((app) => app.triggers?.length > 0);
const response = await request(app)
.get('/api/v1/apps?onlyWithTriggers=true')
.set('Authorization', token)
.expect(200);
const expectedPayload = getAppsMock(appsWithTriggers);
expect(response.body).toEqual(expectedPayload);
});
it('should return only the apps with actions', async () => {
const appsWithActions = apps.filter((app) => app.actions?.length > 0);
const response = await request(app)
.get('/api/v1/apps?onlyWithActions=true')
.set('Authorization', token)
.expect(200);
const expectedPayload = getAppsMock(appsWithActions);
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -4,7 +4,7 @@ import AppAuthClient from '../../../../models/app-auth-client.js';
export default async (request, response) => {
const appAuthClient = await AppAuthClient.query()
.findById(request.params.appAuthClientId)
.where({ active: true })
.where({ app_key: request.params.appKey, active: true })
.throwIfNotFound();
renderObject(response, appAuthClient);

View File

@@ -4,25 +4,27 @@ import Crypto from 'crypto';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../test/factories/user.js';
import getAppAuthClientMock from '../../../../../test/mocks/rest/api/v1/admin/get-app-auth-client.js';
import getAppAuthClientMock from '../../../../../test/mocks/rest/api/v1/apps/get-auth-client.js';
import { createAppAuthClient } from '../../../../../test/factories/app-auth-client.js';
import * as license from '../../../../helpers/license.ee.js';
describe('GET /api/v1/app-auth-clients/:id', () => {
describe('GET /api/v1/apps/:appKey/auth-clients/:appAuthClientId', () => {
let currentUser, currentAppAuthClient, token;
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
currentUser = await createUser();
currentAppAuthClient = await createAppAuthClient();
currentAppAuthClient = await createAppAuthClient({
appKey: 'deepl',
});
token = createAuthTokenByUserId(currentUser.id);
});
it('should return specified app auth client info', async () => {
it('should return specified app auth client', async () => {
const response = await request(app)
.get(`/api/v1/app-auth-clients/${currentAppAuthClient.id}`)
.get(`/api/v1/apps/deepl/auth-clients/${currentAppAuthClient.id}`)
.set('Authorization', token)
.expect(200);
@@ -34,14 +36,14 @@ describe('GET /api/v1/app-auth-clients/:id', () => {
const notExistingAppAuthClientUUID = Crypto.randomUUID();
await request(app)
.get(`/api/v1/app-auth-clients/${notExistingAppAuthClientUUID}`)
.get(`/api/v1/apps/deepl/auth-clients/${notExistingAppAuthClientUUID}`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await request(app)
.get('/api/v1/app-auth-clients/invalidAppAuthClientUUID')
.get('/api/v1/apps/deepl/auth-clients/invalidAppAuthClientUUID')
.set('Authorization', token)
.expect(400);
});

View File

@@ -0,0 +1,10 @@
import { renderObject } from '../../../../helpers/renderer.js';
import AppAuthClient from '../../../../models/app-auth-client.js';
export default async (request, response) => {
const appAuthClients = await AppAuthClient.query()
.where({ app_key: request.params.appKey, active: true })
.orderBy('created_at', 'desc');
renderObject(response, appAuthClients);
};

View File

@@ -0,0 +1,42 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../test/factories/user.js';
import getAuthClientsMock from '../../../../../test/mocks/rest/api/v1/apps/get-auth-clients.js';
import { createAppAuthClient } from '../../../../../test/factories/app-auth-client.js';
import * as license from '../../../../helpers/license.ee.js';
describe('GET /api/v1/apps/:appKey/auth-clients', () => {
let currentUser, token;
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
currentUser = await createUser();
token = createAuthTokenByUserId(currentUser.id);
});
it('should return specified app auth client info', async () => {
const appAuthClientOne = await createAppAuthClient({
appKey: 'deepl',
});
const appAuthClientTwo = await createAppAuthClient({
appKey: 'deepl',
});
const response = await request(app)
.get('/api/v1/apps/deepl/auth-clients')
.set('Authorization', token)
.expect(200);
const expectedPayload = getAuthClientsMock([
appAuthClientTwo,
appAuthClientOne,
]);
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -0,0 +1,8 @@
import App from '../../../../models/app.js';
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const auth = await App.findAuthByKey(request.params.appKey);
renderObject(response, auth, { serializer: 'Auth' });
};

View File

@@ -0,0 +1,35 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import App from '../../../../models/app';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import getAuthMock from '../../../../../test/mocks/rest/api/v1/apps/get-auth.js';
describe('GET /api/v1/apps/:appKey/auth', () => {
let currentUser, token;
beforeEach(async () => {
currentUser = await createUser();
token = createAuthTokenByUserId(currentUser.id);
});
it('should return the app auth info', async () => {
const exampleApp = await App.findOneByKey('github');
const response = await request(app)
.get(`/api/v1/apps/${exampleApp.key}/auth`)
.set('Authorization', token)
.expect(200);
const expectedPayload = getAuthMock(exampleApp.auth);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for invalid app key', async () => {
await request(app)
.get('/api/v1/apps/invalid-app-key/auth')
.set('Authorization', token)
.expect(404);
});
});

View File

@@ -0,0 +1,12 @@
import { renderObject } from '../../../../helpers/renderer.js';
import AppConfig from '../../../../models/app-config.js';
export default async (request, response) => {
const appConfig = await AppConfig.query()
.findOne({
key: request.params.appKey,
})
.throwIfNotFound();
renderObject(response, appConfig);
};

View File

@@ -0,0 +1,44 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../test/factories/user.js';
import getAppConfigMock from '../../../../../test/mocks/rest/api/v1/apps/get-config.js';
import { createAppConfig } from '../../../../../test/factories/app-config.js';
import * as license from '../../../../helpers/license.ee.js';
describe('GET /api/v1/apps/:appKey/config', () => {
let currentUser, appConfig, token;
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
currentUser = await createUser();
appConfig = await createAppConfig({
key: 'deepl',
allowCustomConnection: true,
shared: true,
disabled: false,
});
token = createAuthTokenByUserId(currentUser.id);
});
it('should return specified app config info', async () => {
const response = await request(app)
.get(`/api/v1/apps/${appConfig.key}/config`)
.set('Authorization', token)
.expect(200);
const expectedPayload = getAppConfigMock(appConfig);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for not existing app key', async () => {
await request(app)
.get('/api/v1/apps/not-existing-app-key/config')
.set('Authorization', token)
.expect(404);
});
});

View File

@@ -0,0 +1,23 @@
import { renderObject } from '../../../../helpers/renderer.js';
import App from '../../../../models/app.js';
import paginateRest from '../../../../helpers/pagination-rest.js';
export default async (request, response) => {
const app = await App.findOneByKey(request.params.appKey);
const flowsQuery = request.currentUser.authorizedFlows
.clone()
.joinRelated({
steps: true,
})
.withGraphFetched({
steps: true,
})
.where('steps.app_key', app.key)
.orderBy('active', 'desc')
.orderBy('updated_at', 'desc');
const flows = await paginateRest(flowsQuery, request.query.page);
renderObject(response, flows);
};

View File

@@ -0,0 +1,129 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../test/factories/user.js';
import { createFlow } from '../../../../../test/factories/flow.js';
import { createStep } from '../../../../../test/factories/step.js';
import { createPermission } from '../../../../../test/factories/permission.js';
import getFlowsMock from '../../../../../test/mocks/rest/api/v1/flows/get-flows.js';
describe('GET /api/v1/apps/:appKey/flows', () => {
let currentUser, currentUserRole, token;
beforeEach(async () => {
currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
token = createAuthTokenByUserId(currentUser.id);
});
it('should return the flows data of specified app for current user', async () => {
const currentUserFlowOne = await createFlow({ userId: currentUser.id });
const triggerStepFlowOne = await createStep({
flowId: currentUserFlowOne.id,
type: 'trigger',
appKey: 'webhook',
});
const actionStepFlowOne = await createStep({
flowId: currentUserFlowOne.id,
type: 'action',
});
const currentUserFlowTwo = await createFlow({ userId: currentUser.id });
await createStep({
flowId: currentUserFlowTwo.id,
type: 'trigger',
appKey: 'github',
});
await createStep({
flowId: currentUserFlowTwo.id,
type: 'action',
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const response = await request(app)
.get('/api/v1/apps/webhook/flows')
.set('Authorization', token)
.expect(200);
const expectedPayload = await getFlowsMock(
[currentUserFlowOne],
[triggerStepFlowOne, actionStepFlowOne]
);
expect(response.body).toEqual(expectedPayload);
});
it('should return the flows data of specified app for another user', async () => {
const anotherUser = await createUser();
const anotherUserFlowOne = await createFlow({ userId: anotherUser.id });
const triggerStepFlowOne = await createStep({
flowId: anotherUserFlowOne.id,
type: 'trigger',
appKey: 'webhook',
});
const actionStepFlowOne = await createStep({
flowId: anotherUserFlowOne.id,
type: 'action',
});
const anotherUserFlowTwo = await createFlow({ userId: anotherUser.id });
await createStep({
flowId: anotherUserFlowTwo.id,
type: 'trigger',
appKey: 'github',
});
await createStep({
flowId: anotherUserFlowTwo.id,
type: 'action',
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
const response = await request(app)
.get('/api/v1/apps/webhook/flows')
.set('Authorization', token)
.expect(200);
const expectedPayload = await getFlowsMock(
[anotherUserFlowOne],
[triggerStepFlowOne, actionStepFlowOne]
);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for invalid app key', async () => {
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await request(app)
.get('/api/v1/apps/invalid-app-key/flows')
.set('Authorization', token)
.expect(404);
});
});

View File

@@ -0,0 +1,11 @@
import App from '../../../../models/app.js';
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const substeps = await App.findTriggerSubsteps(
request.params.appKey,
request.params.triggerKey
);
renderObject(response, substeps);
};

View File

@@ -0,0 +1,52 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import App from '../../../../models/app';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import getTriggerSubstepsMock from '../../../../../test/mocks/rest/api/v1/apps/get-trigger-substeps.js';
describe('GET /api/v1/apps/:appKey/triggers/:triggerKey/substeps', () => {
let currentUser, exampleApp, token;
beforeEach(async () => {
currentUser = await createUser();
token = createAuthTokenByUserId(currentUser.id);
exampleApp = await App.findOneByKey('github');
});
it('should return the app auth info', async () => {
const triggers = await App.findTriggersByKey('github');
const exampleTrigger = triggers.find(
(trigger) => trigger.key === 'newIssues'
);
const endpointUrl = `/api/v1/apps/${exampleApp.key}/triggers/${exampleTrigger.key}/substeps`;
const response = await request(app)
.get(endpointUrl)
.set('Authorization', token)
.expect(200);
const expectedPayload = getTriggerSubstepsMock(exampleTrigger.substeps);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for invalid app key', async () => {
await request(app)
.get('/api/v1/apps/invalid-app-key/triggers/invalid-trigger-key/substeps')
.set('Authorization', token)
.expect(404);
});
it('should return empty array for invalid trigger key', async () => {
const endpointUrl = `/api/v1/apps/${exampleApp.key}/triggers/invalid-trigger-key/substeps`;
const response = await request(app)
.get(endpointUrl)
.set('Authorization', token)
.expect(200);
expect(response.body.data).toEqual([]);
});
});

View File

@@ -0,0 +1,8 @@
import App from '../../../../models/app.js';
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const triggers = await App.findTriggersByKey(request.params.appKey);
renderObject(response, triggers, { serializer: 'Trigger' });
};

View File

@@ -0,0 +1,35 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import App from '../../../../models/app';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import getTriggersMock from '../../../../../test/mocks/rest/api/v1/apps/get-triggers.js';
describe('GET /api/v1/apps/:appKey/triggers', () => {
let currentUser, token;
beforeEach(async () => {
currentUser = await createUser();
token = createAuthTokenByUserId(currentUser.id);
});
it('should return the app triggers', async () => {
const exampleApp = await App.findOneByKey('github');
const response = await request(app)
.get(`/api/v1/apps/${exampleApp.key}/triggers`)
.set('Authorization', token)
.expect(200);
const expectedPayload = getTriggersMock(exampleApp.triggers);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for invalid app key', async () => {
await request(app)
.get('/api/v1/apps/invalid-app-key/triggers')
.set('Authorization', token)
.expect(404);
});
});

View File

@@ -0,0 +1,24 @@
import appConfig from '../../../../config/app.js';
import Config from '../../../../models/config.js';
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const defaultConfig = {
disableNotificationsPage: appConfig.disableNotificationsPage,
disableFavicon: appConfig.disableFavicon,
additionalDrawerLink: appConfig.additionalDrawerLink,
additionalDrawerLinkText: appConfig.additionalDrawerLinkText,
};
let config = await Config.query().orderBy('key', 'asc');
config = config.reduce((computedConfig, configEntry) => {
const { key, value } = configEntry;
computedConfig[key] = value?.data;
return computedConfig;
}, defaultConfig);
renderObject(response, config);
};

View File

@@ -0,0 +1,51 @@
import { vi, expect, describe, it } from 'vitest';
import request from 'supertest';
import { createConfig } from '../../../../../test/factories/config.js';
import app from '../../../../app.js';
import configMock from '../../../../../test/mocks/rest/api/v1/automatisch/config.js';
import * as license from '../../../../helpers/license.ee.js';
describe('GET /api/v1/automatisch/config', () => {
it('should return Automatisch config', async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
const logoConfig = await createConfig({
key: 'logo.svgData',
value: { data: '<svg>Sample</svg>' },
});
const primaryDarkConfig = await createConfig({
key: 'palette.primary.dark',
value: { data: '#001F52' },
});
const primaryLightConfig = await createConfig({
key: 'palette.primary.light',
value: { data: '#4286FF' },
});
const primaryMainConfig = await createConfig({
key: 'palette.primary.main',
value: { data: '#0059F7' },
});
const titleConfig = await createConfig({
key: 'title',
value: { data: 'Sample Title' },
});
const response = await request(app)
.get('/api/v1/automatisch/config')
.expect(200);
const expectedPayload = configMock(
logoConfig,
primaryDarkConfig,
primaryLightConfig,
primaryMainConfig,
titleConfig
);
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -0,0 +1,20 @@
import { renderObject } from '../../../../helpers/renderer.js';
import paginateRest from '../../../../helpers/pagination-rest.js';
export default async (request, response) => {
const flowsQuery = request.currentUser.authorizedFlows
.clone()
.joinRelated({
steps: true,
})
.withGraphFetched({
steps: true,
})
.where('steps.connection_id', request.params.connectionId)
.orderBy('active', 'desc')
.orderBy('updated_at', 'desc');
const flows = await paginateRest(flowsQuery, request.query.page);
renderObject(response, flows);
};

View File

@@ -0,0 +1,128 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../test/factories/user.js';
import { createConnection } from '../../../../../test/factories/connection.js';
import { createFlow } from '../../../../../test/factories/flow.js';
import { createStep } from '../../../../../test/factories/step.js';
import { createPermission } from '../../../../../test/factories/permission.js';
import getFlowsMock from '../../../../../test/mocks/rest/api/v1/flows/get-flows.js';
describe('GET /api/v1/connections/:connectionId/flows', () => {
let currentUser, currentUserRole, token;
beforeEach(async () => {
currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
token = createAuthTokenByUserId(currentUser.id);
});
it('should return the flows data of specified connection for current user', async () => {
const currentUserFlowOne = await createFlow({ userId: currentUser.id });
const currentUserConnection = await createConnection({
userId: currentUser.id,
key: 'webhook',
});
const triggerStepFlowOne = await createStep({
flowId: currentUserFlowOne.id,
type: 'trigger',
appKey: 'webhook',
connectionId: currentUserConnection.id,
});
const actionStepFlowOne = await createStep({
flowId: currentUserFlowOne.id,
type: 'action',
});
const currentUserFlowTwo = await createFlow({ userId: currentUser.id });
await createStep({
flowId: currentUserFlowTwo.id,
type: 'trigger',
appKey: 'github',
});
await createStep({
flowId: currentUserFlowTwo.id,
type: 'action',
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const response = await request(app)
.get(`/api/v1/connections/${currentUserConnection.id}/flows`)
.set('Authorization', token)
.expect(200);
const expectedPayload = await getFlowsMock(
[currentUserFlowOne],
[triggerStepFlowOne, actionStepFlowOne]
);
expect(response.body).toEqual(expectedPayload);
});
it('should return the flows data of specified connection for another user', async () => {
const anotherUser = await createUser();
const anotherUserFlowOne = await createFlow({ userId: anotherUser.id });
const anotherUserConnection = await createConnection({
userId: anotherUser.id,
key: 'webhook',
});
const triggerStepFlowOne = await createStep({
flowId: anotherUserFlowOne.id,
type: 'trigger',
appKey: 'webhook',
connectionId: anotherUserConnection.id,
});
const actionStepFlowOne = await createStep({
flowId: anotherUserFlowOne.id,
type: 'action',
});
const anotherUserFlowTwo = await createFlow({ userId: anotherUser.id });
await createStep({
flowId: anotherUserFlowTwo.id,
type: 'trigger',
appKey: 'github',
});
await createStep({
flowId: anotherUserFlowTwo.id,
type: 'action',
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
const response = await request(app)
.get(`/api/v1/connections/${anotherUserConnection.id}/flows`)
.set('Authorization', token)
.expect(200);
const expectedPayload = await getFlowsMock(
[anotherUserFlowOne],
[triggerStepFlowOne, actionStepFlowOne]
);
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -0,0 +1,23 @@
import { renderObject } from '../../../../helpers/renderer.js';
import paginateRest from '../../../../helpers/pagination-rest.js';
export default async (request, response) => {
const execution = await request.currentUser.authorizedExecutions
.clone()
.withSoftDeleted()
.findById(request.params.executionId)
.throwIfNotFound();
const executionStepsQuery = execution
.$relatedQuery('executionSteps')
.withSoftDeleted()
.withGraphFetched('step')
.orderBy('created_at', 'asc');
const executionSteps = await paginateRest(
executionStepsQuery,
request.query.page
);
renderObject(response, executionSteps);
};

View File

@@ -0,0 +1,153 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import { createFlow } from '../../../../../test/factories/flow.js';
import { createStep } from '../../../../../test/factories/step.js';
import { createExecution } from '../../../../../test/factories/execution.js';
import { createExecutionStep } from '../../../../../test/factories/execution-step.js';
import { createPermission } from '../../../../../test/factories/permission';
import getExecutionStepsMock from '../../../../../test/mocks/rest/api/v1/executions/get-execution-steps';
describe('GET /api/v1/executions/:executionId/execution-steps', () => {
let currentUser, currentUserRole, anotherUser, token;
beforeEach(async () => {
currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
anotherUser = await createUser();
token = createAuthTokenByUserId(currentUser.id);
});
it('should return the execution steps of current user execution', async () => {
const currentUserFlow = await createFlow({
userId: currentUser.id,
});
const stepOne = await createStep({
flowId: currentUserFlow.id,
type: 'trigger',
});
const stepTwo = await createStep({
flowId: currentUserFlow.id,
type: 'action',
});
const currentUserExecution = await createExecution({
flowId: currentUserFlow.id,
});
const currentUserExecutionStepOne = await createExecutionStep({
executionId: currentUserExecution.id,
stepId: stepOne.id,
});
const currentUserExecutionStepTwo = await createExecutionStep({
executionId: currentUserExecution.id,
stepId: stepTwo.id,
});
await createPermission({
action: 'read',
subject: 'Execution',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const response = await request(app)
.get(`/api/v1/executions/${currentUserExecution.id}/execution-steps`)
.set('Authorization', token)
.expect(200);
const expectedPayload = await getExecutionStepsMock(
[currentUserExecutionStepOne, currentUserExecutionStepTwo],
[stepOne, stepTwo]
);
expect(response.body).toEqual(expectedPayload);
});
it('should return the execution steps of another user execution', async () => {
const anotherUserFlow = await createFlow({
userId: anotherUser.id,
});
const stepOne = await createStep({
flowId: anotherUserFlow.id,
type: 'trigger',
});
const stepTwo = await createStep({
flowId: anotherUserFlow.id,
type: 'action',
});
const anotherUserExecution = await createExecution({
flowId: anotherUserFlow.id,
});
const anotherUserExecutionStepOne = await createExecutionStep({
executionId: anotherUserExecution.id,
stepId: stepOne.id,
});
const anotherUserExecutionStepTwo = await createExecutionStep({
executionId: anotherUserExecution.id,
stepId: stepTwo.id,
});
await createPermission({
action: 'read',
subject: 'Execution',
roleId: currentUserRole.id,
conditions: [],
});
const response = await request(app)
.get(`/api/v1/executions/${anotherUserExecution.id}/execution-steps`)
.set('Authorization', token)
.expect(200);
const expectedPayload = await getExecutionStepsMock(
[anotherUserExecutionStepOne, anotherUserExecutionStepTwo],
[stepOne, stepTwo]
);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for not existing execution step UUID', async () => {
await createPermission({
action: 'read',
subject: 'Execution',
roleId: currentUserRole.id,
conditions: [],
});
const notExistingExcecutionUUID = Crypto.randomUUID();
await request(app)
.get(`/api/v1/executions/${notExistingExcecutionUUID}/execution-steps`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await createPermission({
action: 'read',
subject: 'Execution',
roleId: currentUserRole.id,
conditions: [],
});
await request(app)
.get('/api/v1/executions/invalidExecutionUUID/execution-steps')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -0,0 +1,16 @@
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const execution = await request.currentUser.authorizedExecutions
.clone()
.withGraphFetched({
flow: {
steps: true,
},
})
.withSoftDeleted()
.findById(request.params.executionId)
.throwIfNotFound();
renderObject(response, execution);
};

View File

@@ -0,0 +1,134 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import { createFlow } from '../../../../../test/factories/flow.js';
import { createStep } from '../../../../../test/factories/step.js';
import { createExecution } from '../../../../../test/factories/execution.js';
import { createPermission } from '../../../../../test/factories/permission';
import getExecutionMock from '../../../../../test/mocks/rest/api/v1/executions/get-execution';
describe('GET /api/v1/executions/:executionId', () => {
let currentUser, currentUserRole, token;
beforeEach(async () => {
currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
token = createAuthTokenByUserId(currentUser.id);
});
it('should return the execution data of current user', async () => {
const currentUserFlow = await createFlow({
userId: currentUser.id,
});
const stepOne = await createStep({
flowId: currentUserFlow.id,
type: 'trigger',
});
const stepTwo = await createStep({
flowId: currentUserFlow.id,
type: 'action',
});
const currentUserExecution = await createExecution({
flowId: currentUserFlow.id,
});
await createPermission({
action: 'read',
subject: 'Execution',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const response = await request(app)
.get(`/api/v1/executions/${currentUserExecution.id}`)
.set('Authorization', token)
.expect(200);
const expectedPayload = await getExecutionMock(
currentUserExecution,
currentUserFlow,
[stepOne, stepTwo]
);
expect(response.body).toEqual(expectedPayload);
});
it('should return the execution data of another user', async () => {
const anotherUser = await createUser();
const anotherUserFlow = await createFlow({
userId: anotherUser.id,
});
const stepOne = await createStep({
flowId: anotherUserFlow.id,
type: 'trigger',
});
const stepTwo = await createStep({
flowId: anotherUserFlow.id,
type: 'action',
});
const anotherUserExecution = await createExecution({
flowId: anotherUserFlow.id,
});
await createPermission({
action: 'read',
subject: 'Execution',
roleId: currentUserRole.id,
conditions: [],
});
const response = await request(app)
.get(`/api/v1/executions/${anotherUserExecution.id}`)
.set('Authorization', token)
.expect(200);
const expectedPayload = await getExecutionMock(
anotherUserExecution,
anotherUserFlow,
[stepOne, stepTwo]
);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for not existing execution UUID', async () => {
await createPermission({
action: 'read',
subject: 'Execution',
roleId: currentUserRole.id,
conditions: [],
});
const notExistingExcecutionUUID = Crypto.randomUUID();
await request(app)
.get(`/api/v1/executions/${notExistingExcecutionUUID}`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await createPermission({
action: 'read',
subject: 'Execution',
roleId: currentUserRole.id,
conditions: [],
});
await request(app)
.get('/api/v1/executions/invalidExecutionUUID')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -0,0 +1,27 @@
import { renderObject } from '../../../../helpers/renderer.js';
import paginateRest from '../../../../helpers/pagination-rest.js';
export default async (request, response) => {
const executionsQuery = request.currentUser.authorizedExecutions
.clone()
.withSoftDeleted()
.orderBy('created_at', 'desc')
.withGraphFetched({
flow: {
steps: true,
},
});
const executions = await paginateRest(executionsQuery, request.query.page);
for (const execution of executions.records) {
const executionSteps = await execution.$relatedQuery('executionSteps');
const status = executionSteps.some((step) => step.status === 'failure')
? 'failure'
: 'success';
execution.status = status;
}
renderObject(response, executions);
};

View File

@@ -0,0 +1,119 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import { createFlow } from '../../../../../test/factories/flow.js';
import { createStep } from '../../../../../test/factories/step.js';
import { createExecution } from '../../../../../test/factories/execution.js';
import { createPermission } from '../../../../../test/factories/permission';
import getExecutionsMock from '../../../../../test/mocks/rest/api/v1/executions/get-executions';
describe('GET /api/v1/executions', () => {
let currentUser, currentUserRole, anotherUser, token;
beforeEach(async () => {
currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
anotherUser = await createUser();
token = createAuthTokenByUserId(currentUser.id);
});
it('should return the executions of current user', async () => {
const currentUserFlow = await createFlow({
userId: currentUser.id,
});
const stepOne = await createStep({
flowId: currentUserFlow.id,
type: 'trigger',
});
const stepTwo = await createStep({
flowId: currentUserFlow.id,
type: 'action',
});
const currentUserExecutionOne = await createExecution({
flowId: currentUserFlow.id,
});
const currentUserExecutionTwo = await createExecution({
flowId: currentUserFlow.id,
});
await currentUserExecutionTwo
.$query()
.patchAndFetch({ deletedAt: new Date().toISOString() });
await createPermission({
action: 'read',
subject: 'Execution',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const response = await request(app)
.get('/api/v1/executions')
.set('Authorization', token)
.expect(200);
const expectedPayload = await getExecutionsMock(
[currentUserExecutionTwo, currentUserExecutionOne],
currentUserFlow,
[stepOne, stepTwo]
);
expect(response.body).toEqual(expectedPayload);
});
it('should return the executions of another user', async () => {
const anotherUserFlow = await createFlow({
userId: anotherUser.id,
});
const stepOne = await createStep({
flowId: anotherUserFlow.id,
type: 'trigger',
});
const stepTwo = await createStep({
flowId: anotherUserFlow.id,
type: 'action',
});
const anotherUserExecutionOne = await createExecution({
flowId: anotherUserFlow.id,
});
const anotherUserExecutionTwo = await createExecution({
flowId: anotherUserFlow.id,
});
await anotherUserExecutionTwo
.$query()
.patchAndFetch({ deletedAt: new Date().toISOString() });
await createPermission({
action: 'read',
subject: 'Execution',
roleId: currentUserRole.id,
conditions: [],
});
const response = await request(app)
.get('/api/v1/executions')
.set('Authorization', token)
.expect(200);
const expectedPayload = await getExecutionsMock(
[anotherUserExecutionTwo, anotherUserExecutionOne],
anotherUserFlow,
[stepOne, stepTwo]
);
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -2,6 +2,7 @@ import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const flow = await request.currentUser.authorizedFlows
.clone()
.withGraphJoined({ steps: true })
.orderBy('steps.position', 'asc')
.findOne({ 'flows.id': request.params.flowId })

View File

@@ -0,0 +1,21 @@
import { renderObject } from '../../../../helpers/renderer.js';
import paginateRest from '../../../../helpers/pagination-rest.js';
export default async (request, response) => {
const flowsQuery = request.currentUser.authorizedFlows
.clone()
.withGraphFetched({
steps: true,
})
.where((builder) => {
if (request.query.name) {
builder.where('flows.name', 'ilike', `%${request.query.name}%`);
}
})
.orderBy('active', 'desc')
.orderBy('updated_at', 'desc');
const flows = await paginateRest(flowsQuery, request.query.page);
renderObject(response, flows);
};

View File

@@ -0,0 +1,118 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import { createFlow } from '../../../../../test/factories/flow';
import { createStep } from '../../../../../test/factories/step';
import { createPermission } from '../../../../../test/factories/permission';
import getFlowsMock from '../../../../../test/mocks/rest/api/v1/flows/get-flows.js';
describe('GET /api/v1/flows', () => {
let currentUser, currentUserRole, token;
beforeEach(async () => {
currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
token = createAuthTokenByUserId(currentUser.id);
});
it('should return the flows data of current user', async () => {
const currentUserFlowOne = await createFlow({ userId: currentUser.id });
const triggerStepFlowOne = await createStep({
flowId: currentUserFlowOne.id,
type: 'trigger',
});
const actionStepFlowOne = await createStep({
flowId: currentUserFlowOne.id,
type: 'action',
});
const currentUserFlowTwo = await createFlow({ userId: currentUser.id });
const triggerStepFlowTwo = await createStep({
flowId: currentUserFlowTwo.id,
type: 'trigger',
});
const actionStepFlowTwo = await createStep({
flowId: currentUserFlowTwo.id,
type: 'action',
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const response = await request(app)
.get('/api/v1/flows')
.set('Authorization', token)
.expect(200);
const expectedPayload = await getFlowsMock(
[currentUserFlowTwo, currentUserFlowOne],
[
triggerStepFlowOne,
actionStepFlowOne,
triggerStepFlowTwo,
actionStepFlowTwo,
]
);
expect(response.body).toEqual(expectedPayload);
});
it('should return the flows data of another user', async () => {
const anotherUser = await createUser();
const anotherUserFlowOne = await createFlow({ userId: anotherUser.id });
const triggerStepFlowOne = await createStep({
flowId: anotherUserFlowOne.id,
type: 'trigger',
});
const actionStepFlowOne = await createStep({
flowId: anotherUserFlowOne.id,
type: 'action',
});
const anotherUserFlowTwo = await createFlow({ userId: anotherUser.id });
const triggerStepFlowTwo = await createStep({
flowId: anotherUserFlowTwo.id,
type: 'trigger',
});
const actionStepFlowTwo = await createStep({
flowId: anotherUserFlowTwo.id,
type: 'action',
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
const response = await request(app)
.get('/api/v1/flows')
.set('Authorization', token)
.expect(200);
const expectedPayload = await getFlowsMock(
[anotherUserFlowTwo, anotherUserFlowOne],
[
triggerStepFlowOne,
actionStepFlowOne,
triggerStepFlowTwo,
actionStepFlowTwo,
]
);
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -0,0 +1,12 @@
import { renderObject } from '../../../../helpers/renderer.js';
import SamlAuthProvider from '../../../../models/saml-auth-provider.ee.js';
export default async (request, response) => {
const samlAuthProviders = await SamlAuthProvider.query()
.where({
active: true,
})
.orderBy('created_at', 'desc');
renderObject(response, samlAuthProviders);
};

View File

@@ -0,0 +1,30 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
import { createSamlAuthProvider } from '../../../../../test/factories/saml-auth-provider.ee.js';
import getSamlAuthProvidersMock from '../../../../../test/mocks/rest/api/v1/saml-auth-providers/get-saml-auth-providers.js';
import * as license from '../../../../helpers/license.ee.js';
describe('GET /api/v1/saml-auth-providers', () => {
let samlAuthProviderOne, samlAuthProviderTwo;
beforeEach(async () => {
samlAuthProviderOne = await createSamlAuthProvider();
samlAuthProviderTwo = await createSamlAuthProvider();
});
it('should return saml auth providers', async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
const response = await request(app)
.get('/api/v1/saml-auth-providers')
.expect(200);
const expectedPayload = await getSamlAuthProvidersMock([
samlAuthProviderTwo,
samlAuthProviderOne,
]);
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -0,0 +1,17 @@
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const step = await request.currentUser.authorizedSteps
.clone()
.where('steps.id', request.params.stepId)
.whereNotNull('steps.app_key')
.first()
.throwIfNotFound();
const dynamicFields = await step.createDynamicFields(
request.body.dynamicFieldsKey,
request.body.parameters
);
renderObject(response, dynamicFields);
};

View File

@@ -0,0 +1,169 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import { createFlow } from '../../../../../test/factories/flow';
import { createStep } from '../../../../../test/factories/step';
import { createPermission } from '../../../../../test/factories/permission';
import createDynamicFieldsMock from '../../../../../test/mocks/rest/api/v1/steps/create-dynamic-fields';
describe('POST /api/v1/steps/:stepId/dynamic-fields', () => {
let currentUser, currentUserRole, token;
beforeEach(async () => {
currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
token = createAuthTokenByUserId(currentUser.id);
});
it('should return dynamically created fields of the current users step', async () => {
const currentUserflow = await createFlow({ userId: currentUser.id });
const actionStep = await createStep({
flowId: currentUserflow.id,
type: 'action',
appKey: 'slack',
key: 'sendMessageToChannel',
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const response = await request(app)
.post(`/api/v1/steps/${actionStep.id}/dynamic-fields`)
.set('Authorization', token)
.send({
dynamicFieldsKey: 'listFieldsAfterSendAsBot',
parameters: {
sendAsBot: true,
},
})
.expect(200);
const expectedPayload = await createDynamicFieldsMock();
expect(response.body).toEqual(expectedPayload);
});
it('should return dynamically created fields of the another users step', async () => {
const anotherUser = await createUser();
const anotherUserflow = await createFlow({ userId: anotherUser.id });
const actionStep = await createStep({
flowId: anotherUserflow.id,
type: 'action',
appKey: 'slack',
key: 'sendMessageToChannel',
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
const response = await request(app)
.post(`/api/v1/steps/${actionStep.id}/dynamic-fields`)
.set('Authorization', token)
.send({
dynamicFieldsKey: 'listFieldsAfterSendAsBot',
parameters: {
sendAsBot: true,
},
})
.expect(200);
const expectedPayload = await createDynamicFieldsMock();
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for not existing step UUID', async () => {
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
const notExistingStepUUID = Crypto.randomUUID();
await request(app)
.get(`/api/v1/steps/${notExistingStepUUID}/dynamic-fields`)
.set('Authorization', token)
.expect(404);
});
it('should return not found response for existing step UUID without app key', async () => {
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
const step = await createStep({ appKey: null });
await request(app)
.get(`/api/v1/steps/${step.id}/dynamic-fields`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await request(app)
.post('/api/v1/steps/invalidStepUUID/dynamic-fields')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -0,0 +1,11 @@
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const step = await request.currentUser.authorizedSteps
.findById(request.params.stepId)
.throwIfNotFound();
const connection = await step.$relatedQuery('connection').throwIfNotFound();
renderObject(response, connection);
};

View File

@@ -0,0 +1,121 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import { createConnection } from '../../../../../test/factories/connection';
import { createFlow } from '../../../../../test/factories/flow';
import { createStep } from '../../../../../test/factories/step';
import { createPermission } from '../../../../../test/factories/permission';
import getConnectionMock from '../../../../../test/mocks/rest/api/v1/steps/get-connection';
describe('GET /api/v1/steps/:stepId/connection', () => {
let currentUser, currentUserRole, token;
beforeEach(async () => {
currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
token = createAuthTokenByUserId(currentUser.id);
});
it('should return the current user connection data of specified step', async () => {
const currentUserflow = await createFlow({ userId: currentUser.id });
const currentUserConnection = await createConnection();
const triggerStep = await createStep({
flowId: currentUserflow.id,
connectionId: currentUserConnection.id,
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const response = await request(app)
.get(`/api/v1/steps/${triggerStep.id}/connection`)
.set('Authorization', token)
.expect(200);
const expectedPayload = await getConnectionMock(currentUserConnection);
expect(response.body).toEqual(expectedPayload);
});
it('should return the current user connection data of specified step', async () => {
const anotherUser = await createUser();
const anotherUserFlow = await createFlow({ userId: anotherUser.id });
const anotherUserConnection = await createConnection();
const triggerStep = await createStep({
flowId: anotherUserFlow.id,
connectionId: anotherUserConnection.id,
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
const response = await request(app)
.get(`/api/v1/steps/${triggerStep.id}/connection`)
.set('Authorization', token)
.expect(200);
const expectedPayload = await getConnectionMock(anotherUserConnection);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for not existing step without connection', async () => {
const stepWithoutConnection = await createStep();
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await request(app)
.get(`/api/v1/steps/${stepWithoutConnection.id}/connection`)
.set('Authorization', token)
.expect(404);
});
it('should return not found response for not existing step UUID', async () => {
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
const notExistingFlowUUID = Crypto.randomUUID();
await request(app)
.get(`/api/v1/steps/${notExistingFlowUUID}/connection`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await request(app)
.get('/api/v1/steps/invalidFlowUUID/connection')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -0,0 +1,27 @@
import { ref } from 'objection';
import ExecutionStep from '../../../../models/execution-step.js';
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const step = await request.currentUser.authorizedSteps
.clone()
.findOne({ 'steps.id': request.params.stepId })
.throwIfNotFound();
const previousSteps = await request.currentUser.authorizedSteps
.clone()
.withGraphJoined('executionSteps')
.where('flow_id', '=', step.flowId)
.andWhere('position', '<', step.position)
.andWhere(
'executionSteps.created_at',
'=',
ExecutionStep.query()
.max('created_at')
.where('step_id', '=', ref('steps.id'))
.andWhere('status', 'success')
)
.orderBy('steps.position', 'asc');
renderObject(response, previousSteps);
};

View File

@@ -0,0 +1,173 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import Crypto from 'crypto';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createUser } from '../../../../../test/factories/user';
import { createFlow } from '../../../../../test/factories/flow';
import { createStep } from '../../../../../test/factories/step';
import { createExecutionStep } from '../../../../../test/factories/execution-step.js';
import { createPermission } from '../../../../../test/factories/permission';
import getPreviousStepsMock from '../../../../../test/mocks/rest/api/v1/steps/get-previous-steps';
describe('GET /api/v1/steps/:stepId/previous-steps', () => {
let currentUser, currentUserRole, token;
beforeEach(async () => {
currentUser = await createUser();
currentUserRole = await currentUser.$relatedQuery('role');
token = createAuthTokenByUserId(currentUser.id);
});
it('should return the previous steps of the specified step of the current user', async () => {
const currentUserflow = await createFlow({ userId: currentUser.id });
const triggerStep = await createStep({
flowId: currentUserflow.id,
type: 'trigger',
});
const actionStepOne = await createStep({
flowId: currentUserflow.id,
type: 'action',
});
const actionStepTwo = await createStep({
flowId: currentUserflow.id,
type: 'action',
});
const executionStepOne = await createExecutionStep({
stepId: triggerStep.id,
});
const executionStepTwo = await createExecutionStep({
stepId: actionStepOne.id,
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: ['isCreator'],
});
const response = await request(app)
.get(`/api/v1/steps/${actionStepTwo.id}/previous-steps`)
.set('Authorization', token)
.expect(200);
const expectedPayload = await getPreviousStepsMock(
[triggerStep, actionStepOne],
[executionStepOne, executionStepTwo]
);
expect(response.body).toEqual(expectedPayload);
});
it('should return the previous steps of the specified step of another user', async () => {
const anotherUser = await createUser();
const anotherUserFlow = await createFlow({ userId: anotherUser.id });
const triggerStep = await createStep({
flowId: anotherUserFlow.id,
type: 'trigger',
});
const actionStepOne = await createStep({
flowId: anotherUserFlow.id,
type: 'action',
});
const actionStepTwo = await createStep({
flowId: anotherUserFlow.id,
type: 'action',
});
const executionStepOne = await createExecutionStep({
stepId: triggerStep.id,
});
const executionStepTwo = await createExecutionStep({
stepId: actionStepOne.id,
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
const response = await request(app)
.get(`/api/v1/steps/${actionStepTwo.id}/previous-steps`)
.set('Authorization', token)
.expect(200);
const expectedPayload = await getPreviousStepsMock(
[triggerStep, actionStepOne],
[executionStepOne, executionStepTwo]
);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response for not existing step UUID', async () => {
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
const notExistingFlowUUID = Crypto.randomUUID();
await request(app)
.get(`/api/v1/steps/${notExistingFlowUUID}/previous-steps`)
.set('Authorization', token)
.expect(404);
});
it('should return bad request response for invalid UUID', async () => {
await createPermission({
action: 'update',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await createPermission({
action: 'read',
subject: 'Flow',
roleId: currentUserRole.id,
conditions: [],
});
await request(app)
.get('/api/v1/steps/invalidFlowUUID/previous-steps')
.set('Authorization', token)
.expect(400);
});
});

View File

@@ -2,15 +2,29 @@ import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createPermission } from '../../../../../test/factories/permission';
import { createRole } from '../../../../../test/factories/role';
import { createUser } from '../../../../../test/factories/user';
import getCurrentUserMock from '../../../../../test/mocks/rest/api/v1/users/get-current-user';
describe('GET /api/v1/users/me', () => {
let role, currentUser, token;
let role, permissionOne, permissionTwo, currentUser, token;
beforeEach(async () => {
currentUser = await createUser();
role = await currentUser.$relatedQuery('role');
role = await createRole();
permissionOne = await createPermission({
roleId: role.id,
});
permissionTwo = await createPermission({
roleId: role.id,
});
currentUser = await createUser({
roleId: role.id,
});
token = createAuthTokenByUserId(currentUser.id);
});
@@ -20,7 +34,11 @@ describe('GET /api/v1/users/me', () => {
.set('Authorization', token)
.expect(200);
const expectedPayload = getCurrentUserMock(currentUser, role);
const expectedPayload = getCurrentUserMock(currentUser, role, [
permissionOne,
permissionTwo,
]);
expect(response.body).toEqual(expectedPayload);
});
});

View File

@@ -0,0 +1,7 @@
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const planAndUsage = await request.currentUser.getPlanAndUsage();
renderObject(response, planAndUsage);
};

View File

@@ -0,0 +1,68 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id.js';
import { createUser } from '../../../../../test/factories/user.js';
import { createSubscription } from '../../../../../test/factories/subscription.js';
import { createUsageData } from '../../../../../test/factories/usage-data.js';
import appConfig from '../../../../config/app.js';
import { DateTime } from 'luxon';
describe('GET /api/v1/users/:userId/plan-and-usage', () => {
let user, token;
beforeEach(async () => {
const trialExpiryDate = DateTime.now().plus({ days: 30 }).toISODate();
user = await createUser({ trialExpiryDate });
token = createAuthTokenByUserId(user.id);
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true);
});
it('should return free trial plan and usage data', async () => {
const response = await request(app)
.get(`/api/v1/users/${user.id}/plan-and-usage`)
.set('Authorization', token)
.expect(200);
const expectedResponseData = {
plan: {
id: null,
limit: null,
name: 'Free Trial',
},
usage: {
task: 0,
},
};
expect(response.body.data).toEqual(expectedResponseData);
});
it('should return current plan and usage data', async () => {
await createSubscription({ userId: user.id });
await createUsageData({
userId: user.id,
consumedTaskCount: 1234,
});
const response = await request(app)
.get(`/api/v1/users/${user.id}/plan-and-usage`)
.set('Authorization', token)
.expect(200);
const expectedResponseData = {
plan: {
id: '47384',
limit: '10,000',
name: '10k - monthly',
},
usage: {
task: 1234,
},
};
expect(response.body.data).toEqual(expectedResponseData);
});
});

View File

@@ -0,0 +1,9 @@
import { renderObject } from '../../../../helpers/renderer.js';
export default async (request, response) => {
const subscription = await request.currentUser
.$relatedQuery('currentSubscription')
.throwIfNotFound();
renderObject(response, subscription);
};

View File

@@ -0,0 +1,51 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import appConfig from '../../../../config/app.js';
import app from '../../../../app.js';
import createAuthTokenByUserId from '../../../../helpers/create-auth-token-by-user-id';
import { createRole } from '../../../../../test/factories/role';
import { createUser } from '../../../../../test/factories/user';
import { createSubscription } from '../../../../../test/factories/subscription.js';
import getSubscriptionMock from '../../../../../test/mocks/rest/api/v1/users/get-subscription.js';
describe('GET /api/v1/users/:userId/subscription', () => {
let currentUser, role, subscription, token;
beforeEach(async () => {
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true);
role = await createRole();
currentUser = await createUser({
roleId: role.id,
});
subscription = await createSubscription({ userId: currentUser.id });
token = createAuthTokenByUserId(currentUser.id);
});
it('should return subscription info of the current user', async () => {
const response = await request(app)
.get(`/api/v1/users/${currentUser.id}/subscription`)
.set('Authorization', token)
.expect(200);
const expectedPayload = getSubscriptionMock(subscription);
expect(response.body).toEqual(expectedPayload);
});
it('should return not found response if there is no current subscription', async () => {
const userWithoutSubscription = await createUser({
roleId: role.id,
});
const token = createAuthTokenByUserId(userWithoutSubscription.id);
await request(app)
.get(`/api/v1/users/${userWithoutSubscription.id}/subscription`)
.set('Authorization', token)
.expect(404);
});
});

View File

@@ -0,0 +1,29 @@
import Flow from '../../models/flow.js';
import logger from '../../helpers/logger.js';
import handlerSync from '../../helpers/webhook-handler-sync.js';
export default async (request, response) => {
const computedRequestPayload = {
headers: request.headers,
body: request.body,
query: request.query,
params: request.params,
};
logger.debug(`Handling incoming webhook request at ${request.originalUrl}.`);
logger.debug(JSON.stringify(computedRequestPayload, null, 2));
const flowId = request.params.flowId;
const flow = await Flow.query().findById(flowId).throwIfNotFound();
const triggerStep = await flow.getTriggerStep();
if (triggerStep.appKey !== 'webhook') {
const connection = await triggerStep.$relatedQuery('connection');
if (!(await connection.verifyWebhook(request))) {
return response.sendStatus(401);
}
}
await handlerSync(flowId, request, response);
};

View File

@@ -0,0 +1,11 @@
export async function up(knex) {
await knex.schema.table('app_auth_clients', (table) => {
table.string('app_key');
});
}
export async function down(knex) {
await knex.schema.table('app_auth_clients', (table) => {
table.dropColumn('app_key');
});
}

View File

@@ -0,0 +1,17 @@
export async function up(knex) {
const appAuthClients = await knex('app_auth_clients').select('*');
for (const appAuthClient of appAuthClients) {
const appConfig = await knex('app_configs')
.where('id', appAuthClient.app_config_id)
.first();
await knex('app_auth_clients')
.where('id', appAuthClient.id)
.update({ app_key: appConfig.key });
}
}
export async function down() {
// void
}

View File

@@ -0,0 +1,15 @@
export async function up(knex) {
await knex.schema.table('app_auth_clients', (table) => {
table.dropColumn('app_config_id');
});
}
export async function down(knex) {
await knex.schema.table('app_auth_clients', (table) => {
table
.uuid('app_config_id')
.notNullable()
.references('id')
.inTable('app_configs');
});
}

View File

@@ -0,0 +1,11 @@
export async function up(knex) {
await knex.schema.table('app_auth_clients', (table) => {
table.string('app_key').notNullable().alter();
});
}
export async function down(knex) {
await knex.schema.table('app_auth_clients', (table) => {
table.string('app_key').nullable().alter();
});
}

View File

@@ -0,0 +1,11 @@
export async function up(knex) {
await knex.schema.table('app_configs', (table) => {
table.boolean('can_connect').defaultTo(false);
});
}
export async function down(knex) {
await knex.schema.table('app_configs', (table) => {
table.dropColumn('can_connect');
});
}

View File

@@ -1,4 +1,4 @@
import Step from '../../models/flow.js';
import Step from '../../models/step.js';
const deleteStep = async (_parent, params, context) => {
const conditions = context.currentUser.can('update', 'Flow');

View File

@@ -1,17 +0,0 @@
import AppConfig from '../../models/app-config.js';
const getAppConfig = async (_parent, params, context) => {
context.currentUser.can('create', 'Connection');
const appConfig = await AppConfig.query()
.withGraphFetched({
appAuthClients: true,
})
.findOne({
key: params.key,
});
return appConfig;
};
export default getAppConfig;

View File

@@ -1,17 +0,0 @@
import App from '../../models/app.js';
const getApps = async (_parent, params) => {
const apps = await App.findAll(params.name);
if (params.onlyWithTriggers) {
return apps.filter((app) => app.triggers?.length);
}
if (params.onlyWithActions) {
return apps.filter((app) => app.actions?.length);
}
return apps;
};
export default getApps;

View File

@@ -1,21 +0,0 @@
import appConfig from '../../config/app.js';
import { getLicense } from '../../helpers/license.ee.js';
const getAutomatischInfo = async () => {
const license = await getLicense();
const computedLicense = {
id: license ? license.id : null,
name: license ? license.name : null,
expireAt: license ? license.expireAt : null,
verified: license ? true : false,
};
return {
isCloud: appConfig.isCloud,
isMation: appConfig.isMation,
license: computedLicense,
};
};
export default getAutomatischInfo;

View File

@@ -1,190 +0,0 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../app';
import * as license from '../../helpers/license.ee';
import appConfig from '../../config/app';
describe('graphQL getAutomatischInfo query', () => {
const query = `
query {
getAutomatischInfo {
isCloud
isMation
license {
id
name
expireAt
verified
}
}
}
`;
describe('and without valid license', () => {
beforeEach(async () => {
vi.spyOn(license, 'getLicense').mockResolvedValue(false);
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(false);
vi.spyOn(appConfig, 'isMation', 'get').mockReturnValue(false);
});
it('should return empty license data', async () => {
const response = await request(app)
.post('/graphql')
.send({ query })
.expect(200);
const expectedResponsePayload = {
data: {
getAutomatischInfo: {
isCloud: false,
isMation: false,
license: {
id: null,
name: null,
expireAt: null,
verified: false,
},
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
});
describe('and with valid license', () => {
beforeEach(async () => {
const mockedLicense = {
id: '123123',
name: 'Test License',
expireAt: '2025-08-09T10:56:54.144Z',
verified: true,
};
vi.spyOn(license, 'getLicense').mockResolvedValue(mockedLicense);
});
describe('and with cloud flag enabled', () => {
beforeEach(async () => {
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(true);
});
it('should return all license data', async () => {
const response = await request(app)
.post('/graphql')
.send({ query })
.expect(200);
const expectedResponsePayload = {
data: {
getAutomatischInfo: {
isCloud: true,
isMation: false,
license: {
expireAt: '2025-08-09T10:56:54.144Z',
id: '123123',
name: 'Test License',
verified: true,
},
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
});
describe('and with cloud flag disabled', () => {
beforeEach(async () => {
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(false);
});
it('should return all license data', async () => {
const response = await request(app)
.post('/graphql')
.send({ query })
.expect(200);
const expectedResponsePayload = {
data: {
getAutomatischInfo: {
isCloud: false,
isMation: false,
license: {
expireAt: '2025-08-09T10:56:54.144Z',
id: '123123',
name: 'Test License',
verified: true,
},
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
});
describe('and with mation flag enabled', () => {
beforeEach(async () => {
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(false);
vi.spyOn(appConfig, 'isMation', 'get').mockReturnValue(true);
});
it('should return all license data', async () => {
const response = await request(app)
.post('/graphql')
.send({ query })
.expect(200);
const expectedResponsePayload = {
data: {
getAutomatischInfo: {
isCloud: false,
isMation: true,
license: {
expireAt: '2025-08-09T10:56:54.144Z',
id: '123123',
name: 'Test License',
verified: true,
},
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
});
describe('and with mation flag disabled', () => {
beforeEach(async () => {
vi.spyOn(appConfig, 'isCloud', 'get').mockReturnValue(false);
vi.spyOn(appConfig, 'isMation', 'get').mockReturnValue(false);
});
it('should return all license data', async () => {
const response = await request(app)
.post('/graphql')
.send({ query })
.expect(200);
const expectedResponsePayload = {
data: {
getAutomatischInfo: {
isMation: false,
isCloud: false,
license: {
expireAt: '2025-08-09T10:56:54.144Z',
id: '123123',
name: 'Test License',
verified: true,
},
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
});
});
});

View File

@@ -1,32 +0,0 @@
import appConfig from '../../config/app.js';
import { hasValidLicense } from '../../helpers/license.ee.js';
import Config from '../../models/config.js';
const getConfig = async (_parent, params) => {
if (!(await hasValidLicense())) return {};
const defaultConfig = {
disableNotificationsPage: appConfig.disableNotificationsPage,
disableFavicon: appConfig.disableFavicon,
additionalDrawerLink: appConfig.additionalDrawerLink,
additionalDrawerLinkText: appConfig.additionalDrawerLinkText,
};
const configQuery = Config.query();
if (Array.isArray(params.keys)) {
configQuery.whereIn('key', params.keys);
}
const config = await configQuery.orderBy('key', 'asc');
return config.reduce((computedConfig, configEntry) => {
const { key, value } = configEntry;
computedConfig[key] = value?.data;
return computedConfig;
}, defaultConfig);
};
export default getConfig;

View File

@@ -1,140 +0,0 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../app';
import { createConfig } from '../../../test/factories/config';
import appConfig from '../../config/app';
import * as license from '../../helpers/license.ee';
describe('graphQL getConfig query', () => {
let configOne, configTwo, configThree, query;
beforeEach(async () => {
configOne = await createConfig({ key: 'configOne' });
configTwo = await createConfig({ key: 'configTwo' });
configThree = await createConfig({ key: 'configThree' });
query = `
query {
getConfig
}
`;
});
describe('and without valid license', () => {
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(false);
});
describe('and correct permissions', () => {
it('should return empty config data', async () => {
const response = await request(app)
.post('/graphql')
.send({ query })
.expect(200);
const expectedResponsePayload = { data: { getConfig: {} } };
expect(response.body).toEqual(expectedResponsePayload);
});
});
});
describe('and with valid license', () => {
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
});
describe('and without providing specific keys', () => {
it('should return all config data', async () => {
const response = await request(app)
.post('/graphql')
.send({ query })
.expect(200);
const expectedResponsePayload = {
data: {
getConfig: {
[configOne.key]: configOne.value.data,
[configTwo.key]: configTwo.value.data,
[configThree.key]: configThree.value.data,
disableNotificationsPage: false,
disableFavicon: false,
additionalDrawerLink: undefined,
additionalDrawerLinkText: undefined,
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
});
describe('and with providing specific keys', () => {
it('should return all config data', async () => {
query = `
query {
getConfig(keys: ["configOne", "configTwo"])
}
`;
const response = await request(app)
.post('/graphql')
.send({ query })
.expect(200);
const expectedResponsePayload = {
data: {
getConfig: {
[configOne.key]: configOne.value.data,
[configTwo.key]: configTwo.value.data,
disableNotificationsPage: false,
disableFavicon: false,
additionalDrawerLink: undefined,
additionalDrawerLinkText: undefined,
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
});
describe('and with different defaults', () => {
beforeEach(async () => {
vi.spyOn(appConfig, 'disableNotificationsPage', 'get').mockReturnValue(
true
);
vi.spyOn(appConfig, 'disableFavicon', 'get').mockReturnValue(true);
vi.spyOn(appConfig, 'additionalDrawerLink', 'get').mockReturnValue(
'https://automatisch.io'
);
vi.spyOn(appConfig, 'additionalDrawerLinkText', 'get').mockReturnValue(
'Automatisch'
);
});
it('should return custom config', async () => {
const response = await request(app)
.post('/graphql')
.send({ query })
.expect(200);
const expectedResponsePayload = {
data: {
getConfig: {
[configOne.key]: configOne.value.data,
[configTwo.key]: configTwo.value.data,
[configThree.key]: configThree.value.data,
disableNotificationsPage: true,
disableFavicon: true,
additionalDrawerLink: 'https://automatisch.io',
additionalDrawerLinkText: 'Automatisch',
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
});
});
});

View File

@@ -1,5 +0,0 @@
const getCurrentUser = async (_parent, _params, context) => {
return context.currentUser;
};
export default getCurrentUser;

View File

@@ -1,79 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../app';
import createAuthTokenByUserId from '../../helpers/create-auth-token-by-user-id';
import { createRole } from '../../../test/factories/role';
import { createUser } from '../../../test/factories/user';
describe('graphQL getCurrentUser query', () => {
let role, currentUser, token, requestObject;
beforeEach(async () => {
role = await createRole({
key: 'sample',
name: 'sample',
});
currentUser = await createUser({
roleId: role.id,
});
token = createAuthTokenByUserId(currentUser.id);
requestObject = request(app).post('/graphql').set('Authorization', token);
});
it('should return user data', async () => {
const query = `
query {
getCurrentUser {
id
email
fullName
email
createdAt
updatedAt
role {
id
name
}
}
}
`;
const response = await requestObject.send({ query }).expect(200);
const expectedResponsePayload = {
data: {
getCurrentUser: {
createdAt: currentUser.createdAt.getTime().toString(),
email: currentUser.email,
fullName: currentUser.fullName,
id: currentUser.id,
role: { id: role.id, name: role.name },
updatedAt: currentUser.updatedAt.getTime().toString(),
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
it('should not return user password', async () => {
const query = `
query {
getCurrentUser {
id
email
password
}
}
`;
const response = await requestObject.send({ query }).expect(400);
expect(response.body.errors).toBeDefined();
expect(response.body.errors[0].message).toEqual(
'Cannot query field "password" on type "User".'
);
});
});

View File

@@ -1,40 +0,0 @@
import App from '../../models/app.js';
import Step from '../../models/step.js';
import globalVariable from '../../helpers/global-variable.js';
const getDynamicFields = async (_parent, params, context) => {
const conditions = context.currentUser.can('update', 'Flow');
const userSteps = context.currentUser.$relatedQuery('steps');
const allSteps = Step.query();
const stepBaseQuery = conditions.isCreator ? userSteps : allSteps;
const step = await stepBaseQuery
.clone()
.withGraphFetched({
connection: true,
flow: true,
})
.findById(params.stepId);
if (!step) return null;
const connection = step.connection;
if (!step.appKey) return null;
const app = await App.findOneByKey(step.appKey);
const $ = await globalVariable({ connection, app, flow: step.flow, step });
const command = app.dynamicFields.find((data) => data.key === params.key);
for (const parameterKey in params.parameters) {
const parameterValue = params.parameters[parameterKey];
$.step.parameters[parameterKey] = parameterValue;
}
const additionalFields = (await command.run($)) || [];
return additionalFields;
};
export default getDynamicFields;

View File

@@ -1,27 +0,0 @@
import paginate from '../../helpers/pagination.js';
import Execution from '../../models/execution.js';
const getExecutionSteps = async (_parent, params, context) => {
const conditions = context.currentUser.can('read', 'Execution');
const userExecutions = context.currentUser.$relatedQuery('executions');
const allExecutions = Execution.query();
const executionBaseQuery = conditions.isCreator
? userExecutions
: allExecutions;
const execution = await executionBaseQuery
.clone()
.withSoftDeleted()
.findById(params.executionId)
.throwIfNotFound();
const executionSteps = execution
.$relatedQuery('executionSteps')
.withSoftDeleted()
.withGraphFetched('step')
.orderBy('created_at', 'asc');
return paginate(executionSteps, params.limit, params.offset);
};
export default getExecutionSteps;

View File

@@ -1,25 +0,0 @@
import Execution from '../../models/execution.js';
const getExecution = async (_parent, params, context) => {
const conditions = context.currentUser.can('read', 'Execution');
const userExecutions = context.currentUser.$relatedQuery('executions');
const allExecutions = Execution.query();
const executionBaseQuery = conditions.isCreator
? userExecutions
: allExecutions;
const execution = await executionBaseQuery
.clone()
.withGraphFetched({
flow: {
steps: true,
},
})
.withSoftDeleted()
.findById(params.executionId)
.throwIfNotFound();
return execution;
};
export default getExecution;

View File

@@ -1,70 +0,0 @@
import { raw } from 'objection';
import { DateTime } from 'luxon';
import Execution from '../../models/execution.js';
import paginate from '../../helpers/pagination.js';
const getExecutions = async (_parent, params, context) => {
const conditions = context.currentUser.can('read', 'Execution');
const filters = params.filters;
const userExecutions = context.currentUser.$relatedQuery('executions');
const allExecutions = Execution.query();
const executionBaseQuery = conditions.isCreator
? userExecutions
: allExecutions;
const selectStatusStatement = `
case
when count(*) filter (where execution_steps.status = 'failure') > 0
then 'failure'
else 'success'
end
as status
`;
const executions = executionBaseQuery
.clone()
.joinRelated('executionSteps as execution_steps')
.select('executions.*', raw(selectStatusStatement))
.groupBy('executions.id')
.orderBy('created_at', 'desc');
const computedExecutions = Execution.query()
.with('executions', executions)
.withSoftDeleted()
.withGraphFetched({
flow: {
steps: true,
},
});
if (filters?.flowId) {
computedExecutions.where('executions.flow_id', filters.flowId);
}
if (filters?.status) {
computedExecutions.where('executions.status', filters.status);
}
if (filters?.createdAt) {
const createdAtFilter = filters.createdAt;
if (createdAtFilter.from) {
const isoFromDateTime = DateTime.fromMillis(
parseInt(createdAtFilter.from, 10)
).toISO();
computedExecutions.where('executions.created_at', '>=', isoFromDateTime);
}
if (createdAtFilter.to) {
const isoToDateTime = DateTime.fromMillis(
parseInt(createdAtFilter.to, 10)
).toISO();
computedExecutions.where('executions.created_at', '<=', isoToDateTime);
}
}
return paginate(computedExecutions, params.limit, params.offset);
};
export default getExecutions;

View File

@@ -1,472 +0,0 @@
import { describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../app';
import appConfig from '../../config/app';
import createAuthTokenByUserId from '../../helpers/create-auth-token-by-user-id';
import { createRole } from '../../../test/factories/role';
import { createPermission } from '../../../test/factories/permission';
import { createUser } from '../../../test/factories/user';
import { createFlow } from '../../../test/factories/flow';
import { createStep } from '../../../test/factories/step';
import { createExecution } from '../../../test/factories/execution';
import { createExecutionStep } from '../../../test/factories/execution-step';
describe('graphQL getExecutions query', () => {
const query = `
query {
getExecutions(limit: 10, offset: 0) {
pageInfo {
currentPage
totalPages
}
edges {
node {
id
testRun
createdAt
updatedAt
status
flow {
id
name
active
steps {
iconUrl
}
}
}
}
}
}
`;
describe('and without correct permissions', () => {
it('should throw not authorized error', async () => {
const userWithoutPermissions = await createUser();
const token = createAuthTokenByUserId(userWithoutPermissions.id);
const response = await request(app)
.post('/graphql')
.set('Authorization', token)
.send({ query })
.expect(200);
expect(response.body.errors).toBeDefined();
expect(response.body.errors[0].message).toEqual('Not authorized!');
});
});
describe('and with correct permission', () => {
let role,
currentUser,
anotherUser,
token,
flowOne,
stepOneForFlowOne,
stepTwoForFlowOne,
executionOne,
flowTwo,
stepOneForFlowTwo,
stepTwoForFlowTwo,
executionTwo,
flowThree,
stepOneForFlowThree,
stepTwoForFlowThree,
executionThree,
expectedResponseForExecutionOne,
expectedResponseForExecutionTwo,
expectedResponseForExecutionThree;
beforeEach(async () => {
role = await createRole({
key: 'sample',
name: 'sample',
});
currentUser = await createUser({
roleId: role.id,
fullName: 'Current User',
});
anotherUser = await createUser();
token = createAuthTokenByUserId(currentUser.id);
flowOne = await createFlow({
userId: currentUser.id,
});
stepOneForFlowOne = await createStep({
flowId: flowOne.id,
});
stepTwoForFlowOne = await createStep({
flowId: flowOne.id,
});
executionOne = await createExecution({
flowId: flowOne.id,
});
await createExecutionStep({
executionId: executionOne.id,
stepId: stepOneForFlowOne.id,
status: 'success',
});
await createExecutionStep({
executionId: executionOne.id,
stepId: stepTwoForFlowOne.id,
status: 'success',
});
flowTwo = await createFlow({
userId: currentUser.id,
});
stepOneForFlowTwo = await createStep({
flowId: flowTwo.id,
});
stepTwoForFlowTwo = await createStep({
flowId: flowTwo.id,
});
executionTwo = await createExecution({
flowId: flowTwo.id,
});
await createExecutionStep({
executionId: executionTwo.id,
stepId: stepOneForFlowTwo.id,
status: 'success',
});
await createExecutionStep({
executionId: executionTwo.id,
stepId: stepTwoForFlowTwo.id,
status: 'failure',
});
flowThree = await createFlow({
userId: anotherUser.id,
});
stepOneForFlowThree = await createStep({
flowId: flowThree.id,
});
stepTwoForFlowThree = await createStep({
flowId: flowThree.id,
});
executionThree = await createExecution({
flowId: flowThree.id,
});
await createExecutionStep({
executionId: executionThree.id,
stepId: stepOneForFlowThree.id,
status: 'success',
});
await createExecutionStep({
executionId: executionThree.id,
stepId: stepTwoForFlowThree.id,
status: 'failure',
});
expectedResponseForExecutionOne = {
node: {
createdAt: executionOne.createdAt.getTime().toString(),
flow: {
active: flowOne.active,
id: flowOne.id,
name: flowOne.name,
steps: [
{
iconUrl: `${appConfig.baseUrl}/apps/${stepOneForFlowOne.appKey}/assets/favicon.svg`,
},
{
iconUrl: `${appConfig.baseUrl}/apps/${stepTwoForFlowOne.appKey}/assets/favicon.svg`,
},
],
},
id: executionOne.id,
status: 'success',
testRun: executionOne.testRun,
updatedAt: executionOne.updatedAt.getTime().toString(),
},
};
expectedResponseForExecutionTwo = {
node: {
createdAt: executionTwo.createdAt.getTime().toString(),
flow: {
active: flowTwo.active,
id: flowTwo.id,
name: flowTwo.name,
steps: [
{
iconUrl: `${appConfig.baseUrl}/apps/${stepTwoForFlowTwo.appKey}/assets/favicon.svg`,
},
{
iconUrl: `${appConfig.baseUrl}/apps/${stepTwoForFlowTwo.appKey}/assets/favicon.svg`,
},
],
},
id: executionTwo.id,
status: 'failure',
testRun: executionTwo.testRun,
updatedAt: executionTwo.updatedAt.getTime().toString(),
},
};
expectedResponseForExecutionThree = {
node: {
createdAt: executionThree.createdAt.getTime().toString(),
flow: {
active: flowThree.active,
id: flowThree.id,
name: flowThree.name,
steps: [
{
iconUrl: `${appConfig.baseUrl}/apps/${stepOneForFlowThree.appKey}/assets/favicon.svg`,
},
{
iconUrl: `${appConfig.baseUrl}/apps/${stepTwoForFlowThree.appKey}/assets/favicon.svg`,
},
],
},
id: executionThree.id,
status: 'failure',
testRun: executionThree.testRun,
updatedAt: executionThree.updatedAt.getTime().toString(),
},
};
});
describe('and with isCreator condition', () => {
beforeEach(async () => {
await createPermission({
action: 'read',
subject: 'Execution',
roleId: role.id,
conditions: ['isCreator'],
});
});
it('should return executions data of the current user', async () => {
const response = await request(app)
.post('/graphql')
.set('Authorization', token)
.send({ query })
.expect(200);
const expectedResponsePayload = {
data: {
getExecutions: {
edges: [
expectedResponseForExecutionTwo,
expectedResponseForExecutionOne,
],
pageInfo: { currentPage: 1, totalPages: 1 },
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
});
describe('and without isCreator condition', () => {
beforeEach(async () => {
await createPermission({
action: 'read',
subject: 'Execution',
roleId: role.id,
conditions: [],
});
});
it('should return executions data of all users', async () => {
const response = await request(app)
.post('/graphql')
.set('Authorization', token)
.send({ query })
.expect(200);
const expectedResponsePayload = {
data: {
getExecutions: {
edges: [
expectedResponseForExecutionThree,
expectedResponseForExecutionTwo,
expectedResponseForExecutionOne,
],
pageInfo: { currentPage: 1, totalPages: 1 },
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
});
describe('and with filters', () => {
beforeEach(async () => {
await createPermission({
action: 'read',
subject: 'Execution',
roleId: role.id,
conditions: [],
});
});
it('should return executions data for the specified flow', async () => {
const query = `
query {
getExecutions(limit: 10, offset: 0, filters: { flowId: "${flowOne.id}" }) {
pageInfo {
currentPage
totalPages
}
edges {
node {
id
testRun
createdAt
updatedAt
status
flow {
id
name
active
steps {
iconUrl
}
}
}
}
}
}
`;
const response = await request(app)
.post('/graphql')
.set('Authorization', token)
.send({ query })
.expect(200);
const expectedResponsePayload = {
data: {
getExecutions: {
edges: [expectedResponseForExecutionOne],
pageInfo: { currentPage: 1, totalPages: 1 },
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
it('should return only executions data with success status', async () => {
const query = `
query {
getExecutions(limit: 10, offset: 0, filters: { status: "success" }) {
pageInfo {
currentPage
totalPages
}
edges {
node {
id
testRun
createdAt
updatedAt
status
flow {
id
name
active
steps {
iconUrl
}
}
}
}
}
}
`;
const response = await request(app)
.post('/graphql')
.set('Authorization', token)
.send({ query })
.expect(200);
const expectedResponsePayload = {
data: {
getExecutions: {
edges: [expectedResponseForExecutionOne],
pageInfo: { currentPage: 1, totalPages: 1 },
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
it('should return only executions data within date range', async () => {
const createdAtFrom = executionOne.createdAt.getTime().toString();
const createdAtTo = executionOne.createdAt.getTime().toString();
const query = `
query {
getExecutions(limit: 10, offset: 0, filters: { createdAt: { from: "${createdAtFrom}", to: "${createdAtTo}" }}) {
pageInfo {
currentPage
totalPages
}
edges {
node {
id
testRun
createdAt
updatedAt
status
flow {
id
name
active
steps {
iconUrl
}
}
}
}
}
}
`;
const response = await request(app)
.post('/graphql')
.set('Authorization', token)
.send({ query })
.expect(200);
const expectedResponsePayload = {
data: {
getExecutions: {
edges: [expectedResponseForExecutionOne],
pageInfo: { currentPage: 1, totalPages: 1 },
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
});
});
});

View File

@@ -136,7 +136,7 @@ describe('graphQL getFlow query', () => {
id: actionStep.id,
key: 'translateText',
parameters: {},
position: 1,
position: 2,
status: actionStep.status,
type: 'action',
webhookUrl: 'http://localhost:3000/null',
@@ -223,7 +223,7 @@ describe('graphQL getFlow query', () => {
id: actionStep.id,
key: 'translateText',
parameters: {},
position: 1,
position: 2,
status: actionStep.status,
type: 'action',
webhookUrl: 'http://localhost:3000/null',

View File

@@ -1,40 +0,0 @@
import Flow from '../../models/flow.js';
import paginate from '../../helpers/pagination.js';
const getFlows = async (_parent, params, context) => {
const conditions = context.currentUser.can('read', 'Flow');
const userFlows = context.currentUser.$relatedQuery('flows');
const allFlows = Flow.query();
const baseQuery = conditions.isCreator ? userFlows : allFlows;
const flowsQuery = baseQuery
.clone()
.joinRelated({
steps: true,
})
.withGraphFetched({
steps: {
connection: true,
},
})
.where((builder) => {
if (params.connectionId) {
builder.where('steps.connection_id', params.connectionId);
}
if (params.name) {
builder.where('flows.name', 'ilike', `%${params.name}%`);
}
if (params.appKey) {
builder.where('steps.app_key', params.appKey);
}
})
.groupBy('flows.id')
.orderBy('active', 'desc')
.orderBy('updated_at', 'desc');
return paginate(flowsQuery, params.limit, params.offset);
};
export default getFlows;

View File

@@ -1,19 +0,0 @@
import Billing from '../../helpers/billing/index.ee.js';
const getInvoices = async (_parent, _params, context) => {
const subscription = await context.currentUser.$relatedQuery(
'currentSubscription'
);
if (!subscription) {
return;
}
const invoices = await Billing.paddleClient.getInvoices(
Number(subscription.paddleSubscriptionId)
);
return invoices;
};
export default getInvoices;

View File

@@ -1,16 +0,0 @@
import axios from '../../helpers/axios-with-proxy.js';
const NOTIFICATIONS_URL =
'https://notifications.automatisch.io/notifications.json';
const getNotifications = async () => {
try {
const { data: notifications = [] } = await axios.get(NOTIFICATIONS_URL);
return notifications;
} catch (err) {
return [];
}
};
export default getNotifications;

View File

@@ -1,10 +0,0 @@
import appConfig from '../../config/app.js';
import Billing from '../../helpers/billing/index.ee.js';
const getPaddleInfo = async () => {
if (!appConfig.isCloud) return;
return Billing.paddleInfo;
};
export default getPaddleInfo;

View File

@@ -1,10 +0,0 @@
import appConfig from '../../config/app.js';
import Billing from '../../helpers/billing/index.ee.js';
const getPaymentPlans = async () => {
if (!appConfig.isCloud) return;
return Billing.paddlePlans;
};
export default getPaymentPlans;

View File

@@ -1,7 +0,0 @@
import permissionCatalog from '../../helpers/permission-catalog.ee.js';
const getPermissionCatalog = async () => {
return permissionCatalog;
};
export default getPermissionCatalog;

View File

@@ -1,17 +0,0 @@
import Role from '../../models/role.js';
const getRole = async (_parent, params, context) => {
context.currentUser.can('read', 'Role');
return await Role.query()
.leftJoinRelated({
permissions: true,
})
.withGraphFetched({
permissions: true,
})
.findById(params.id)
.throwIfNotFound();
};
export default getRole;

View File

@@ -1,164 +0,0 @@
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../app';
import createAuthTokenByUserId from '../../helpers/create-auth-token-by-user-id';
import Crypto from 'crypto';
import { createRole } from '../../../test/factories/role';
import { createPermission } from '../../../test/factories/permission';
import { createUser } from '../../../test/factories/user';
import * as license from '../../helpers/license.ee';
describe('graphQL getRole query', () => {
let validRole,
invalidRoleId,
queryWithValidRole,
queryWithInvalidRole,
userWithPermissions,
userWithoutPermissions,
tokenWithPermissions,
tokenWithoutPermissions,
permissionOne,
permissionTwo;
beforeEach(async () => {
validRole = await createRole();
invalidRoleId = Crypto.randomUUID();
queryWithValidRole = `
query {
getRole(id: "${validRole.id}") {
id
name
key
description
isAdmin
permissions {
id
action
subject
conditions
}
}
}
`;
queryWithInvalidRole = `
query {
getRole(id: "${invalidRoleId}") {
id
name
}
}
`;
permissionOne = await createPermission({
action: 'read',
subject: 'Role',
roleId: validRole.id,
});
permissionTwo = await createPermission({
action: 'read',
subject: 'User',
roleId: validRole.id,
});
userWithPermissions = await createUser({
roleId: validRole.id,
});
userWithoutPermissions = await createUser();
tokenWithPermissions = createAuthTokenByUserId(userWithPermissions.id);
tokenWithoutPermissions = createAuthTokenByUserId(
userWithoutPermissions.id
);
});
describe('and with valid license', () => {
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
});
describe('and without permissions', () => {
it('should throw not authorized error', async () => {
const response = await request(app)
.post('/graphql')
.set('Authorization', tokenWithoutPermissions)
.send({ query: queryWithValidRole })
.expect(200);
expect(response.body.errors).toBeDefined();
expect(response.body.errors[0].message).toEqual('Not authorized!');
});
});
describe('and correct permissions', () => {
it('should return role data for a valid role id', async () => {
const response = await request(app)
.post('/graphql')
.set('Authorization', tokenWithPermissions)
.send({ query: queryWithValidRole })
.expect(200);
const expectedResponsePayload = {
data: {
getRole: {
description: validRole.description,
id: validRole.id,
isAdmin: validRole.key === 'admin',
key: validRole.key,
name: validRole.name,
permissions: [
{
action: permissionOne.action,
conditions: permissionOne.conditions,
id: permissionOne.id,
subject: permissionOne.subject,
},
{
action: permissionTwo.action,
conditions: permissionTwo.conditions,
id: permissionTwo.id,
subject: permissionTwo.subject,
},
],
},
},
};
expect(response.body).toEqual(expectedResponsePayload);
});
it('should return not found for invalid role id', async () => {
const response = await request(app)
.post('/graphql')
.set('Authorization', tokenWithPermissions)
.send({ query: queryWithInvalidRole })
.expect(200);
expect(response.body.errors).toBeDefined();
expect(response.body.errors[0].message).toEqual('NotFoundError');
});
});
});
describe('and without valid license', () => {
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(false);
});
describe('and correct permissions', () => {
it('should throw not authorized error', async () => {
const response = await request(app)
.post('/graphql')
.set('Authorization', tokenWithPermissions)
.send({ query: queryWithInvalidRole })
.expect(200);
expect(response.body.errors).toBeDefined();
expect(response.body.errors[0].message).toEqual('Not authorized!');
});
});
});
});

View File

@@ -1,9 +0,0 @@
import Role from '../../models/role.js';
const getRoles = async (_parent, params, context) => {
context.currentUser.can('read', 'Role');
return await Role.query().orderBy('name');
};
export default getRoles;

Some files were not shown because too many files have changed in this diff Show More