Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: Add permissions and roles e2e tests #4172

Open
wants to merge 67 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
c512014
test: Add permissions and roles e2e tests
novakzaballa Jun 12, 2024
cbdb48e
Update project-permissio-test
novakzaballa Jun 13, 2024
e13a27a
Update project and environments permissions
novakzaballa Jun 14, 2024
801e406
Add data-test property
novakzaballa Jun 18, 2024
10d2538
Role test
novakzaballa Jun 18, 2024
24e881d
Update the role test, and include permission and role test in the wor…
novakzaballa Jun 19, 2024
4a44fec
solve merge issues
novakzaballa Aug 12, 2024
6bb85fe
Merge branch 'main' into test/add-permissions-and-roles-e2e-tests
novakzaballa Aug 12, 2024
8db66c4
Merge branch 'main' into test/add-permissions-and-roles-e2e-tests
novakzaballa Aug 13, 2024
0cbc289
Solve roles test
novakzaballa Aug 15, 2024
83b3b2e
Add tag based permissions
kyle-ssg Sep 18, 2024
e838de4
Merge branch 'refs/heads/main' into test/add-permissions-and-roles-e2…
matthewelwell Oct 7, 2024
e188231
Post merge additions
matthewelwell Oct 7, 2024
c8d4532
Merge branch 'refs/heads/main' into feat/tag-based-permissions
kyle-ssg Oct 8, 2024
58e6478
Merge
kyle-ssg Oct 8, 2024
dcef5ae
Tag based permissions flag
kyle-ssg Nov 5, 2024
82519f2
Add tag based permissions ui
kyle-ssg Nov 5, 2024
716706b
tag based role ui
kyle-ssg Nov 6, 2024
2c71cfa
Add tag_based_permissions logic to frontend
kyle-ssg Nov 6, 2024
3634609
Merge branch 'refs/heads/main' into feat/tag-based-permissions-valida…
kyle-ssg Nov 12, 2024
985cceb
Adjust tag base permissions data structure
kyle-ssg Nov 12, 2024
c09d39b
Adjust tag base permissions data structure
kyle-ssg Nov 12, 2024
a940e9a
Better permissions view for create feature
kyle-ssg Nov 12, 2024
750054b
Merge branch 'refs/heads/main' into feat/tag-based-permissions
kyle-ssg Nov 12, 2024
1837651
Merge branch 'refs/heads/main' into feat/tag-based-permissions
kyle-ssg Nov 12, 2024
48d54c5
Remove static tag_based variable
kyle-ssg Nov 12, 2024
f962c1c
Remove incorrect function call
kyle-ssg Nov 12, 2024
243c7a4
Adjust doc wording
kyle-ssg Nov 12, 2024
9ad625f
Merge branch 'refs/heads/main' into feat/tag-based-permissions
kyle-ssg Nov 13, 2024
bf5d177
Permissions v2
kyle-ssg Nov 13, 2024
72e5d8c
Tag based permissions UI
kyle-ssg Nov 19, 2024
195c400
Merge branch 'refs/heads/main' into feat/tag-based-permissions-v2
kyle-ssg Nov 19, 2024
d2b7011
Interop edit permissions / role-based permissions
kyle-ssg Nov 19, 2024
8482f89
Adjust UI for limited permissions
kyle-ssg Nov 19, 2024
29051be
LIMITED > GRANTED_FOR_TAGS
kyle-ssg Nov 19, 2024
23f5d18
Merge branch 'refs/heads/main' into feat/tag-based-permissions-v2
kyle-ssg Nov 27, 2024
7b2f458
Fix
kyle-ssg Nov 27, 2024
050f305
Integrate new API
kyle-ssg Dec 3, 2024
ae40fbc
Merge branch 'refs/heads/main' into feat/tag-based-permissions-v2
kyle-ssg Dec 3, 2024
f347559
Update docs
kyle-ssg Dec 3, 2024
6d986f3
Merge remote-tracking branch 'refs/remotes/origin/main' into test/add…
kyle-ssg Dec 3, 2024
f3e6082
Update tests
kyle-ssg Dec 3, 2024
2753ab8
Merge remote-tracking branch 'refs/remotes/origin/feat/tag-based-perm…
kyle-ssg Dec 3, 2024
db27e30
Merge tag based roles
kyle-ssg Dec 3, 2024
1422ab9
Merge tag based roles
kyle-ssg Dec 3, 2024
af13ff7
add tests
kyle-ssg Dec 3, 2024
244a675
Add tests
kyle-ssg Dec 4, 2024
f450416
Merge branch 'refs/heads/main' into test/add-permissions-and-roles-e2…
kyle-ssg Dec 4, 2024
0b7b8da
more tests
kyle-ssg Dec 4, 2024
ec813ab
setuser permissions helper
kyle-ssg Dec 6, 2024
3c1a3f7
Merge branch 'refs/heads/main' into test/add-permissions-and-roles-e2…
kyle-ssg Dec 10, 2024
a7be3f7
Remove debugger
kyle-ssg Dec 10, 2024
bc71237
Fix type and tags copy
kyle-ssg Dec 10, 2024
08b5795
Add tests for all project permissions
kyle-ssg Dec 10, 2024
44246b5
re-add tests
kyle-ssg Dec 10, 2024
00f935b
re-add tests
kyle-ssg Dec 10, 2024
976d675
Re-add all tests
kyle-ssg Dec 17, 2024
3a59576
Environment tests
kyle-ssg Dec 17, 2024
b73808a
Environment tests
kyle-ssg Dec 17, 2024
c296e92
Add environment permissions
kyle-ssg Dec 17, 2024
186ef91
Add environment permissions
kyle-ssg Dec 17, 2024
8767c78
Merge branch 'refs/heads/main' into test/add-permissions-and-roles-e2…
kyle-ssg Dec 17, 2024
569fa9f
Don't fetch user overrides in edit feature if user has no permissions
kyle-ssg Dec 17, 2024
15edd44
More environment tests
kyle-ssg Dec 17, 2024
3543adc
More environment tests
kyle-ssg Dec 17, 2024
1e22b48
More environment tests
kyle-ssg Dec 17, 2024
2266221
feat: Run tests against private-cloud build on PRs and merge to main
khvn26 Dec 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/platform-docker-build-test-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ jobs:
concurrency: 2
- tests: versioning
concurrency: 1
- tests: organisation-permission environment-permission project-permission roles
concurrency: 1

docker-publish-api:
needs: [docker-build-api, run-e2e-tests]
Expand Down
20 changes: 20 additions & 0 deletions .github/workflows/platform-pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,23 @@ jobs:
concurrency: 2
- tests: versioning
concurrency: 1

run-e2e-tests-private-cloud:
if: needs.permissions-check.outputs.can-write == 'true' && !cancelled()
needs: [permissions-check, docker-build-private-cloud, docker-build-e2e]
uses: ./.github/workflows/.reusable-docker-e2e-tests.yml
with:
runs-on: ${{ matrix.runs-on }}
e2e-image: ${{ needs.docker-build-e2e.outputs.image }}
api-image: ${{ needs.docker-build-private-cloud.outputs.image }}
concurrency: ${{ matrix.args.concurrency }}
tests: ${{ matrix.args.tests }}
secrets:
gcr-token: ${{ needs.permissions-check.outputs.can-write == 'true' && secrets.GITHUB_TOKEN || '' }}

strategy:
matrix:
runs-on: [ubuntu-latest, ARM64-2c]
args:
- tests: organisation-permission environment-permission project-permission roles
concurrency: 1
1 change: 1 addition & 0 deletions frontend/common/stores/organisation-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,7 @@ store.dispatcherIndex = Dispatcher.register(store, (payload) => {
controller.invalidateInviteLink(action.link)
break
case Actions.LOGOUT:
store.model = null
store.id = null
break
default:
Expand Down
3 changes: 3 additions & 0 deletions frontend/common/stores/project-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,9 @@ store.dispatcherIndex = Dispatcher.register(store, (payload) => {
case Actions.EDIT_PROJECT:
controller.editProject(action.id, action.project)
break
case Actions.LOGOUT:
store.model = null
break
default:
}
})
Expand Down
7 changes: 7 additions & 0 deletions frontend/e2e/config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
const E2E_EMAIL_DOMAIN = 'flagsmithe2etestdomain.io'
export const E2E_SIGN_UP_USER = `e2e_signup_user@${E2E_EMAIL_DOMAIN}`
export const E2E_USER = `e2e_user@${E2E_EMAIL_DOMAIN}`
export const E2E_NON_ADMIN_USER = `e2e_user@${E2E_EMAIL_DOMAIN}`
//e2e_non_admin_user_with_project_permissions@flagsmithe2etestdomain.io
export const E2E_NON_ADMIN_USER_WITH_ORG_PERMISSIONS = `e2e_non_admin_user_with_org_permissions@${E2E_EMAIL_DOMAIN}`
export const E2E_NON_ADMIN_USER_WITH_PROJECT_PERMISSIONS = `e2e_non_admin_user_with_project_permissions@${E2E_EMAIL_DOMAIN}`
export const E2E_NON_ADMIN_USER_WITH_PROJECT_READ_PERMISSIONS = `e2e_non_admin_user_with_project_read_permissions@${E2E_EMAIL_DOMAIN}`
export const E2E_NON_ADMIN_USER_WITH_ENV_PERMISSIONS = `e2e_non_admin_user_with_env_permissions@${E2E_EMAIL_DOMAIN}`
export const E2E_NON_ADMIN_USER_WITH_A_ROLE = `e2e_non_admin_user_with_a_role@${E2E_EMAIL_DOMAIN}`
export const E2E_CHANGE_MAIL = `e2e_change_email@${E2E_EMAIL_DOMAIN}`
export const PASSWORD = 'Str0ngp4ssw0rd!'
87 changes: 86 additions & 1 deletion frontend/e2e/helpers.cafe.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { RequestLogger, Selector, t } from 'testcafe'
import { FlagsmithValue } from '../common/types/responses';
import { E2E_USER, PASSWORD } from './config';
import { cli } from 'yaml/dist/cli';

export const LONG_TIMEOUT = 40000

Expand Down Expand Up @@ -35,6 +37,24 @@ export const waitForElementVisible = async (selector: string) => {
.ok(`waitForElementVisible(${selector})`, { timeout: LONG_TIMEOUT })
}

export const waitForElementNotClickable = async (selector: string) => {
logUsingLastSection(`Waiting element visible ${selector}`)
await t
.expect(Selector(selector).visible)
.ok(`waitForElementVisible(${selector})`, { timeout: LONG_TIMEOUT })
await t
.expect(Selector(selector).hasAttribute('disabled')).ok()
}

export const waitForElementClickable = async (selector: string) => {
logUsingLastSection(`Waiting element visible ${selector}`)
await t
.expect(Selector(selector).visible)
.ok(`waitForElementVisible(${selector})`, { timeout: LONG_TIMEOUT })
await t
.expect(Selector(selector).hasAttribute('disabled')).notOk()
}

export const logResults = async (requests: LoggedRequest[], t) => {
if (!t.testRun?.errs?.length) {
log('Finished without errors')
Expand Down Expand Up @@ -88,6 +108,17 @@ export const click = async (selector: string) => {
.click(selector)
}

export const clickByText = async (text:string, element = 'button') => {
logUsingLastSection(`Click by text ${text} ${element}`)
const selector = Selector(element).withText(text);
await t
.scrollIntoView(selector)
.expect(Selector(selector).hasAttribute('disabled'))
.notOk('ready for testing', { timeout: 5000 })
.hover(selector)
.click(selector)
}

export const gotoSegments = async () => {
await click('#segments-link')
}
Expand Down Expand Up @@ -219,6 +250,12 @@ export const saveFeatureSegments = async () => {
await waitForElementNotExist('#create-feature-modal')
}

export const createEnvironment = async (name:string) => {
await setText('[name="envName"]', name)
await click('#create-env-btn')
await waitForElementVisible(byId(`switch-environment-${name.toLowerCase()}-active`))
}

export const goToUser = async (index: number) => {
await click('#features-link')
await click('#users-link')
Expand Down Expand Up @@ -257,7 +294,7 @@ export const login = async (email: string, password: string) => {
await click('#login-btn')
await waitForElementVisible('#project-manage-widget')
}
export const logout = async (t) => {
export const logout = async () => {
await click('#account-settings-link')
await click('#logout-link')
await waitForElementVisible('#login-page')
Expand Down Expand Up @@ -413,6 +450,14 @@ export const toggleFeature = async (index: number, toValue: boolean) => {
)
}

export const setUserPermissions = async (index: number, toValue: boolean) => {
await click(byId(`feature-switch-${index}${toValue ? '-off' : 'on'}`))
await click('#confirm-toggle-feature-btn')
await waitForElementVisible(
byId(`feature-switch-${index}${toValue ? '-on' : 'off'}`),
)
}

export const setSegmentRule = async (
ruleIndex: number,
orIndex: number,
Expand Down Expand Up @@ -478,4 +523,44 @@ export const refreshUntilElementVisible = async (selector: string, maxRetries=20
return t.scrollIntoView(element)
}

const permissionsMap = {
'CREATE_PROJECT': 'organisation',
'MANAGE_USERS': 'organisation',
'MANAGE_USER_GROUPS': 'organisation',
'VIEW_PROJECT': 'project',
'CREATE_ENVIRONMENT': 'project',
'DELETE_FEATURE': 'project',
'CREATE_FEATURE': 'project',
'MANAGE_SEGMENTS': 'project',
'VIEW_AUDIT_LOG': 'project',
'VIEW_ENVIRONMENT': 'environment',
'UPDATE_FEATURE_STATE': 'environment',
'MANAGE_IDENTITIES': 'environment',
'CREATE_CHANGE_REQUEST': 'environment',
'APPROVE_CHANGE_REQUEST': 'environment',
'VIEW_IDENTITIES': 'environment',
'MANAGE_SEGMENT_OVERRIDES': 'environment',
'MANAGE_TAGS': 'project',
} as const;


export const setUserPermission = async (email: string, permission: keyof typeof permissionsMap | 'ADMIN', entityName:string|null, entityLevel?: 'project'|'environment'|'organisation', parentName?: string) => {
await click(byId('users-and-permissions'))
await click(byId(`user-${email}`))
const level = permissionsMap[permission] || entityLevel
await click(byId(`${level}-permissions-tab`))
if(parentName) {
await clickByText(parentName, 'a')
}
if(entityName) {
await click(byId(`permissions-${entityName.toLowerCase()}`))
}
if(permission==='ADMIN') {
await click(byId(`admin-switch-${level}`))
} else {
await click(byId(`permission-switch-${permission}`))
}
await closeModal()
}

export default {}
13 changes: 9 additions & 4 deletions frontend/e2e/index.cafe.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,15 @@ createTestCafe()
testcafe = tc;
await new Promise((resolve) => {
process.env.PORT = 3000;
server = fork('./api/index');
server.on('message', () => {
resolve();
});
console.log(process.env.E2E_LOCAL)
if(process.env.E2E_LOCAL) {
resolve()
} else {
server = fork('./api/index');
server.on('message', () => {
resolve();
});
}
});
const runner = testcafe.createRunner()
const args = process.argv.splice(2).map(value => value.toLowerCase());
Expand Down
21 changes: 20 additions & 1 deletion frontend/e2e/init.cafe.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ import projectTest from './tests/project-test'
import { testSegment1, testSegment2, testSegment3 } from './tests/segment-test'
import initialiseTests from './tests/initialise-tests'
import flagTests from './tests/flag-tests'
import versioningTests from './tests/versioning-tests';
import versioningTests from './tests/versioning-tests'
import organisationPermissionTest from './tests/organisation-permission-test'
import projectPermissionTest from './tests/project-permission-test'
import environmentPermissionTest from './tests/environment-permission-test'
import rolesTest from './tests/roles-test'

require('dotenv').config()

Expand Down Expand Up @@ -124,3 +128,18 @@ test('Versioning', async () => {
await versioningTests()
await logout()
})

test('Organisation-permission', async () => {
await organisationPermissionTest()
await logout()
})

test('Project-permission', async () => {
await projectPermissionTest()
await logout()
})

test('Environment-permission', async () => {
await environmentPermissionTest()
await logout()
})
147 changes: 147 additions & 0 deletions frontend/e2e/tests/environment-permission-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import {
byId,
click, clickByText, closeModal, createEnvironment,
createFeature, editRemoteConfig,
gotoTraits,
log,
login, logout, setUserPermission,
toggleFeature, waitForElementClickable, waitForElementNotClickable, waitForElementNotExist, waitForElementVisible,
} from '../helpers.cafe';
import {
PASSWORD,
E2E_NON_ADMIN_USER_WITH_ENV_PERMISSIONS,
E2E_USER,
E2E_NON_ADMIN_USER_WITH_PROJECT_PERMISSIONS,
} from '../config';
import { Selector, t } from 'testcafe'
import { cli } from 'yaml/dist/cli';

export default async function () {
log('Login')
await login(E2E_NON_ADMIN_USER_WITH_ENV_PERMISSIONS, PASSWORD)
log('User only can see an project')
await click('#project-select-0')
await t
.expect(Selector('#project-select-1').exists)
.notOk('The element"#project-select-1" should not be present')
await logout()

log('User with permissions can Handle the Features')
await login(E2E_NON_ADMIN_USER_WITH_ENV_PERMISSIONS, PASSWORD)
await click('#project-select-0')
await createFeature(0, 'test_feature', false)
await toggleFeature(0, true)
await logout()

log('User without permissions cannot create traits')
await login(E2E_NON_ADMIN_USER_WITH_ENV_PERMISSIONS, PASSWORD)
await click('#project-select-0')
await gotoTraits()
const createTraitBtn = Selector(byId('add-trait'))
await t.expect(createTraitBtn.hasAttribute('disabled')).ok()
await logout()

log('User without permissions cannot see audit logs')
await login(E2E_NON_ADMIN_USER_WITH_ENV_PERMISSIONS, PASSWORD)
await click('#project-select-0')
await waitForElementNotExist(byId('audit-log-link'))
await logout()

log('Create new environment')
await login(E2E_USER, PASSWORD)
await clickByText('My Test Project 6 Env Permission')
await click('#create-env-link')
await createEnvironment('Production')
await logout()
log('User without permissions cannot see environment')
await login(E2E_NON_ADMIN_USER_WITH_ENV_PERMISSIONS, PASSWORD)
await click('#project-select-0')
await waitForElementVisible(byId('switch-environment-development'))
await waitForElementNotExist(byId('switch-environment-production'))
await logout()

log('Grant view environment permission')
await login(E2E_USER, PASSWORD)
await setUserPermission(E2E_NON_ADMIN_USER_WITH_ENV_PERMISSIONS, 'VIEW_ENVIRONMENT', 'Production', 'environment', 'My Test Project 6 Env Permission' )
await logout()
log('User with permissions can see environment')
await login(E2E_NON_ADMIN_USER_WITH_ENV_PERMISSIONS, PASSWORD)
await click('#project-select-0')
await waitForElementVisible(byId('switch-environment-production'))
await waitForElementVisible(byId('switch-environment-production'))
await logout()

log('User with permissions can update feature state')
await login(E2E_NON_ADMIN_USER_WITH_ENV_PERMISSIONS, PASSWORD)
await click('#project-select-0')
await createFeature(0,'my_feature',"foo",'A test feature')
await editRemoteConfig(0, 'bar')
await logout()
log('User without permission cannot create a segment override')
await login(E2E_NON_ADMIN_USER_WITH_ENV_PERMISSIONS, PASSWORD)
await click('#project-select-0')
await click(byId('feature-item-0'))
await click(byId('segment_overrides'))
await waitForElementNotClickable('#update-feature-segments-btn')
await closeModal()
await logout()
log('Grant MANAGE_IDENTITIES permission')
await login(E2E_USER, PASSWORD)
await setUserPermission(E2E_NON_ADMIN_USER_WITH_ENV_PERMISSIONS, 'MANAGE_SEGMENT_OVERRIDES', 'Development', 'environment', 'My Test Project 6 Env Permission' )
await logout()
log('User with permission can create a segment override')
await login(E2E_NON_ADMIN_USER_WITH_ENV_PERMISSIONS, PASSWORD)
await click('#project-select-0')
await click(byId('feature-item-0'))
await click(byId('segment_overrides'))
await waitForElementClickable('#update-feature-segments-btn')
await closeModal()
await logout()

log('User without permissions cannot update feature state')
await login(E2E_NON_ADMIN_USER_WITH_ENV_PERMISSIONS, PASSWORD)
await click('#project-select-0')
await waitForElementClickable(byId('feature-switch-0-on'))
await click(byId('switch-environment-production'))
await waitForElementNotClickable(byId('feature-switch-0-on'))
await click(byId('feature-item-0'))
await waitForElementNotClickable(byId('update-feature-btn'))
await closeModal()
await logout()

log('User with permissions can view identities')
await login(E2E_NON_ADMIN_USER_WITH_ENV_PERMISSIONS, PASSWORD)
await click('#project-select-0')
await waitForElementVisible('#users-link')
await logout()

log('User without permissions cannot add user trait')
await login(E2E_NON_ADMIN_USER_WITH_ENV_PERMISSIONS, PASSWORD)
await click('#project-select-0')
await click('#users-link')
await click(byId('user-item-0'))
await waitForElementNotClickable(byId('add-trait'))
await logout()

log('Grant MANAGE_IDENTITIES permission')
await login(E2E_USER, PASSWORD)
await setUserPermission(E2E_NON_ADMIN_USER_WITH_ENV_PERMISSIONS, 'MANAGE_IDENTITIES', 'Development', 'environment', 'My Test Project 6 Env Permission' )
await logout()
log('User with permissions can add user trait')
await login(E2E_NON_ADMIN_USER_WITH_ENV_PERMISSIONS, PASSWORD)
await click('#project-select-0')
await click('#users-link')
await click(byId('user-item-0'))
await waitForElementClickable(byId('add-trait'))
await logout()


log('Remove VIEW_IDENTITIES permission')
await login(E2E_USER, PASSWORD)
await setUserPermission(E2E_NON_ADMIN_USER_WITH_ENV_PERMISSIONS, 'VIEW_IDENTITIES', 'Development', 'environment', 'My Test Project 6 Env Permission' )
await logout()
log('User without permissions cannot view identities')
await login(E2E_NON_ADMIN_USER_WITH_ENV_PERMISSIONS, PASSWORD)
await click('#project-select-0')
await waitForElementNotExist('#users-link')
}
Loading
Loading