Fix Cursor driver inactive account detection
Cursor's Admin API exposes two independent removal signals: the isRemoved boolean and a role value of "removed". They are not always consistent — a member can carry role "removed" while isRemoved is still false, a known gap documented on the Cursor community forum. Previously Active was derived from isRemoved alone, so a member with role "removed" but isRemoved=false was incorrectly reported as active. Now either signal is sufficient to mark the account inactive. Add a cassette entry and test case covering the inconsistent state (role "removed", isRemoved false) to prevent regression. Signed-off-by: Bryan Frimin <bryan@probo.com>
This commit is contained in:
@@ -86,10 +86,12 @@ func (d *CursorDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// isRemoved is Cursor's only account-status signal, so Active is
|
// Cursor exposes two removal signals that are not always
|
||||||
|
// consistent: the isRemoved soft-delete flag and a role of
|
||||||
|
// "removed". Either one marks the member inactive, so Active is
|
||||||
// always populated (never nil): a removed member is reported
|
// always populated (never nil): a removed member is reported
|
||||||
// inactive rather than dropped, per the AccountRecord contract.
|
// inactive rather than dropped, per the AccountRecord contract.
|
||||||
active := !m.IsRemoved
|
active := !m.IsRemoved && m.Role != "removed"
|
||||||
|
|
||||||
records = append(records, AccountRecord{
|
records = append(records, AccountRecord{
|
||||||
Email: m.Email,
|
Email: m.Email,
|
||||||
@@ -98,8 +100,8 @@ func (d *CursorDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error
|
|||||||
Active: &active,
|
Active: &active,
|
||||||
IsAdmin: cursorIsAdmin(m.Role),
|
IsAdmin: cursorIsAdmin(m.Role),
|
||||||
MFAStatus: coredata.MFAStatusUnknown,
|
MFAStatus: coredata.MFAStatusUnknown,
|
||||||
AuthMethod: coredata.AccessEntryAuthMethodUnknown,
|
AuthMethod: coredata.AccessReviewEntryAuthMethodUnknown,
|
||||||
AccountType: coredata.AccessEntryAccountTypeUser,
|
AccountType: coredata.AccessReviewEntryAccountTypeUser,
|
||||||
ExternalID: m.ID,
|
ExternalID: m.ID,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ func TestCursorDriver(t *testing.T) {
|
|||||||
driver := NewCursorDriver(client)
|
driver := NewCursorDriver(client)
|
||||||
records, err := driver.ListAccounts(context.Background())
|
records, err := driver.ListAccounts(context.Background())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, records, 3)
|
require.Len(t, records, 4)
|
||||||
|
|
||||||
member := records[0]
|
member := records[0]
|
||||||
assert.Equal(t, "jane@example.com", member.Email)
|
assert.Equal(t, "jane@example.com", member.Email)
|
||||||
@@ -61,6 +61,15 @@ func TestCursorDriver(t *testing.T) {
|
|||||||
assert.False(t, removed.IsAdmin)
|
assert.False(t, removed.IsAdmin)
|
||||||
require.NotNil(t, removed.Active)
|
require.NotNil(t, removed.Active)
|
||||||
assert.False(t, *removed.Active)
|
assert.False(t, *removed.Active)
|
||||||
|
|
||||||
|
// Cursor's two removal signals are not always consistent: a member can
|
||||||
|
// carry role "removed" while isRemoved is still false. The role alone
|
||||||
|
// must mark the account inactive.
|
||||||
|
removedByRole := records[3]
|
||||||
|
assert.Equal(t, "Removed", removedByRole.Role)
|
||||||
|
assert.False(t, removedByRole.IsAdmin)
|
||||||
|
require.NotNil(t, removedByRole.Active)
|
||||||
|
assert.False(t, *removedByRole.Active)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCursorRole(t *testing.T) {
|
func TestCursorRole(t *testing.T) {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ interactions:
|
|||||||
proto_minor: 0
|
proto_minor: 0
|
||||||
content_length: -1
|
content_length: -1
|
||||||
uncompressed: true
|
uncompressed: true
|
||||||
body: '{"teamMembers":[{"id":"10000001","name":"Jane Doe","email":"jane@example.com","role":"member","isRemoved":false},{"id":"10000002","name":"Alex Martin","email":"alex@example.com","role":"owner","isRemoved":false},{"id":"10000003","name":"John Smith","email":"john@example.com","role":"removed","isRemoved":true}]}'
|
body: '{"teamMembers":[{"id":"10000001","name":"Jane Doe","email":"jane@example.com","role":"member","isRemoved":false},{"id":"10000002","name":"Alex Martin","email":"alex@example.com","role":"owner","isRemoved":false},{"id":"10000003","name":"John Smith","email":"john@example.com","role":"removed","isRemoved":true},{"id":"10000004","name":"Mary Jones","email":"mary@example.com","role":"removed","isRemoved":false}]}'
|
||||||
headers:
|
headers:
|
||||||
Content-Type:
|
Content-Type:
|
||||||
- application/json
|
- application/json
|
||||||
|
|||||||
Reference in New Issue
Block a user