Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
@@ -0,0 +1,72 @@
Feature: change role
As an admin
I want to change the role of user
So that I can manage the role of user
Background:
Given user "Alice" has been created with default attributes
Scenario Outline: admin user changes the role of another user with different roles
Given user "Brian" has been created with default attributes
And the administrator has assigned the role "Admin" to user "Alice" using the Graph API
And the administrator has assigned the role "<user-role>" to user "Brian" using the Graph API
When user "Alice" changes the role of user "Brian" to role "<new-user-role>" using the Graph API
Then the HTTP status code should be "201"
And user "Brian" should have the role "<new-user-role>"
Examples:
| user-role | new-user-role |
| Admin | Admin |
| Admin | Space Admin |
| Admin | User |
| Admin | User Light |
| Space Admin | Admin |
| Space Admin | Space Admin |
| Space Admin | User |
| Space Admin | User Light |
| User | Admin |
| User | Space Admin |
| User | User |
| User | User Light |
| User Light | Admin |
| User Light | Space Admin |
| User Light | User |
| User Light | User Light |
Scenario Outline: admin user tries to change his/her own role
Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API
When user "Alice" tries to change the role of user "Alice" to role "<new-user-role>" using the Graph API
Then the HTTP status code should be "403"
And user "Alice" should have the role "Admin"
Examples:
| new-user-role |
| Space Admin |
| User |
| User Light |
| Admin |
Scenario Outline: non-admin cannot change the user role
Given the administrator has assigned the role "<user-role>" to user "Alice" using the Graph API
And user "Brian" has been created with default attributes
When user "Alice" tries to change the role of user "Alice" to role "Admin" using the Graph API
Then the HTTP status code should be "403"
And user "Brian" should have the role "User"
Examples:
| user-role |
| Space Admin |
| User |
| User Light |
Scenario Outline: non-admin user tries to change the role of nonexistent user
Given the administrator has assigned the role "<user-role>" to user "Alice" using the Graph API
And user "Brian" has been created with default attributes
When user "Alice" tries to change the role of user "nonexistent" to role "Admin" using the Graph API
Then the HTTP status code should be "403"
Examples:
| user-role |
| Space Admin |
| User |
| User Light |
@@ -0,0 +1,405 @@
@env-config
Feature: enforce password on public link
As a user
I want to enforce passwords on public links shared with upload, edit, or contribute permission
So that the password is required to access the contents of the link
Password requirements. set by default:
| OC_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD | true |
| OC_PASSWORD_POLICY_MIN_CHARACTERS | 8 |
| OC_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS | 1 |
| OC_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS | 1 |
| OC_PASSWORD_POLICY_MIN_DIGITS | 1 |
| OC_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS | 1 |
Background:
And user "Alice" has been created with default attributes
And user "Alice" has uploaded file with content "test file" to "/testfile.txt"
Scenario Outline: create a public link without a password when enforce-password for writable share is enabled
Given the following configs have been set:
| config | value |
| OC_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD | false |
| OC_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD | true |
When user "Alice" creates the following resource link share using the Graph API:
| resource | testfile.txt |
| space | Personal |
| permissionsRole | <permissions-role> |
Then the HTTP status code should be "<status-code>"
Examples:
| permissions-role | status-code |
| view | 200 |
| edit | 400 |
Scenario: try to update a public link to edit permission without a password
Given the following configs have been set:
| config | value |
| OC_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD | false |
| OC_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD | true |
And user "Alice" has created the following resource link share:
| resource | testfile.txt |
| space | Personal |
| permissionsRole | view |
When user "Alice" tries to update the last public link share using the permissions endpoint of the Graph API:
| resource | testfile.txt |
| space | Personal |
| permissionsRole | edit |
Then the HTTP status code should be "400"
And the JSON data of the response should match
"""
{
"type": "object",
"required": [
"error"
],
"properties": {
"error": {
"type": "object",
"required": [
"code",
"message"
],
"properties": {
"code": {
"type": "string",
"pattern": "invalidRequest"
},
"message": {
"const": "password protection is enforced"
}
}
}
}
}
"""
@issue-2048
Scenario: update a public link to edit permission. Need set pasword first
Given the following configs have been set:
| config | value |
| OC_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD | false |
| OC_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD | true |
And user "Alice" has created the following resource link share:
| resource | testfile.txt |
| space | Personal |
| permissionsRole | view |
When user "Alice" sets the following password for the last link share using the Graph API:
| resource | testfile.txt |
| space | Personal |
| password | %public% |
Then the HTTP status code should be "200"
And user "Alice" updates the last public link share using the permissions endpoint of the Graph API:
| resource | testfile.txt |
| space | Personal |
| permissionsRole | edit |
And the HTTP status code should be "200"
And the JSON data of the response should match
"""
{
"type": "object",
"required": [
"hasPassword",
"link"
],
"properties": {
"hasPassword": { "const": true },
"link": {
"type": "object",
"required": [
"type"
],
"properties": {
"type": { "const": "edit" }
}
}
}
}
"""
@issue-9724 @issue-10331
Scenario: create a public link with a password in accordance with the password policy
Given the following configs have been set:
| config | value |
| OC_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD | true |
| OC_PASSWORD_POLICY_MIN_CHARACTERS | 13 |
| OC_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS | 3 |
| OC_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS | 2 |
| OC_PASSWORD_POLICY_MIN_DIGITS | 2 |
| OC_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS | 2 |
When user "Alice" creates the following resource link share using the Graph API:
| resource | testfile.txt |
| space | Personal |
| permissionsRole | edit |
| password | 3s:5WW9uE5h=A |
Then the HTTP status code should be "200"
And the JSON data of the response should match
"""
{
"type": "object",
"required": [
"hasPassword",
"id",
"link"
],
"properties": {
"hasPassword": { "const": true },
"id": {
"type": "string",
"pattern": "^[a-zA-Z]{15}$"
},
"link": {
"type": "object",
"required": [
"type"
],
"properties": {
"type": { "const": "edit" }
}
}
}
}
"""
Scenario: try to create a public link with a password that does not comply with the password policy
Given the following configs have been set:
| config | value |
| OC_PASSWORD_POLICY_MIN_CHARACTERS | 13 |
| OC_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS | 3 |
| OC_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS | 2 |
| OC_PASSWORD_POLICY_MIN_DIGITS | 2 |
| OC_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS | 2 |
When user "Alice" tries to create the following resource link share using the Graph API:
| space | Personal |
| resource | testfile.txt |
| permissionsRole | edit |
| password | Pas1 |
Then the HTTP status code should be "400"
And the JSON data of the response should match
"""
{
"type": "object",
"required": ["error"],
"properties": {
"error": {
"type": "object",
"required": [
"code",
"innererror",
"message"
],
"properties": {
"code": { "const": "invalidRequest" },
"innererror": {
"type": "object",
"required": [
"date",
"request-id"
]
},
"message": {
"type": "string",
"pattern": "at least 13 characters are required\\s+at least 3 lowercase letters are required\\s+at least 2 uppercase letters are required\\s+at least 2 numbers are required\\s+at least 2 special characters are required\\s+[!\"#$%&'()*+,\\-./:;<=>?@\\[\\\\\\]^_`{|}~]+"
}
}
}
}
}
"""
@issue-9724 @issue-10331
Scenario: update a public link with a password in accordance with the password policy
Given the following configs have been set:
| config | value |
| OC_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD | false |
| OC_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD | true |
| OC_PASSWORD_POLICY_MIN_CHARACTERS | 13 |
| OC_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS | 3 |
| OC_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS | 2 |
| OC_PASSWORD_POLICY_MIN_DIGITS | 1 |
| OC_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS | 2 |
And user "Alice" has created the following resource link share:
| resource | testfile.txt |
| space | Personal |
| permissionsRole | view |
When user "Alice" sets the following password for the last link share using the Graph API:
| resource | testfile.txt |
| space | Personal |
| password | 6a0Q;A3 +i^m[ |
Then the HTTP status code should be "200"
And the JSON data of the response should match
"""
{
"type": "object",
"required": [ "hasPassword" ],
"properties": {
"hasPassword": { "const": true }
}
}
"""
Scenario Outline: try to update a public link with a password that does not comply with the password policy
Given the following configs have been set:
| config | value |
| OC_SHARING_PUBLIC_SHARE_MUST_HAVE_PASSWORD | false |
| OC_SHARING_PUBLIC_WRITEABLE_SHARE_MUST_HAVE_PASSWORD | true |
| OC_PASSWORD_POLICY_MIN_CHARACTERS | 13 |
| OC_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS | 3 |
| OC_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS | 2 |
| OC_PASSWORD_POLICY_MIN_DIGITS | 1 |
| OC_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS | 2 |
And using OCS API version "<ocs-api-version>"
And using SharingNG
And user "Alice" has created the following resource link share:
| resource | testfile.txt |
| space | Personal |
| permissionsRole | view |
When user "Alice" updates the last public link share using the sharing API with
| permissions | 3 |
| password | Pws^ |
And user "Alice" tries to set the following password for the last link share using the Graph API:
| resource | testfile.txt |
| space | Personal |
| password | Pws^ |
Then the HTTP status code should be "400"
And the JSON data of the response should match
"""
{
"type": "object",
"required": [ "error" ],
"properties": {
"error": {
"type": "object",
"required": [ "message" ],
"properties": {
"message": {
"type": "string",
"pattern": "at least 13 characters are required\\s+at least 3 lowercase letters are required\\s+at least 2 uppercase letters are required\\s+at least 1 numbers are required\\s+at least 2 special characters are required\\s+[!\"#$%&'()*+,\\-./:;<=>?@\\[\\\\\\]^_`{|}~]+"
}
}
}
}
}
"""
@issue-9724 @issue-10331
Scenario Outline: create a public link with a password in accordance with the password policy (valid cases)
Given the config "<config>" has been set to "<config-value>"
When user "Alice" creates the following resource link share using the Graph API:
| space | Personal |
| resource | testfile.txt |
| permissionsRole | view |
| password | <password> |
Then the HTTP status code should be "200"
Examples:
| config | config-value | password |
| OC_PASSWORD_POLICY_MIN_CHARACTERS | 4 | Ps-1 |
| OC_PASSWORD_POLICY_MIN_CHARACTERS | 14 | Ps1:with space |
| OC_PASSWORD_POLICY_MIN_LOWERCASE_CHARACTERS | 4 | PS1:test |
| OC_PASSWORD_POLICY_MIN_UPPERCASE_CHARACTERS | 3 | PS1:TeƒsT |
| OC_PASSWORD_POLICY_MIN_DIGITS | 2 | PS1:test2 |
| OC_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS | 2 | PS1:test pass |
| OC_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS | 33 | pS1! #$%&'()*+,-./:;<=>?@[\]^_`{ }~ |
| OC_PASSWORD_POLICY_MIN_SPECIAL_CHARACTERS | 5 | 1sameCharacterShouldWork!!!!! |
Scenario Outline: try to create a public link with a password that does not comply with the password policy (invalid cases)
When user "Alice" tries to create the following resource link share using the Graph API:
| space | Personal |
| resource | testfile.txt |
| permissionsRole | view |
| password | <password> |
Then the HTTP status code should be "400"
And the JSON data of the response should match
"""
{
"type": "object",
"required": [ "error" ],
"properties": {
"error": {
"type": "object",
"required": [ "message" ],
"properties": {
"message": {
"const": "<message>"
}
}
}
}
}
"""
Examples:
| password | message |
| 1Pw: | at least 8 characters are required |
| 1P:12345 | at least 1 lowercase letters are required |
| test-123 | at least 1 uppercase letters are required |
| Test-psw | at least 1 numbers are required |
Scenario Outline: update a public link with a password that is listed in the Banned-Password-List
Given the config "OC_PASSWORD_POLICY_BANNED_PASSWORDS_LIST" has been set to path "config/woodpecker/banned-password-list.txt"
And user "Alice" has created the following resource link share:
| resource | testfile.txt |
| space | Personal |
| permissionsRole | view |
| password | %public% |
And user "Alice" tries to set the following password for the last link share using the Graph API:
| resource | testfile.txt |
| space | Personal |
| password | <password> |
Then the HTTP status code should be "400"
And the JSON data of the response should match
"""
{
"type": "object",
"required": [ "error" ],
"properties": {
"error": {
"type": "object",
"required": [ "message" ],
"properties": {
"message": { "const": "<message>" }
}
}
}
}
"""
Examples:
| password | message |
| 123 | unfortunately, your password is commonly used. please pick a harder-to-guess password for your safety |
| password | unfortunately, your password is commonly used. please pick a harder-to-guess password for your safety |
| КуСфера | unfortunately, your password is commonly used. please pick a harder-to-guess password for your safety |
Scenario Outline: create a public link with a password that is listed in the Banned-Password-List
Given the config "OC_PASSWORD_POLICY_BANNED_PASSWORDS_LIST" has been set to path "config/woodpecker/banned-password-list.txt"
When user "Alice" tries to create the following resource link share using the Graph API:
| space | Personal |
| resource | testfile.txt |
| permissionsRole | view |
| password | <password> |
Then the HTTP status code should be "400"
And the JSON data of the response should match
"""
{
"type": "object",
"required": [ "error" ],
"properties": {
"error": {
"type": "object",
"required": [ "message" ],
"properties": {
"message": { "const": "<message>" }
}
}
}
}
"""
Examples:
| password | message |
| 123 | unfortunately, your password is commonly used. please pick a harder-to-guess password for your safety |
| password | unfortunately, your password is commonly used. please pick a harder-to-guess password for your safety |
| КуСфера | unfortunately, your password is commonly used. please pick a harder-to-guess password for your safety |
@@ -0,0 +1,618 @@
Feature: favorites
As a user
I want to check that I can mark and unmark files and folders as favorites using the Graph API
Background:
Given user "Alice" has been created with default attributes
Scenario Outline: add a file to favorites in the personal space
Given user "Alice" has created folder "parent"
And user "Alice" has uploaded file "filesForUpload/<file>" to "/parent/<file>"
When user "Alice" marks file "parent/<file>" as favorite from space "Personal" using the Graph API
Then the HTTP status code should be "201"
And the JSON data of the response should match
"""
{
"type": "object",
"required": [
"eTag",
"file",
"id",
"lastModifiedDateTime",
"name",
"parentReference",
"size"
],
"properties": {
"eTag": {
"type": "string",
"pattern": "%etag_pattern%"
},
"file": {
"type": "object",
"required": ["mimeType"],
"properties": {
"mimeType": {
"const": "<mimeType>"
}
}
},
"id": {
"type": "string",
"pattern": "^%file_id_pattern%$"
},
"lastModifiedDateTime": {
"type": "string",
"format": "date-time"
},
"name": {
"const": "<file>"
},
"parentReference": {
"type": "object",
"required": [
"driveId",
"driveType",
"id",
"name",
"path"
],
"properties": {
"driveId": {
"type": "string",
"pattern": "^%space_id_pattern%$"
},
"driveType": {
"const": "personal"
},
"id": {
"type": "string",
"pattern": "^%file_id_pattern%$"
},
"name": {
"const": "parent"
},
"path": {
"const": "/parent"
}
}
},
"size": {
"type": "integer",
"const": <size>
}
}
}
"""
And as user "Alice" file "parent/<file>" should be favorited
Examples:
| file | size | mimeType |
| simple.odt | 10119 | application/vnd.oasis.opendocument.text |
| testavatar.jpg | 45343 | image/jpeg |
| simple.pdf | 17684 | application/pdf |
Scenario: add a folder to favorites in the personal space
Given user "Alice" has created folder "parent"
And user "Alice" has uploaded file with content "first" to "/parent/first.txt"
And user "Alice" has uploaded file with content "second" to "/parent/second.txt"
When user "Alice" marks folder "parent" as favorite from space "Personal" using the Graph API
Then the HTTP status code should be "201"
And the JSON data of the response should match
"""
{
"type": "object",
"required": [
"eTag",
"folder",
"id",
"lastModifiedDateTime",
"name",
"parentReference",
"size"
],
"properties": {
"eTag": {
"type": "string",
"pattern": "%etag_pattern%"
},
"folder": {
"type": "object"
},
"id": {
"type": "string",
"pattern": "^%file_id_pattern%$"
},
"lastModifiedDateTime": {
"type": "string",
"format": "date-time"
},
"name": {
"const": "parent"
},
"parentReference": {
"type": "object",
"required": [
"driveId",
"driveType",
"id",
"name",
"path"
],
"properties": {
"driveId": {
"type": "string",
"pattern": "^%space_id_pattern%$"
},
"driveType": {
"const": "personal"
},
"id": {
"type": "string",
"pattern": "^%file_id_pattern%$"
},
"name": {
"const": "/"
},
"path": {
"const": "/"
}
}
},
"size": {
"type": "integer",
"const": 11
}
}
}
"""
And as user "Alice" folder "parent" should be favorited
Scenario: add a shared file to favorites
Given user "Brian" has been created with default attributes
And user "Alice" has uploaded file with content "КуСфера test text file" to "textfile.txt"
And user "Alice" has sent the following resource share invitation:
| resource | textfile.txt |
| space | Personal |
| sharee | Brian |
| shareType | user |
| permissionsRole | Viewer |
And user "Brian" has a share "textfile.txt" synced
When user "Brian" marks file "textfile.txt" as favorite from space "Shares" using the Graph API
Then the HTTP status code should be "201"
And the JSON data of the response should match
"""
{
"type": "object",
"required": [
"eTag",
"file",
"id",
"lastModifiedDateTime",
"name",
"parentReference",
"size"
],
"properties": {
"eTag": {
"type": "string",
"pattern": "%etag_pattern%"
},
"file": {
"type": "object",
"required": ["mimeType"],
"properties": {
"mimeType": {
"const": "text/plain"
}
}
},
"id": {
"type": "string",
"pattern": "^%file_id_pattern%$"
},
"lastModifiedDateTime": {
"type": "string",
"format": "date-time"
},
"name": {
"const": "textfile.txt"
},
"parentReference": {
"type": "object",
"required": [
"driveId",
"driveType",
"id",
"name",
"path"
],
"properties": {
"driveId": {
"type": "string",
"pattern": "^%space_id_pattern%$"
},
"driveType": {
"const": "personal"
},
"id": {
"type": "string",
"pattern": "^%file_id_pattern%$"
},
"name": {
"const": "/"
},
"path": {
"const": "/"
}
}
},
"size": {
"type": "integer",
"const": 24
}
}
}
"""
And as user "Brian" file "Shares/textfile.txt" should be favorited
But as user "Alice" file "textfile.txt" should not be favorited
Scenario: add a shared folder to favorites
Given user "Brian" has been created with default attributes
And user "Alice" has created folder "parent"
And user "Alice" has created folder "parent/sub"
And user "Alice" has uploaded file with content "КуСфера test text file" to "parent/textfile.txt"
And user "Alice" has sent the following resource share invitation:
| resource | parent |
| space | Personal |
| sharee | Brian |
| shareType | user |
| permissionsRole | Viewer |
And user "Brian" has a share "parent" synced
When user "Brian" marks folder "parent/sub" as favorite from space "Shares" using the Graph API
Then the HTTP status code should be "201"
And the JSON data of the response should match
"""
{
"type": "object",
"required": [
"eTag",
"folder",
"id",
"lastModifiedDateTime",
"name",
"parentReference",
"size"
],
"properties": {
"eTag": {
"type": "string",
"pattern": "%etag_pattern%"
},
"folder": {
"type": "object"
},
"id": {
"type": "string",
"pattern": "^%file_id_pattern%$"
},
"lastModifiedDateTime": {
"type": "string",
"format": "date-time"
},
"name": {
"const": "sub"
},
"parentReference": {
"type": "object",
"required": [
"driveId",
"driveType",
"id",
"name",
"path"
],
"properties": {
"driveId": {
"type": "string",
"pattern": "^%space_id_pattern%$"
},
"driveType": {
"const": "personal"
},
"id": {
"type": "string",
"pattern": "^%file_id_pattern%$"
},
"name": {
"const": "parent"
},
"path": {
"const": "/parent"
}
}
},
"size": {
"type": "integer",
"const": 0
}
}
}
"""
And as user "Brian" folder "Shares/parent/sub" should be favorited
But as user "Alice" folder "parent/sub" should not be favorited
Scenario: add a file of the project space to favorites
Given user "Brian" has been created with default attributes
And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API
And using spaces DAV path
And user "Alice" has created a space "new-space" with the default quota using the Graph API
And user "Alice" has uploaded a file inside space "new-space" with content "hello world" to "text.txt"
And user "Alice" has sent the following space share invitation:
| space | new-space |
| sharee | Brian |
| shareType | user |
| permissionsRole | Space Viewer |
When user "Brian" marks file "text.txt" as favorite from space "new-space" using the Graph API
Then the HTTP status code should be "201"
And the JSON data of the response should match
"""
{
"type": "object",
"required": [
"eTag",
"file",
"id",
"lastModifiedDateTime",
"name",
"parentReference",
"size"
],
"properties": {
"eTag": {
"type": "string",
"pattern": "%etag_pattern%"
},
"file": {
"type": "object",
"required": ["mimeType"],
"properties": {
"mimeType": {
"const": "text/plain"
}
}
},
"id": {
"type": "string",
"pattern": "^%file_id_pattern%$"
},
"lastModifiedDateTime": {
"type": "string",
"format": "date-time"
},
"name": {
"const": "text.txt"
},
"parentReference": {
"type": "object",
"required": [
"driveId",
"driveType",
"id",
"name",
"path"
],
"properties": {
"driveId": {
"type": "string",
"pattern": "^%space_id_pattern%$"
},
"driveType": {
"const": "project"
},
"id": {
"type": "string",
"pattern": "^%file_id_pattern%$"
},
"name": {
"const": "/"
},
"path": {
"const": "/"
}
}
},
"size": {
"type": "integer",
"const": 11
}
}
}
"""
Scenario: add a folder of the project space to favorites
Given user "Brian" has been created with default attributes
And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API
And using spaces DAV path
And user "Alice" has created a space "new-space" with the default quota using the Graph API
And user "Alice" has created a folder "space-folder" in space "new-space"
And user "Alice" has sent the following space share invitation:
| space | new-space |
| sharee | Brian |
| shareType | user |
| permissionsRole | Space Viewer |
When user "Brian" marks folder "space-folder" as favorite from space "new-space" using the Graph API
Then the HTTP status code should be "201"
And the JSON data of the response should match
"""
{
"type": "object",
"required": [
"eTag",
"folder",
"id",
"lastModifiedDateTime",
"name",
"parentReference",
"size"
],
"properties": {
"eTag": {
"type": "string",
"pattern": "%etag_pattern%"
},
"folder": {
"type": "object"
},
"id": {
"type": "string",
"pattern": "^%file_id_pattern%$"
},
"lastModifiedDateTime": {
"type": "string",
"format": "date-time"
},
"name": {
"const": "space-folder"
},
"parentReference": {
"type": "object",
"required": [
"driveId",
"driveType",
"id",
"name",
"path"
],
"properties": {
"driveId": {
"type": "string",
"pattern": "^%space_id_pattern%$"
},
"driveType": {
"const": "project"
},
"id": {
"type": "string",
"pattern": "^%file_id_pattern%$"
},
"name": {
"const": "/"
},
"path": {
"const": "/"
}
}
},
"size": {
"type": "integer",
"const": 0
}
}
}
"""
Scenario: remove file from favorites from the personal space
Given user "Alice" has created folder "parent"
And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/parent/textfile.txt"
And user "Alice" has marked file "parent/textfile.txt" as favorite from space "Personal"
When user "Alice" unmarks file "parent/textfile.txt" as favorite from space "Personal" using the Graph API
Then the HTTP status code should be "204"
And as user "Alice" file "parent/textfile.txt" should not be favorited
Scenario: remove folder from favorites from the personal space
Given user "Alice" has created folder "parent"
And user "Alice" has marked folder "parent" as favorite from space "Personal"
When user "Alice" unmarks folder "parent" as favorite from space "Personal" using the Graph API
Then the HTTP status code should be "204"
And as user "Alice" folder "parent" should not be favorited
Scenario: remove file from favorites from the shares
Given user "Brian" has been created with default attributes
And user "Alice" has created folder "parent"
And user "Alice" has uploaded file "filesForUpload/textfile.txt" to "/parent/textfile.txt"
And user "Alice" has sent the following resource share invitation:
| resource | parent/textfile.txt |
| space | Personal |
| sharee | Brian |
| shareType | user |
| permissionsRole | Viewer |
And user "Brian" has a share "textfile.txt" synced
And user "Brian" has marked file "textfile.txt" as favorite from space "Shares"
When user "Brian" unmarks file "textfile.txt" as favorite from space "Shares" using the Graph API
Then the HTTP status code should be "204"
And as user "Brian" file "Shares/textfile.txt" should not be favorited
Scenario: remove folder from favorites from the shares
Given user "Brian" has been created with default attributes
And user "Alice" has created folder "parent"
And user "Alice" has created folder "parent/sub"
And user "Alice" has sent the following resource share invitation:
| resource | parent |
| space | Personal |
| sharee | Brian |
| shareType | user |
| permissionsRole | Viewer |
And user "Brian" has a share "parent" synced
And user "Brian" has marked folder "parent/sub" as favorite from space "Shares"
When user "Brian" unmarks folder "parent/sub" as favorite from space "Shares" using the Graph API
Then the HTTP status code should be "204"
And as user "Brian" folder "Shares/parent/sub" should not be favorited
Scenario: remove file from favorites from the project space
Given user "Brian" has been created with default attributes
And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API
And using spaces DAV path
And user "Alice" has created a space "new-space" with the default quota using the Graph API
And user "Alice" has uploaded a file inside space "new-space" with content "hello world" to "text.txt"
And user "Alice" has sent the following space share invitation:
| space | new-space |
| sharee | Brian |
| shareType | user |
| permissionsRole | Space Viewer |
And user "Brian" has marked file "text.txt" as favorite from space "new-space"
When user "Brian" unmarks file "text.txt" as favorite from space "new-space" using the Graph API
Then the HTTP status code should be "204"
Scenario: remove folder from favorites from the project space
Given user "Brian" has been created with default attributes
And the administrator has assigned the role "Space Admin" to user "Alice" using the Graph API
And using spaces DAV path
And user "Alice" has created a space "new-space" with the default quota using the Graph API
And user "Alice" has created a folder "space-folder" in space "new-space"
And user "Alice" has sent the following space share invitation:
| space | new-space |
| sharee | Brian |
| shareType | user |
| permissionsRole | Space Viewer |
And user "Brian" has marked folder "space-folder" as favorite from space "new-space"
When user "Brian" unmarks folder "space-folder" as favorite from space "new-space" using the Graph API
Then the HTTP status code should be "204"
Scenario: add a file to favorites after unmarking it as favorite in the personal space
Given user "Alice" has uploaded file "filesForUpload/testavatar.jpg" to "/testavatar.jpg"
And user "Alice" has marked file "testavatar.jpg" as favorite from space "Personal"
And user "Alice" has unmarked file "testavatar.jpg" as favorite from space "Personal"
When user "Alice" marks file "/testavatar.jpg" as favorite from space "Personal" using the Graph API
Then the HTTP status code should be "201"
And as user "Alice" file "testavatar.jpg" should be favorited
Scenario: add a file to favorites twice
Given user "Alice" has uploaded file "filesForUpload/testavatar.jpg" to "/testavatar.jpg"
And user "Alice" has marked file "testavatar.jpg" as favorite from space "Personal"
When user "Alice" marks file "/testavatar.jpg" as favorite from space "Personal" using the Graph API
Then the HTTP status code should be "201"
And as user "Alice" file "testavatar.jpg" should be favorited
@@ -0,0 +1,28 @@
Feature: get applications
As a user
I want to be able to get application information with existing roles
So that I can see which role belongs to what user
Background:
Given user "Alice" has been created with default attributes
Scenario Outline: admin user lists all the groups
Given the administrator has assigned the role "<user-role>" to user "Alice" using the Graph API
When user "Alice" gets all applications using the Graph API
Then the HTTP status code should be "200"
And the user API response should contain the following application information:
| key | value |
| displayName | КуСфера |
| id | %uuid_v4% |
And the user API response should contain the following app roles:
| Admin |
| Space Admin |
| User |
| User Light |
Examples:
| user-role |
| Admin |
| Space Admin |
| User |
| User Light |
@@ -0,0 +1,58 @@
Feature: assign role
As an admin,
I want to assign roles to users.
So that users without an admin role cannot get the list of roles, assignments list and assign roles to users
Background:
Given user "Alice" has been created with default attributes
Scenario Outline: get assigned role of a user
Given the administrator has assigned the role "<user-role>" to user "Alice" using the Graph API
When the administrator retrieves the assigned role of user "Alice" using the Graph API
Then the HTTP status code should be "200"
And the Graph API response should have the role "<user-role>"
Examples:
| user-role |
| Admin |
| Space Admin |
| User |
| User Light |
@issue-5032
Scenario Outline: get assigned role of a user via setting api
Given the administrator has assigned the role "<user-role>" to user "Alice" using the Graph API
When user "Alice" tries to get list of assignment using the settings API
Then the HTTP status code should be "<http-status-code>"
And the setting API response should have the role "<user-role>"
Examples:
| user-role | http-status-code |
| Admin | 201 |
| Space Admin | 401 |
| User | 401 |
| User Light | 401 |
Scenario Outline: get role of a user assigned via setting api
Given the administrator has given "Alice" the role "<user-role>" using the settings api
When the administrator retrieves the assigned role of user "Alice" using the Graph API
Then the HTTP status code should be "200"
And the Graph API response should have the role "<user-role>"
Examples:
| user-role |
| Admin |
| Space Admin |
| User |
| User Light |
Scenario: non-admin user tries to get assigned role of another user
Given user "Brian" has been created with default attributes
When user "Alice" tries to get the assigned role of user "Brian" using the Graph API
Then the HTTP status code should be "403"
Scenario: non-admin user tries to get assigned role of nonexistent user
Given user "Brian" has been created with default attributes
When user "Alice" tries to get the assigned role of user "nonexistent" using the Graph API
Then the HTTP status code should be "403"
@@ -0,0 +1,759 @@
Feature: permissions role definitions
As a user
I want to get the role management endpoints
So that I can find out if those endpoints are working correctly or not
Background:
Given user "Alice" has been created with default attributes
Scenario: get a list of permissions role definitions
When user "Alice" gets a list of permissions role definitions using the Graph API
Then the HTTP status code should be "200"
And the JSON data of the response should match
"""
{
"type": "array",
"maxItems": 7,
"minItems": 7,
"uniqueItems": true,
"items": {
"oneOf": [
{
"type": "object",
"required": [
"@libre.graph.weight",
"description",
"displayName",
"id",
"rolePermissions"
],
"properties": {
"@libre.graph.weight": {
"const": 10
},
"description": {
"const": "View and download."
},
"displayName": {
"const": "Can view"
},
"id": {
"const": "b1e2218d-eef8-4d4c-b82d-0f1a1b48f3b5"
},
"rolePermissions": {
"type": "array",
"maxItems": 4,
"minItems": 4,
"uniqueItems": true,
"items": {
"oneOf": [
{
"type": "object",
"required": [
"allowedResourceActions",
"condition"
],
"properties": {
"allowedResourceActions": {
"const": [
"libre.graph/driveItem/path/read",
"libre.graph/driveItem/quota/read",
"libre.graph/driveItem/content/read",
"libre.graph/driveItem/children/read",
"libre.graph/driveItem/deleted/read",
"libre.graph/driveItem/basic/read"
]
},
"condition": {
"const": "exists @Resource.File"
}
}
},
{
"type": "object",
"required": [
"allowedResourceActions",
"condition"
],
"properties": {
"allowedResourceActions": {
"const": [
"libre.graph/driveItem/path/read",
"libre.graph/driveItem/quota/read",
"libre.graph/driveItem/content/read",
"libre.graph/driveItem/children/read",
"libre.graph/driveItem/deleted/read",
"libre.graph/driveItem/basic/read"
]
},
"condition": {
"const": "exists @Resource.Folder"
}
}
},
{
"type": "object",
"required": [
"allowedResourceActions",
"condition"
],
"properties": {
"allowedResourceActions": {
"const": [
"libre.graph/driveItem/path/read",
"libre.graph/driveItem/quota/read",
"libre.graph/driveItem/content/read",
"libre.graph/driveItem/children/read",
"libre.graph/driveItem/deleted/read",
"libre.graph/driveItem/basic/read"
]
},
"condition": {
"const": "exists @Resource.File \u0026\u0026 @Subject.UserType==\"Federated\""
}
}
},
{
"type": "object",
"required": [
"allowedResourceActions",
"condition"
],
"properties": {
"allowedResourceActions": {
"const": [
"libre.graph/driveItem/path/read",
"libre.graph/driveItem/quota/read",
"libre.graph/driveItem/content/read",
"libre.graph/driveItem/children/read",
"libre.graph/driveItem/deleted/read",
"libre.graph/driveItem/basic/read"
]
},
"condition": {
"const": "exists @Resource.Folder \u0026\u0026 @Subject.UserType==\"Federated\""
}
}
}
]
}
}
}
},
{
"type": "object",
"required": [
"@libre.graph.weight",
"description",
"displayName",
"id",
"rolePermissions"
],
"properties": {
"@libre.graph.weight": {
"const": 40
},
"description": {
"const": "View and download."
},
"displayName": {
"const": "Can view"
},
"id": {
"const": "a8d5fe5e-96e3-418d-825b-534dbdf22b99"
},
"rolePermissions": {
"type": "array",
"maxItems": 1,
"minItems": 1,
"uniqueItems": true,
"items": {
"type": "object",
"required": [
"allowedResourceActions",
"condition"
],
"properties": {
"allowedResourceActions": {
"const": [
"libre.graph/driveItem/path/read",
"libre.graph/driveItem/quota/read",
"libre.graph/driveItem/content/read",
"libre.graph/driveItem/permissions/read",
"libre.graph/driveItem/children/read",
"libre.graph/driveItem/deleted/read",
"libre.graph/driveItem/basic/read"
]
},
"condition": {
"const": "exists @Resource.Root"
}
}
}
}
}
},
{
"type": "object",
"required": [
"@libre.graph.weight",
"description",
"displayName",
"id",
"rolePermissions"
],
"properties": {
"@libre.graph.weight": {
"const": 60
},
"description": {
"const": "View, download, upload, edit, add and delete."
},
"displayName": {
"const": "Can edit"
},
"id": {
"const": "fb6c3e19-e378-47e5-b277-9732f9de6e21"
},
"rolePermissions": {
"type": "array",
"maxItems": 2,
"minItems": 2,
"uniqueItems": true,
"items": {
"oneOf": [
{
"type": "object",
"required": [
"allowedResourceActions",
"condition"
],
"properties": {
"allowedResourceActions": {
"const": [
"libre.graph/driveItem/children/create",
"libre.graph/driveItem/standard/delete",
"libre.graph/driveItem/path/read",
"libre.graph/driveItem/quota/read",
"libre.graph/driveItem/content/read",
"libre.graph/driveItem/upload/create",
"libre.graph/driveItem/children/read",
"libre.graph/driveItem/deleted/read",
"libre.graph/driveItem/path/update",
"libre.graph/driveItem/deleted/update",
"libre.graph/driveItem/basic/read"
]
},
"condition": {
"const": "exists @Resource.Folder"
}
}
},
{
"type": "object",
"required": [
"allowedResourceActions",
"condition"
],
"properties": {
"allowedResourceActions": {
"const": [
"libre.graph/driveItem/children/create",
"libre.graph/driveItem/standard/delete",
"libre.graph/driveItem/path/read",
"libre.graph/driveItem/quota/read",
"libre.graph/driveItem/content/read",
"libre.graph/driveItem/upload/create",
"libre.graph/driveItem/children/read",
"libre.graph/driveItem/deleted/read",
"libre.graph/driveItem/path/update",
"libre.graph/driveItem/deleted/update",
"libre.graph/driveItem/basic/read"
]
},
"condition": {
"const": "exists @Resource.Folder \u0026\u0026 @Subject.UserType==\"Federated\""
}
}
}
]
}
}
}
},
{
"type": "object",
"required": [
"@libre.graph.weight",
"description",
"displayName",
"id",
"rolePermissions"
],
"properties": {
"@libre.graph.weight": {
"const": 90
},
"description": {
"const": "View, download, upload, edit, add, delete including the history."
},
"displayName": {
"const": "Can edit"
},
"id": {
"const": "58c63c02-1d89-4572-916a-870abc5a1b7d"
},
"rolePermissions": {
"type": "array",
"maxItems": 1,
"minItems": 1,
"uniqueItems": true,
"items": {
"type": "object",
"required": [
"allowedResourceActions",
"condition"
],
"properties": {
"allowedResourceActions": {
"const": [
"libre.graph/driveItem/children/create",
"libre.graph/driveItem/standard/delete",
"libre.graph/driveItem/path/read",
"libre.graph/driveItem/quota/read",
"libre.graph/driveItem/content/read",
"libre.graph/driveItem/upload/create",
"libre.graph/driveItem/permissions/read",
"libre.graph/driveItem/children/read",
"libre.graph/driveItem/versions/read",
"libre.graph/driveItem/deleted/read",
"libre.graph/driveItem/path/update",
"libre.graph/driveItem/versions/update",
"libre.graph/driveItem/deleted/update",
"libre.graph/driveItem/basic/read"
]
},
"condition": {
"const": "exists @Resource.Root"
}
}
}
}
}
},
{
"type": "object",
"required": [
"@libre.graph.weight",
"description",
"displayName",
"id",
"rolePermissions"
],
"properties": {
"@libre.graph.weight": {
"const": 100
},
"description": {
"const": "View, download and edit."
},
"displayName": {
"const": "Can edit"
},
"id": {
"const": "2d00ce52-1fc2-4dbc-8b95-a73b73395f5a"
},
"rolePermissions": {
"type": "array",
"maxItems": 2,
"minItems": 2,
"uniqueItems": true,
"items": {
"oneOf": [
{
"type": "object",
"required": [
"allowedResourceActions",
"condition"
],
"properties": {
"allowedResourceActions": {
"const": [
"libre.graph/driveItem/path/read",
"libre.graph/driveItem/quota/read",
"libre.graph/driveItem/content/read",
"libre.graph/driveItem/upload/create",
"libre.graph/driveItem/children/read",
"libre.graph/driveItem/deleted/read",
"libre.graph/driveItem/deleted/update",
"libre.graph/driveItem/basic/read"
]
},
"condition": {
"const":"exists @Resource.File"
}
}
},
{
"type": "object",
"required": [
"allowedResourceActions",
"condition"
],
"properties": {
"allowedResourceActions": {
"const": [
"libre.graph/driveItem/path/read",
"libre.graph/driveItem/quota/read",
"libre.graph/driveItem/content/read",
"libre.graph/driveItem/upload/create",
"libre.graph/driveItem/children/read",
"libre.graph/driveItem/deleted/read",
"libre.graph/driveItem/deleted/update",
"libre.graph/driveItem/basic/read"
]
},
"condition": {
"const":"exists @Resource.File \u0026\u0026 @Subject.UserType==\"Federated\""
}
}
}
]
}
}
}
},
{
"type": "object",
"required": [
"@libre.graph.weight",
"description",
"displayName",
"id",
"rolePermissions"
],
"properties": {
"@libre.graph.weight": {
"const": 50
},
"description": {
"const": "View, download and upload."
},
"displayName": {
"const": "Can upload"
},
"id": {
"const": "1c996275-f1c9-4e71-abdf-a42f6495e960"
},
"rolePermissions": {
"type": "array",
"maxItems": 1,
"minItems": 1,
"uniqueItems": true,
"items": {
"type": "object",
"required": [
"allowedResourceActions",
"condition"
],
"properties": {
"allowedResourceActions": {
"const": [
"libre.graph/driveItem/children/create",
"libre.graph/driveItem/path/read",
"libre.graph/driveItem/content/read",
"libre.graph/driveItem/upload/create",
"libre.graph/driveItem/children/read",
"libre.graph/driveItem/path/update",
"libre.graph/driveItem/basic/read"
]
},
"condition": {
"const": "exists @Resource.Folder"
}
}
}
}
}
},
{
"type": "object",
"required": [
"@libre.graph.weight",
"description",
"displayName",
"id",
"rolePermissions"
],
"properties": {
"@libre.graph.weight": {
"const": 120
},
"description": {
"const": "View, download, upload, edit, add, delete and manage members."
},
"displayName": {
"const": "Can manage"
},
"id": {
"const": "312c0871-5ef7-4b3a-85b6-0e4074c64049"
},
"rolePermissions": {
"type": "array",
"maxItems": 1,
"minItems": 1,
"uniqueItems": true,
"items": {
"type": "object",
"required": [
"allowedResourceActions",
"condition"
],
"properties": {
"allowedResourceActions": {
"const": [
"libre.graph/driveItem/permissions/create",
"libre.graph/driveItem/children/create",
"libre.graph/driveItem/standard/delete",
"libre.graph/driveItem/path/read",
"libre.graph/driveItem/quota/read",
"libre.graph/driveItem/content/read",
"libre.graph/driveItem/upload/create",
"libre.graph/driveItem/permissions/read",
"libre.graph/driveItem/children/read",
"libre.graph/driveItem/versions/read",
"libre.graph/driveItem/deleted/read",
"libre.graph/driveItem/path/update",
"libre.graph/driveItem/permissions/delete",
"libre.graph/driveItem/deleted/delete",
"libre.graph/driveItem/versions/update",
"libre.graph/driveItem/deleted/update",
"libre.graph/driveItem/basic/read",
"libre.graph/driveItem/permissions/update",
"libre.graph/driveItem/permissions/deny"
]
},
"condition": {
"const": "exists @Resource.Root"
}
}
}
}
}
}
]
}
}
"""
Scenario: get details of a specific permission role definition
When user "Alice" gets the "Viewer" role definition using the Graph API
Then the HTTP status code should be "200"
And the JSON data of the response should match
"""
{
"type": "object",
"required": [
"@libre.graph.weight",
"description",
"displayName",
"id",
"rolePermissions"
],
"properties": {
"@libre.graph.weight":{
"const": 10
},
"description": {
"const": "View and download."
},
"displayName": {
"const": "Can view"
},
"id": {
"const": "b1e2218d-eef8-4d4c-b82d-0f1a1b48f3b5"
},
"rolePermissions": {
"type": "array",
"maxItems": 4,
"minItems": 4,
"uniqueItems": true,
"items": {
"oneOf": [
{
"type": "object",
"required": [
"allowedResourceActions",
"condition"
],
"properties": {
"allowedResourceActions": {
"const": [
"libre.graph/driveItem/path/read",
"libre.graph/driveItem/quota/read",
"libre.graph/driveItem/content/read",
"libre.graph/driveItem/children/read",
"libre.graph/driveItem/deleted/read",
"libre.graph/driveItem/basic/read"
]
},
"condition": {
"const": "exists @Resource.File"
}
}
},
{
"type": "object",
"required": [
"allowedResourceActions",
"condition"
],
"properties": {
"allowedResourceActions": {
"const": [
"libre.graph/driveItem/path/read",
"libre.graph/driveItem/quota/read",
"libre.graph/driveItem/content/read",
"libre.graph/driveItem/children/read",
"libre.graph/driveItem/deleted/read",
"libre.graph/driveItem/basic/read"
]
},
"condition": {
"const": "exists @Resource.Folder"
}
}
},
{
"type": "object",
"required": [
"allowedResourceActions",
"condition"
],
"properties": {
"allowedResourceActions": {
"const": [
"libre.graph/driveItem/path/read",
"libre.graph/driveItem/quota/read",
"libre.graph/driveItem/content/read",
"libre.graph/driveItem/children/read",
"libre.graph/driveItem/deleted/read",
"libre.graph/driveItem/basic/read"
]
},
"condition": {
"const": "exists @Resource.File \u0026\u0026 @Subject.UserType==\"Federated\""
}
}
},
{
"type": "object",
"required": [
"allowedResourceActions",
"condition"
],
"properties": {
"allowedResourceActions": {
"const": [
"libre.graph/driveItem/path/read",
"libre.graph/driveItem/quota/read",
"libre.graph/driveItem/content/read",
"libre.graph/driveItem/children/read",
"libre.graph/driveItem/deleted/read",
"libre.graph/driveItem/basic/read"
]
},
"condition": {
"const": "exists @Resource.Folder \u0026\u0026 @Subject.UserType==\"Federated\""
}
}
}
]
}
}
}
}
"""
@env-config
Scenario: get details of a secure viewer role definition
Given the administrator has enabled the permissions role "Secure Viewer"
When user "Alice" gets the "Secure Viewer" role definition using the Graph API
Then the HTTP status code should be "200"
And the JSON data of the response should match
"""
{
"type": "object",
"required": [
"@libre.graph.weight",
"description",
"displayName",
"id",
"rolePermissions"
],
"properties": {
"@libre.graph.weight": {
"const": 20
},
"description": {
"const": "View only documents, images and PDFs. Watermarks will be applied."
},
"displayName": {
"const": "Can view (secure)"
},
"id": {
"const": "aa97fe03-7980-45ac-9e50-b325749fd7e6"
},
"rolePermissions": {
"type": "array",
"maxItems": 2,
"minItems": 2,
"uniqueItems": true,
"items": {
"oneOf": [
{
"type": "object",
"required": [
"allowedResourceActions",
"condition"
],
"properties": {
"allowedResourceActions": {
"const": [
"libre.graph/driveItem/path/read",
"libre.graph/driveItem/children/read",
"libre.graph/driveItem/basic/read"
]
},
"condition": {
"const": "exists @Resource.File"
}
}
},
{
"type": "object",
"required": [
"allowedResourceActions",
"condition"
],
"properties": {
"allowedResourceActions": {
"const": [
"libre.graph/driveItem/path/read",
"libre.graph/driveItem/children/read",
"libre.graph/driveItem/basic/read"
]
},
"condition": {
"const": "exists @Resource.Folder"
}
}
}
]
}
}
}
}
"""
@@ -0,0 +1,43 @@
Feature: unassign user role
As an admin
I want to unassign the role of user
So that the role of user is set to default
Background:
Given user "Alice" has been created with default attributes
Scenario Outline: admin user unassigns the role of another user
Given user "Brian" has been created with default attributes
And the administrator has assigned the role "Admin" to user "Alice" using the Graph API
And the administrator has assigned the role "<user-role>" to user "Brian" using the Graph API
When user "Alice" unassigns the role of user "Brian" using the Graph API
Then the HTTP status code should be "204"
And user "Brian" should not have any role assigned
When user "Brian" uploads file with content "this step will assign the role to default" to "assign-to-default.txt" using the WebDAV API
And user "Brian" should have the role "User" assigned
Examples:
| user-role |
| Admin |
| Space Admin |
| User |
| User Light |
@issue-6035
Scenario: admin user tries to unassign his/her own role
Given the administrator has assigned the role "Admin" to user "Alice" using the Graph API
When user "Alice" tries to unassign the role of user "Alice" using the Graph API
Then the HTTP status code should be "403"
And user "Alice" should have the role "Admin" assigned
Scenario: non-admin user tries to unassign role of another user
Given user "Brian" has been created with default attributes
When user "Alice" tries to unassign the role of user "Brian" using the Graph API
Then the HTTP status code should be "403"
And user "Brian" should have the role "User" assigned
Scenario: non-admin user tries to unassign role of nonexistent user
When user "Alice" tries to unassign the role of user "nonexistent" using the Graph API
Then the HTTP status code should be "403"
File diff suppressed because it is too large Load Diff