Repository navigation
feat: add overview export permissions and update related components - #7854
Conversation
| ), | ||
| self.assertRaises(AppApiException), | ||
| ): | ||
| ModelSerializer.Create(data=self.serializer_data("workspace-a")).insert("workspace-a") |
There was a problem hiding this comment.
Overall, the tests cover the intended behavior, but they are somewhat brittle and may pass/fail for the wrong reason.
Key issues and suggestions:
-
Inconsistent permission mocking
UserResourcePermissionSerializeris patched in the first test but not the second.- If permission validation runs before the duplicate-name check in the second test, the test may hit real permission logic and fail for an unrelated reason.
- Patch it consistently in both tests, or move the patch into a helper/setUp context.
-
MagicMockprovider is too permissiveself.provider = MagicMock()will return truthy mocks for any unexpected method call, which can hide bugs.- Prefer
MagicMock(spec=...)/create_autospec(...)and configure every provider method used by the serializer. - At minimum, document which provider contract the test depends on.
-
Permission serializer mock is also too permissive
- Patching the class without configuring
is_valid,validated_data, etc. may silently pass validation. - Configure the mocked return values explicitly so the test verifies the intended path.
- Patching the class without configuring
-
Plaintext password
User.objects.create(password="password", ...)stores an unhashed password in tests.- Prefer
User.objects.create_user(...)or callset_password(...)if using a custom user model.
-
Hardcoded role string
role="ADMIN"is a magic string. Use the project’s role enum/constant if available.
-
Workspace IDs may not match the field type
"workspace-a"/"workspace-b"are fine only ifworkspace_idis a text field.- If
workspace_idis a UUID field, use valid UUID strings.
-
Second test should assert no row was created
- Add a count assertion after
assertRaises:self.assertEqual(Model.objects.filter(workspace_id="workspace-a", name=MODEL_NAME).count(), 1)
- Also assert the exact exception message/code if possible, e.g.
assertRaisesRegex(...), so it does not pass because of an unrelatedAppApiException.
- Add a count assertion after
-
First test should assert full side effects
- Checking only
result["id"]workspace is good, but also assert counts:self.assertEqual(Model.objects.filter(workspace_id="workspace-a", name=MODEL_NAME).count(), 1) self.assertEqual(Model.objects.filter(workspace_id="workspace-b", name=MODEL_NAME).count(), 1)
- Checking only
-
Narrow the failing operation
- In the rejection test, construct the serializer outside the
assertRaisescontext if possible, and assert that only.insert(...)raises. This avoids passing because of an unrelated construction/validation error. - If construction must happen inside, assert the exception message/code.
- In the rejection test, construct the serializer outside the
-
Minor optimization
- Since the user is only read in these tests, consider creating it with
setUpTestDatainstead ofsetUpto reduce repeated DB setup. - Keep
self.providerinsetUpbecause it is a lightweight mock.
- Since the user is only read in these tests, consider creating it with
-
Patch target may be brittle
patch("models_provider.serializers.model_serializer.ModelProvideConstants", ...)is fine only if the serializer references that exact module attribute. If it imports the constant directly elsewhere, the patch may not apply. Confirm the patch target matches the lookup location.
Recommended shape:
with (
patch("models_provider.serializers.model_serializer.ModelProvideConstants",
{"test_provider": self.provider}),
patch("models_provider.serializers.model_serializer.UserResourcePermissionSerializer") as permission_serializer,
):
permission_serializer.return_value.is_valid.return_value = True
permission_serializer.return_value.validated_data = {}
...Bottom line: the test intent is good, but the mocks are too broad, permission patching is inconsistent, and the rejection test should verify that the duplicate-name rule is the actual cause of failure.
|
|
||
| RESOURCE_APPLICATION_OVERVIEW_BACKEND_API_ACCESS = ( | ||
| Permission( | ||
| group=Group.SYSTEM_RES_APPLICATION, |
There was a problem hiding this comment.
The patch is syntactically consistent with the existing PermissionConstants enum pattern. I do not see an immediate syntax error, but the following points should be verified:
-
bit_indexuniqueness and bit width- Confirm
bit_index=43andbit_index=32are not already used within the same permission group/sub-group. - Pay special attention to
RESOURCE_APPLICATION_OVERVIEW_EXPORTwithbit_index=32. If permission masks are stored as 32-bit signed integers, bit index 32 (and 43) will overflow. Ensure the mask is stored asuint64/bigintor otherwise supports these indexes.
- Confirm
-
Authorization scope / least privilege
APPLICATION_OVERVIEW_EXPORTgrants export toRoleConstants.USER. Confirm this is intended and that export does not expose sensitive workspace data to all users. If it does, consider restricting toWORKSPACE_MANAGEor a more specific role.- Confirm
scope=[WORKSPACE_RESOURCE, WORKSPACE]is correct. If export is workspace-level only, includingWORKSPACE_RESOURCEmay unintentionally allow resource-scoped access.
-
PermissionMetaconsistency- The first entry does not set
is_ee, while the second setsis_ee=is_ee. Confirm this difference is intentional and matches neighboring permissions. - Confirm
resource_permission_group_list=[ResourcePermissionConst.APPLICATION_MANAGE]is required for the workspace export and that the constant name is correct. - Confirm the resource permission does not also need a
resource_permission_group_list.
- The first entry does not set
-
Naming and placement
- The names and placement before the corresponding
BACKEND_API_ACCESSentries look consistent with the existing pattern. - No obvious optimization is needed; using a helper/factory would only be worthwhile if this enum pattern is repeated heavily.
- The names and placement before the corresponding
-
Tests / enforcement
- Add or verify tests for:
- unique
bit_indexvalues, - correct role/scope/category metadata,
- API enforcement of the new export permissions,
- EE-only behavior for
RESOURCE_APPLICATION_OVERVIEW_EXPORT.
- unique
- If permissions are cached or persisted elsewhere, ensure the new permissions are refreshed/migrated appropriately.
- Add or verify tests for:
Overall: no definite bug from the diff alone, but the main risks are bit-index collision/overflow and overly broad export authorization for RoleConstants.USER.
| <el-button v-if="canExport" plain :loading="exporting" :disabled="loading" @click="handleExport"> | ||
| <MkIcon name="icon_export_outlined" /> | ||
| <span>导出</span> | ||
| </el-button> |
There was a problem hiding this comment.
Review of the diff
1. Correctness / Reactivity
-
computedwill only update if its dependencies are reactive.props.applicationIdis reactive, so that part is fine.isSystemResource()and theperm.application.*.overviewExport()methods must read from reactive state. Ifpermis a plain module object populated once at startup,canExportwill not update when permissions change (e.g. after login, role switch, or async permission loading). Verifypermis backed by a reactive store/composable.- Also check what
isSystemResource()does. If it internally usesinject()/getCurrentInstance()/useRoute(), calling it inside acomputedgetter can be unsafe, because composables that rely oninject()must be called synchronously duringsetup(). Safer pattern:If it usesconst isSystem = computed(() => isSystemResource()) // only if pure/reactive global // or, if it is static for this component: const isSystem = isSystemResource()
inject, resolve it at setup top-level.
-
Guard against an empty
applicationId.if (!props.applicationId) return false
Otherwise
workspace.overviewExport('')may return an unexpected result. -
Verify the permission API shape.
perm.application.system.overviewExport()andperm.application.workspace.overviewExport(id)are invoked as functions. If any of them are actually booleans/getters, this will throw at runtime. The inconsistent shape (system vs. workspace) also suggests a unified helper would be cleaner.
2. Security
- Hiding the button with
v-ifis only a UX control, not a security boundary. Ensure the export API enforces the same permission server-side. - Consider also guarding the handler for defense in depth:
This protects against stale state or programmatic calls.
function handleExport() { if (!canExport.value) return // ... }
3. UX
v-ifremoves the button from the DOM, causing layout shift. If the export button is in a fixed toolbar, considerv-showor rendering it disabled with a tooltip explaining missing permission. Either is fine; just be consistent with the rest of the app.- The
:disabled="loading"state does not includeexporting, though:loadingusually blocks clicks. No issue, just noting.
4. Naming / maintainability
canExportis very generic in this component. PrefercanExportRankingorcanExportOverviewRanking.- The permission logic is likely needed elsewhere. Consider extracting a composable such as
useOverviewExportPermission(applicationId)or av-permissiondirective to avoid duplicatingisSystemResource()branching. - Import ordering:
permandisSystemResourceare mixed into the import block. If the project enforces import sorting/grouping (ESLintimport/order), this may fail lint. Minor.
5. Redundant Boolean(...)
- If the permission functions are typed as
boolean, theBoolean(...)wrappers are redundant. Keep them only if they can returnundefined/null/truthy non-booleans. Typing the permission methods asbooleanwould be clearer.
Suggested revision
const isSystem = computed(() => isSystemResource())
const canExportRanking = computed(() => {
if (!props.applicationId) return false
if (isSystem.value) {
return Boolean(perm.application.system.overviewExport())
}
return Boolean(perm.application.workspace.overviewExport(props.applicationId))
})
function handleExport() {
if (!canExportRanking.value) return
// ...
}<el-button
v-if="canExportRanking"
plain
:loading="exporting"
:disabled="loading"
@click="handleExport"
>Bottom line
The change is directionally correct, but verify:
permandisSystemResource()are reactive (otherwisecanExportwill be stale).isSystemResource()does not rely oninject()when called insidecomputed.- The
overviewExportpermission entries are actually callable functions. - The server enforces the export permission independently of the UI.
feat: add overview export permissions and update related components