Skip to content

Commit

Permalink
Fix conditions for userOwnsHistory and isRegisteredUser
Browse files Browse the repository at this point in the history
  • Loading branch information
davelopez committed May 28, 2024
1 parent 562129f commit 6f38f5b
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 5 deletions.
113 changes: 113 additions & 0 deletions client/src/api/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import {
type AnonymousUser,
type AnyHistory,
type HistorySummary,
type HistorySummaryExtended,
isRegisteredUser,
type User,
userOwnsHistory,
} from ".";

const REGISTERED_USER_ID = "fake-user-id";
const ANOTHER_USER_ID = "another-fake-user-id";
const ANONYMOUS_USER_ID = null;

const REGISTERED_USER: User = {
id: REGISTERED_USER_ID,
email: "[email protected]",
tags_used: [],
isAnonymous: false,
total_disk_usage: 0,
};

const ANONYMOUS_USER: AnonymousUser = {
isAnonymous: true,
};

const SESSIONLESS_USER = null;

function createFakeHistory<T>(historyId: string = "fake-id", user_id?: string | null): T {
const history: AnyHistory = {
id: historyId,
name: "test",
model_class: "History",
deleted: false,
archived: false,
purged: false,
published: false,
annotation: null,
update_time: "2021-09-01T00:00:00.000Z",
tags: [],
url: `/history/${historyId}`,
contents_active: { active: 0, deleted: 0, hidden: 0 },
count: 0,
size: 0,
};
if (user_id !== undefined) {
(history as HistorySummaryExtended).user_id = user_id;
}
return history as T;
}

const HISTORY_OWNED_BY_REGISTERED_USER = createFakeHistory<HistorySummaryExtended>("1234", REGISTERED_USER_ID);
const HISTORY_OWNED_BY_ANOTHER_USER = createFakeHistory<HistorySummaryExtended>("5678", ANOTHER_USER_ID);
const HISTORY_OWNED_BY_ANONYMOUS_USER = createFakeHistory<HistorySummaryExtended>("1234", ANONYMOUS_USER_ID);
const HISTORY_SUMMARY_WITHOUT_USER_ID = createFakeHistory<HistorySummary>("1234");

describe("API Types Helpers", () => {
describe("isRegisteredUser", () => {
it("should return true for a registered user", () => {
expect(isRegisteredUser(REGISTERED_USER)).toBe(true);
});

it("should return false for an anonymous user", () => {
expect(isRegisteredUser(ANONYMOUS_USER)).toBe(false);
});

it("should return false for sessionless users", () => {
expect(isRegisteredUser(SESSIONLESS_USER)).toBe(false);
});
});

describe("isAnonymousUser", () => {
it("should return true for an anonymous user", () => {
expect(isRegisteredUser(ANONYMOUS_USER)).toBe(false);
});

it("should return false for a registered user", () => {
expect(isRegisteredUser(REGISTERED_USER)).toBe(true);
});

it("should return false for sessionless users", () => {
expect(isRegisteredUser(SESSIONLESS_USER)).toBe(false);
});
});

describe("userOwnsHistory", () => {
it("should return true for a registered user owning the history", () => {
expect(userOwnsHistory(REGISTERED_USER, HISTORY_OWNED_BY_REGISTERED_USER)).toBe(true);
});

it("should return false for a registered user not owning the history", () => {
expect(userOwnsHistory(REGISTERED_USER, HISTORY_OWNED_BY_ANOTHER_USER)).toBe(false);
});

it("should return true for a registered user owning a history without user_id", () => {
expect(userOwnsHistory(REGISTERED_USER, HISTORY_SUMMARY_WITHOUT_USER_ID)).toBe(true);
});

it("should return true for an anonymous user owning a history with null user_id", () => {
expect(userOwnsHistory(ANONYMOUS_USER, HISTORY_OWNED_BY_ANONYMOUS_USER)).toBe(true);
});

it("should return false for an anonymous user not owning a history", () => {
expect(userOwnsHistory(ANONYMOUS_USER, HISTORY_OWNED_BY_REGISTERED_USER)).toBe(false);
});

it("should return false for sessionless users", () => {
expect(userOwnsHistory(SESSIONLESS_USER, HISTORY_OWNED_BY_REGISTERED_USER)).toBe(false);
expect(userOwnsHistory(SESSIONLESS_USER, HISTORY_SUMMARY_WITHOUT_USER_ID)).toBe(false);
expect(userOwnsHistory(SESSIONLESS_USER, HISTORY_OWNED_BY_ANONYMOUS_USER)).toBe(false);
});
});
});
24 changes: 19 additions & 5 deletions client/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ export interface HistoryContentsStats {
* Data returned by the API when requesting `?view=summary&keys=size,contents_active,user_id`.
*/
export interface HistorySummaryExtended extends HistorySummary, HistoryContentsStats {
user_id: string;
/** The ID of the user that owns the history. Null if the history is owned by an anonymous user. */
user_id: string | null;
}

type HistoryDetailedModel = components["schemas"]["HistoryDetailed"];
Expand Down Expand Up @@ -203,6 +204,7 @@ export function isHistorySummaryExtended(history: AnyHistory): history is Histor

type QuotaUsageResponse = components["schemas"]["UserQuotaUsage"];

/** Represents a registered user.**/
export interface User extends QuotaUsageResponse {
id: string;
email: string;
Expand All @@ -221,22 +223,34 @@ export interface AnonymousUser {

export type GenericUser = User | AnonymousUser;

export function isRegisteredUser(user: User | AnonymousUser | null): user is User {
return !user?.isAnonymous;
/** Represents any user, including anonymous users or session-less (null) users.**/
export type AnyUser = GenericUser | null;

export function isRegisteredUser(user: AnyUser): user is User {
return user !== null && !user?.isAnonymous;
}

export function isAnonymousUser(user: AnyUser): user is AnonymousUser {
return user !== null && user.isAnonymous;
}

export function userOwnsHistory(user: User | AnonymousUser | null, history: AnyHistory) {
export function userOwnsHistory(user: AnyUser, history: AnyHistory) {
return (
// Assuming histories without user_id are owned by the current user
(isRegisteredUser(user) && !hasOwner(history)) ||
(isRegisteredUser(user) && hasOwner(history) && user.id === history.user_id)
(isRegisteredUser(user) && hasOwner(history) && user.id === history.user_id) ||
(isAnonymousUser(user) && hasAnonymousOwner(history))
);
}

function hasOwner(history: AnyHistory): history is HistorySummaryExtended {
return "user_id" in history && history.user_id !== null;
}

function hasAnonymousOwner(history: AnyHistory): history is HistorySummaryExtended {
return "user_id" in history && history.user_id === null;
}

export function canMutateHistory(history: AnyHistory): boolean {
return !history.purged && !history.archived;
}
Expand Down

0 comments on commit 6f38f5b

Please sign in to comment.