Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 162 additions & 0 deletions static/js/publisher/hooks/__tests__/useSerialLogs.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,168 @@ describe("useSerialLogs", () => {
});
});

test("returns serial logs data with pageSize param", async () => {
server.use(
http.get(
"/api/store/test-brand-id/models/test-model/serial-log",
({ request }) => {
const url = new URL(request.url);

expect(url.searchParams.get("page-size")).toBe("10");
return HttpResponse.json({
data: serialLogsResponse,
success: true,
});
},
),
);

const { result } = renderHook(
() =>
useSerialLogs("test-brand-id", "test-model", {
pageSize: 10,
}),
{
wrapper: createWrapper(),
},
);
Comment thread
steverydz marked this conversation as resolved.

await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});

expect(result.current.data).toEqual({
data: serialLogsResponse,
success: true,
});
});

test("returns serial logs data with startTime and endTime params", async () => {
server.use(
http.get(
"/api/store/test-brand-id/models/test-model/serial-log",
({ request }) => {
const url = new URL(request.url);
expect(url.searchParams.get("start-time")).toBe(
"2026-03-24T04:00:23.875000",
);
expect(url.searchParams.get("end-time")).toBe(
"2026-03-28T04:00:23.875000",
);
return HttpResponse.json({
data: serialLogsResponse,
success: true,
});
},
),
);

const { result } = renderHook(
() =>
useSerialLogs("test-brand-id", "test-model", {
interval: {
startTime: "2026-03-24T04:00:23.875000",
endTime: "2026-03-28T04:00:23.875000",
},
}),
{
wrapper: createWrapper(),
},
);

await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});

expect(result.current.data).toEqual({
data: serialLogsResponse,
success: true,
});
});

test("returns serial logs data with nextPage param", async () => {
server.use(
http.get(
"/api/store/test-brand-id/models/test-model/serial-log",
({ request }) => {
const url = new URL(request.url);

expect(url.searchParams.get("next-page")).toBe("nextpagecursor");
return HttpResponse.json({
data: serialLogsResponse,
success: true,
});
},
),
);

const { result } = renderHook(
() =>
useSerialLogs("test-brand-id", "test-model", {
nextPage: "nextpagecursor",
}),
{
wrapper: createWrapper(),
},
);

await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});

expect(result.current.data).toEqual({
data: serialLogsResponse,
success: true,
});
});

test("returns serial logs data with startTime, endTime, pageSize and nextPage params", async () => {
server.use(
http.get(
"/api/store/test-brand-id/models/test-model/serial-log",
({ request }) => {
const url = new URL(request.url);
expect(url.searchParams.get("start-time")).toBe(
"2026-03-24T04:00:23.875000",
);
expect(url.searchParams.get("end-time")).toBe(
"2026-03-28T04:00:23.875000",
);
expect(url.searchParams.get("page-size")).toBe("10");
expect(url.searchParams.get("next-page")).toBe("nextpagecursor");
return HttpResponse.json({
data: serialLogsResponse,
success: true,
});
},
),
);

const { result } = renderHook(
() =>
useSerialLogs("test-brand-id", "test-model", {
interval: {
startTime: "2026-03-24T04:00:23.875000",
endTime: "2026-03-28T04:00:23.875000",
},
pageSize: 10,
nextPage: "nextpagecursor",
}),
{
wrapper: createWrapper(),
},
);

await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});

expect(result.current.data).toEqual({
data: serialLogsResponse,
success: true,
});
});

test("returns error if request fails", async () => {
const { result } = renderHook(
() => useSerialLogs("test-brand-id-fail", "test-model"),
Expand Down
36 changes: 32 additions & 4 deletions static/js/publisher/hooks/useSerialLogs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,41 @@ import type { SerialLogResponse, ApiResponse } from "../types/shared";
const useSerialLogs = (
brandId: string | undefined,
modelId: string | undefined,
urlSearchParams?: {
pageSize?: number;
nextPage?: string;
interval?: {
startTime: string;
endTime: string;
};
},
): UseQueryResult<ApiResponse<SerialLogResponse>, Error> => {
const url = new URL(
`/api/store/${brandId}/models/${modelId}/serial-log`,
window.location.origin,
);

if (urlSearchParams) {
const { interval, pageSize, nextPage } = urlSearchParams;

if (interval) {
url.searchParams.set("start-time", interval.startTime);
url.searchParams.set("end-time", interval.endTime);
}
Comment thread
steverydz marked this conversation as resolved.

if (pageSize) {
Comment thread
edisile marked this conversation as resolved.
url.searchParams.set("page-size", pageSize.toString());
}

if (nextPage) {
url.searchParams.set("next-page", nextPage);
}
}

return useQuery<ApiResponse<SerialLogResponse>, Error>({
queryKey: ["serials", brandId, modelId],
queryKey: ["serials", brandId, modelId, urlSearchParams],
queryFn: async () => {
const response = await fetch(
`/api/store/${brandId}/models/${modelId}/serial-log`,
);
const response = await fetch(url.toString());
const responseData = await response.json();

return responseData;
Expand Down
139 changes: 139 additions & 0 deletions tests/endpoints/tests_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -427,3 +427,142 @@ def test_get_serial_log_general_error(self, mock_get_serial_log):
self.assertEqual(response.status_code, 500)
self.assertFalse(data["success"])
self.assertEqual(data["message"], "Internal server error")

@patch(
"canonicalwebteam.store_api.publishergw.PublisherGW"
+ ".get_store_model_serial_logs"
)
def test_get_serial_log_with_time_range(self, mock_get_serial_log):
mock_serial_log = []
mock_get_serial_log.return_value = mock_serial_log
response = self.client.get(
"/api/store/1/models/test-model/serial-log"
"?start-time=2026-06-01T00:00:00Z"
"&end-time=2026-06-30T23:59:59Z"
)
Comment thread
steverydz marked this conversation as resolved.
data = response.json

self.assertEqual(response.status_code, 200)
self.assertTrue(data["success"])
self.assertEqual(data["data"], [])

mock_get_serial_log.assert_called_once()
# Arguments are passed positionally:
# (session, store_id, model_name, start_time, end_time, page_size)
Comment on lines +450 to +451
self.assertEqual(
mock_get_serial_log.call_args.args[3],
"2026-06-01T00:00:00Z",
)
self.assertEqual(
mock_get_serial_log.call_args.args[4],
"2026-06-30T23:59:59Z",
)

@patch(
"canonicalwebteam.store_api.publishergw.PublisherGW"
+ ".get_store_model_serial_logs"
)
def test_get_serial_log_with_page_size(self, mock_get_serial_log):
mock_serial_log = [
{
"brand-id": "test-brand-id",
"created-at": "2026-03-23T04:00:23.875000",
"model-name": "test-model",
"serial": "test-serial",
}
]
mock_get_serial_log.return_value = mock_serial_log
response = self.client.get(
"/api/store/1/models/test-model/serial-log?page-size=50"
)
data = response.json
Comment thread
steverydz marked this conversation as resolved.

self.assertEqual(response.status_code, 200)
self.assertTrue(data["success"])
self.assertEqual(data["data"], mock_serial_log)

mock_get_serial_log.assert_called_once()
# Arguments are passed positionally:
# session, store_id, model_name, start_time,
# end_time, page_size, cursor
self.assertEqual(
mock_get_serial_log.call_args.args[5],
"50",
)

@patch(
"canonicalwebteam.store_api.publishergw.PublisherGW"
+ ".get_store_model_serial_logs"
)
def test_get_serial_log_with_next_page(self, mock_get_serial_log):
mock_serial_log = [
{
"brand-id": "test-brand-id",
"created-at": "2026-03-23T04:00:23.875000",
"model-name": "test-model",
"serial": "test-serial",
}
]
mock_get_serial_log.return_value = mock_serial_log
response = self.client.get(
"/api/store/1/models/test-model/serial-log?next-page=nextpage"
)
data = response.json

self.assertEqual(response.status_code, 200)
self.assertTrue(data["success"])
self.assertEqual(data["data"], mock_serial_log)

mock_get_serial_log.assert_called_once()
# Arguments are passed positionally:
# session, store_id, model_name, start_time,
# end_time, page_size, cursor
self.assertEqual(
mock_get_serial_log.call_args.args[6],
"nextpage",
)
Comment on lines +516 to +523
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

bad suggestion IMO, I feel like this would make the test more brittle and difficult to read


@patch(
"canonicalwebteam.store_api.publishergw.PublisherGW"
+ ".get_store_model_serial_logs"
)
def test_get_serial_log_with_all_parameters(self, mock_get_serial_log):
mock_serial_log = [
{
"brand-id": "test-brand-id",
"created-at": "2026-03-23T04:00:23.875000",
"model-name": "test-model",
"serial": "test-serial",
}
]
mock_get_serial_log.return_value = mock_serial_log
response = self.client.get(
"/api/store/1/models/test-model/serial-log"
"?start-time=2026-01-01T00:00:00Z"
"&end-time=2026-12-31T23:59:59Z"
"&page-size=25"
"&next-page=nextpage"
)
Comment thread
steverydz marked this conversation as resolved.
data = response.json

self.assertEqual(response.status_code, 200)
self.assertTrue(data["success"])
self.assertEqual(data["data"], mock_serial_log)

mock_get_serial_log.assert_called_once()
# Arguments are passed positionally:
# session, store_id, model_name, start_time,
# end_time, page_size, cursor
self.assertEqual(
mock_get_serial_log.call_args.args[3],
"2026-01-01T00:00:00Z",
)
self.assertEqual(
mock_get_serial_log.call_args.args[4],
"2026-12-31T23:59:59Z",
)
self.assertEqual(
mock_get_serial_log.call_args.args[5],
"25",
)
self.assertEqual(mock_get_serial_log.call_args.args[6], "nextpage")
8 changes: 8 additions & 0 deletions webapp/endpoints/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,13 +353,21 @@ def create_remodel_allowlist(store_id: str):
@login_required
@exchange_required
def get_serial_log(store_id: str, model_name: str):
start_time = flask.request.args.get("start-time")
end_time = flask.request.args.get("end-time")
page_size = flask.request.args.get("page-size")
cursor = flask.request.args.get("next-page")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Any reason why we rename cursor to next-page param?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@bartaz This is for clarity for the frontend as cursor isn't necessarily that obvious, although that is what it's called in the store API

res = {}

Comment thread
steverydz marked this conversation as resolved.
try:
logs = publisher_gateway.get_store_model_serial_logs(
flask.session,
store_id,
model_name,
start_time,
end_time,
page_size,
cursor,
Comment thread
steverydz marked this conversation as resolved.
)
res["data"] = logs
res["success"] = True
Expand Down
Loading