Skip to content

Conversation

@mlhenderson
Copy link
Contributor

Comprehensive test suite reveals multiple permission system bugs:

  • Type validation failures in AppModel configuration
  • Broken permission hierarchy (changePermission without write)
  • Silent failures in policy creation and broadcasting
  • Integration inconsistencies across views and models

Tests document expected failures and provide roadmap for fixes.

Comprehensive test suite reveals multiple permission system bugs:
- Type validation failures in AppModel configuration
- Broken permission hierarchy (changePermission without write)
- Silent failures in policy creation and broadcasting
- Integration inconsistencies across views and models

Tests document expected failures and provide roadmap for fixes.
@mlhenderson
Copy link
Contributor Author

I suspect that these issues are related to the bugs contained in the skipped tests marked EXPECTED FAILURE:

#1918
#1916

There do not appear to be that many issues regarding access policy bugs, but it could be that this is because most data deployed with Metacat is public.

Copy link
Member

@robyngit robyngit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for all these tests @mlhenderson, this is fantastic!! I like the new coverage report also, very helpful! I left comments on a few of the tests. Some of the "expected failures" are actually just because of little mix ups about how backbone behaves - it's not always intuitive. And the others are questions about the rationale behind testing serializing/parsing the dataONEObject as JSON. We usually use the serialize method to save as XML, but I could be missing something here.

Finally, I'm having trouble running a number of the new package.json scripts. For example, I can see the test site when I run view-tests, and for most of the new scripts I'm seeing errors like [PAGE error] Failed to load resource: the server responded with a status of 404 (404). I ran npm install to update my local deps, but is there something else I need to do to get these working?

Thanks again for working on this!

Comment on lines +1013 to +1017
it.skip("EXPECTED FAILURE: always returns an <accesspolicy> element (never null)", function() {
const el = state.policy.serialize();
expect(el && el.nodeType).to.equal(1);
expect(el.tagName.toLowerCase()).to.equal("accesspolicy");
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning null when a node is empty is the general serialize pattern that MetacatUI uses. We use AccessPolicy.serialize in DataONEObject.serializeSysMeta:

https://github.com/NCEAS/metacatui/blob/main/src/js/models/DataONEObject.js#L1129-L1149

We don't want to add an empty accessPolicy to the sysMeta. However, is there a reason you're thinking of that makes it better to return <accesspolicy></accesspolicy>?

Comment on lines +1120 to +1121
rule.trigger("removeMe");
expect(state.policy.length).to.equal(0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
rule.trigger("removeMe");
expect(state.policy.length).to.equal(0);
state.policy.listenToOnce(state.policy, "remove", () => {
expect(state.policy.length).to.equal(0);
});
rule.trigger("removeMe");

The original test fails because it checks the collection length immediately after triggering "removeMe", before the asynchronous removal actually completes. Waiting for the "remove" event makes sure the assertion runs when the model has actually been removed. It was backbone's solution of ensuring async events had completed, back before we had Promises, await/async!

Comment on lines +1408 to +1423
it.skip("EXPECTED FAILURE: set() with partial list should NOT remove rules not included", function() {
const editedBob = new AccessRule({
subject: "uid:bob",
read: true,
write: true,
dataONEObject: obj
});

policy.set([editedBob]);

// This should fail - we expect ALL rules to be preserved
expect(policy.length).to.equal(4, "All rules should be preserved");
expect(policy.findWhere({ subject: "uid:alice" })).to.exist;
expect(policy.findWhere({ subject: "uid:carol" })).to.exist;
expect(policy.findWhere({ subject: "public" })).to.exist;
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, this is how we expect collection.set to work, see the backbone.js docs:

... and if the collection contains any models that aren't present in the list, they'll be removed

We can pass the options { remove: false } to set to keep other models, but then we will actually have 5 rules, not 4 as your test asserts. I believe backbone will only merge the two models if the model IDs are the same (pretty sure, but verify)

Comment on lines +1425 to +1433
it.skip("EXPECTED FAILURE: safe usage: set() with remove:false merges edits without removing other rules", function() {
// Simulate UI sending only an edited subset
const editedBob = makeRule("uid:bob", ["read", "write"]);
policy.set([editedBob], { merge: true, remove: false });

expect(policy).to.have.lengthOf(4);
expect(getSubjects(policy)).to.have.members(["uid:alice", "uid:bob", "uid:carol", "public"]);
expect(policy.findWhere({ subject: "uid:bob" }).get("permissions")).to.have.members(["read", "write"]);
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comment above about merging. This isn't an expected failure, backbone has no way of knowing that the subject is the key that is being used to identify models.

Comment on lines +1467 to +1481
it.skip("EXPECTED FAILURE: re-adding a rule for the same subject merges permissions and does not remove or duplicate", function() {
expect(policy.length).to.equal(4);

// Try to add another rule for alice
policy.add(new AccessRule({
subject: "uid:alice",
changePermission: true,
dataONEObject: obj
}));

// Should not create duplicate - still 4 rules
expect(policy.length).to.equal(4);
const aliceRules = policy.where({ subject: "uid:alice" });
expect(aliceRules.length).to.equal(1);
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above re: merging

Comment on lines +1483 to +1492
it.skip("EXPECTED FAILURE: rebuilding from a visible subset should NOT drop non-visible rules", function() {
const visibleRules = policy.first(2);
const visibleRulesJSON = visibleRules.map(rule => rule.toJSON());

policy.reset(visibleRulesJSON);

// This should fail - we expect NO data loss
expect(policy.length).to.equal(4, "No rules should be lost during rebuild");
});
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the expected behaviour for collection.reset

Use reset to replace a collection with a new list of models

So in this case, expect(policy.length).to.equal(2) <--- 2 not 4

console.log("Has serialize method:", typeof state.collection.serialize === 'function');

// Simulate what happens during save/sync - JSON serialization
var jsonData = state.dataONEObject.toJSON();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm missing something here, why are we converting the dataONEObject to JSON? We send the access policy to the server as serialized XML (sys meta), so just need help understanding what you mean when you suggest we're simulating what happens during save to server?

Comment on lines +191 to +195
var savedJson = state.dataONEObject.toJSON();
var reloadedObject = new DataONEObject(savedJson);

// Check how many students retained access
var reloadedPolicy = reloadedObject.get("accessPolicy");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you planning to implement a strategy to cache to access policy in case of a reload?

Comment on lines +281 to +282
var jsonData = state.dataObject.toJSON();
var reloadedObject = new DataONEObject(jsonData);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar questions here, why to JSON and when/how is the dataone obj data being reloaded in the app?

Comment on lines +23 to +38
let triggerSpy = null;
// Add sinon spy for tests that need it
if (typeof sinon !== "undefined") {
sinon.spy(policy, "trigger");
}

return { dataONEObject, policy };
}, beforeEach);

// Add afterEach to clean up spies
afterEach(function() {
if (state.triggerSpy) {
state.triggerSpy.restore();
}
});

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can remove triggerSpy, not used here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants