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
6 changes: 5 additions & 1 deletion admin/src/PlaytestDetailPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const mockGetAdtLinkages = vi.fn()
const mockGetAdtGames = vi.fn()
const mockGetAdtBuilds = vi.fn()
const mockChangeAdtBuild = vi.fn()
const mockCheckAdtBuild = vi.fn()
const mockApprove = vi.fn()
const mockReject = vi.fn()
const mockRetryDm = vi.fn()
Expand All @@ -49,7 +50,8 @@ vi.mock('./playtesthubapi/generated-admin/queries/PlaytesthubServiceAdmin.query'
Survey_ByPlaytestId: 'survey-by-playtest-id',
GamesAdt_ByAdtLinkageId: 'adt-games-by-linkage-id',
BuildsAdt_ByAdtLinkageId: 'adt-builds-by-linkage-id',
AdtBuildChange_ByPlaytestId: 'adt-build-change-by-playtest-id'
AdtBuildChange_ByPlaytestId: 'adt-build-change-by-playtest-id',
AdtBuildCheck_ByPlaytestId: 'adt-build-check-by-playtest-id'
},
usePlaytesthubServiceAdminApi_GetPlaytests: (...a: unknown[]) => mockGetPlaytests(...a),
usePlaytesthubServiceAdminApi_CreatePlaytest_ByPlaytestIdTransitionStatuMutation: (...a: unknown[]) =>
Expand All @@ -67,6 +69,7 @@ vi.mock('./playtesthubapi/generated-admin/queries/PlaytesthubServiceAdmin.query'
usePlaytesthubServiceAdminApi_GetGamesAdt_ByAdtLinkageId: (...a: unknown[]) => mockGetAdtGames(...a),
usePlaytesthubServiceAdminApi_GetBuildsAdt_ByAdtLinkageId: (...a: unknown[]) => mockGetAdtBuilds(...a),
usePlaytesthubServiceAdminApi_CreateAdtBuildChange_ByPlaytestIdMutation: (...a: unknown[]) => mockChangeAdtBuild(...a),
usePlaytesthubServiceAdminApi_CreateAdtBuildCheck_ByPlaytestIdMutation: (...a: unknown[]) => mockCheckAdtBuild(...a),
usePlaytesthubServiceAdminApi_CreateApplicant_ByApplicantIdApproveMutation: (...a: unknown[]) =>
mockApprove(...a),
usePlaytesthubServiceAdminApi_CreateApplicant_ByApplicantIdRejectMutation: (...a: unknown[]) => mockReject(...a),
Expand Down Expand Up @@ -130,6 +133,7 @@ beforeEach(() => {
mockGetAdtGames.mockReturnValue({ data: { games: [] }, isLoading: false, error: null })
mockGetAdtBuilds.mockReturnValue({ data: { builds: [] }, isLoading: false, error: null })
mockChangeAdtBuild.mockReturnValue({ mutate: vi.fn(), isPending: false, isError: false, error: null })
mockCheckAdtBuild.mockReturnValue({ mutate: vi.fn(), isPending: false, isError: false, error: null })
mockApprove.mockReturnValue({ mutate: vi.fn(), isPending: false })
mockReject.mockReturnValue({ mutate: vi.fn(), isPending: false })
mockRetryDm.mockReturnValue({ mutate: vi.fn(), isPending: false })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { ApiUtils, Network } from '@accelbyte/sdk'
import type { AxiosRequestConfig, AxiosResponse } from 'axios'
import { PlaytesthubServiceApproveApplicantBody } from '../generated-definitions/PlaytesthubServiceApproveApplicantBody.js'
import { PlaytesthubServiceChangeAdtBuildBody } from '../generated-definitions/PlaytesthubServiceChangeAdtBuildBody.js'
import { PlaytesthubServiceCheckAdtBuildBody } from '../generated-definitions/PlaytesthubServiceCheckAdtBuildBody.js'
import { PlaytesthubServiceCompleteAdtLinkBody } from '../generated-definitions/PlaytesthubServiceCompleteAdtLinkBody.js'
import { PlaytesthubServiceCreateAnnouncementBody } from '../generated-definitions/PlaytesthubServiceCreateAnnouncementBody.js'
import { PlaytesthubServiceCreatePlaytestBody } from '../generated-definitions/PlaytesthubServiceCreatePlaytestBody.js'
Expand All @@ -29,6 +30,7 @@ import { PlaytesthubServiceUploadCodesBody } from '../generated-definitions/Play
import { V1AdminGetPlaytestResponse } from '../generated-definitions/V1AdminGetPlaytestResponse.js'
import { V1ApproveApplicantResponse } from '../generated-definitions/V1ApproveApplicantResponse.js'
import { V1ChangeAdtBuildResponse } from '../generated-definitions/V1ChangeAdtBuildResponse.js'
import { V1CheckAdtBuildResponse } from '../generated-definitions/V1CheckAdtBuildResponse.js'
import { V1CompleteAdtLinkResponse } from '../generated-definitions/V1CompleteAdtLinkResponse.js'
import { V1CreateAnnouncementResponse } from '../generated-definitions/V1CreateAnnouncementResponse.js'
import { V1CreatePlaytestResponse } from '../generated-definitions/V1CreatePlaytestResponse.js'
Expand Down Expand Up @@ -330,6 +332,16 @@ export function PlaytesthubServiceAdminApi(sdk: AccelByteSDK, args?: SdkSetConfi
return resp.response
}

async function createAdtBuildCheck_ByPlaytestId(
playtestId: string,
data: PlaytesthubServiceCheckAdtBuildBody
): Promise<AxiosResponse<V1CheckAdtBuildResponse>> {
const $ = new PlaytesthubServiceAdmin$(axiosInstance, namespace, useSchemaValidation)
const resp = await $.createAdtBuildCheck_ByPlaytestId(playtestId, data)
if (resp.error) throw resp.error
return resp.response
}

async function createPlaytest_ByPlaytestIdTransitionStatu(
playtestId: string,
data: PlaytesthubServiceTransitionPlaytestStatusBody
Expand Down Expand Up @@ -482,6 +494,10 @@ export function PlaytesthubServiceAdminApi(sdk: AccelByteSDK, args?: SdkSetConfi
* Resolves recipients at call time (NOT a stored snapshot). Subject + message are PII-sensitive and are never written to audit JSONB or structured logs.
*/
createAnnouncement_ByPlaytestId,
/**
* Issues a download URL for the playtest's current adt_build_id (same call as ApproveApplicant) and persists adt_build_status: 'OK' when a URL was minted, 'UNAVAILABLE' when ADT returns build-not-found. Non-ADT playtest → FailedPrecondition. Linkage missing / ADT unreachable → FailedPrecondition / Unavailable (status not overwritten). Side effect: a throwaway download URL is minted on success.
*/
createAdtBuildCheck_ByPlaytestId,

createPlaytest_ByPlaytestIdTransitionStatu,
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Validate } from '@accelbyte/sdk'
import type { AxiosInstance, AxiosRequestConfig } from 'axios'
import { PlaytesthubServiceApproveApplicantBody } from '../../generated-definitions/PlaytesthubServiceApproveApplicantBody.js'
import { PlaytesthubServiceChangeAdtBuildBody } from '../../generated-definitions/PlaytesthubServiceChangeAdtBuildBody.js'
import { PlaytesthubServiceCheckAdtBuildBody } from '../../generated-definitions/PlaytesthubServiceCheckAdtBuildBody.js'
import { PlaytesthubServiceCompleteAdtLinkBody } from '../../generated-definitions/PlaytesthubServiceCompleteAdtLinkBody.js'
import { PlaytesthubServiceCreateAnnouncementBody } from '../../generated-definitions/PlaytesthubServiceCreateAnnouncementBody.js'
import { PlaytesthubServiceCreatePlaytestBody } from '../../generated-definitions/PlaytesthubServiceCreatePlaytestBody.js'
Expand All @@ -29,6 +30,7 @@ import { PlaytesthubServiceUploadCodesBody } from '../../generated-definitions/P
import { V1AdminGetPlaytestResponse } from '../../generated-definitions/V1AdminGetPlaytestResponse.js'
import { V1ApproveApplicantResponse } from '../../generated-definitions/V1ApproveApplicantResponse.js'
import { V1ChangeAdtBuildResponse } from '../../generated-definitions/V1ChangeAdtBuildResponse.js'
import { V1CheckAdtBuildResponse } from '../../generated-definitions/V1CheckAdtBuildResponse.js'
import { V1CompleteAdtLinkResponse } from '../../generated-definitions/V1CompleteAdtLinkResponse.js'
import { V1CreateAnnouncementResponse } from '../../generated-definitions/V1CreateAnnouncementResponse.js'
import { V1CreatePlaytestResponse } from '../../generated-definitions/V1CreatePlaytestResponse.js'
Expand Down Expand Up @@ -504,6 +506,26 @@ export class PlaytesthubServiceAdmin$ {
'V1CreateAnnouncementResponse'
)
}
/**
* Issues a download URL for the playtest's current adt_build_id (same call as ApproveApplicant) and persists adt_build_status: 'OK' when a URL was minted, 'UNAVAILABLE' when ADT returns build-not-found. Non-ADT playtest → FailedPrecondition. Linkage missing / ADT unreachable → FailedPrecondition / Unavailable (status not overwritten). Side effect: a throwaway download URL is minted on success.
*/
createAdtBuildCheck_ByPlaytestId(
playtestId: string,
data: PlaytesthubServiceCheckAdtBuildBody
): Promise<Response<V1CheckAdtBuildResponse>> {
const params = {} as AxiosRequestConfig
const url = '/v1/admin/namespaces/{namespace}/playtests/{playtestId}/adt/build:check'
.replace('{namespace}', this.namespace)
.replace('{playtestId}', playtestId)
const resultPromise = this.axiosInstance.post(url, data, { params })

return Validate.validateOrReturnResponse(
this.useSchemaValidation,
() => resultPromise,
V1CheckAdtBuildResponse,
'V1CheckAdtBuildResponse'
)
}

createPlaytest_ByPlaytestIdTransitionStatu(
playtestId: string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { PlaytesthubServiceAdminApi } from '../PlaytesthubServiceAdminApi.js'

import { PlaytesthubServiceApproveApplicantBody } from '../../generated-definitions/PlaytesthubServiceApproveApplicantBody.js'
import { PlaytesthubServiceChangeAdtBuildBody } from '../../generated-definitions/PlaytesthubServiceChangeAdtBuildBody.js'
import { PlaytesthubServiceCheckAdtBuildBody } from '../../generated-definitions/PlaytesthubServiceCheckAdtBuildBody.js'
import { PlaytesthubServiceCompleteAdtLinkBody } from '../../generated-definitions/PlaytesthubServiceCompleteAdtLinkBody.js'
import { PlaytesthubServiceCreateAnnouncementBody } from '../../generated-definitions/PlaytesthubServiceCreateAnnouncementBody.js'
import { PlaytesthubServiceCreatePlaytestBody } from '../../generated-definitions/PlaytesthubServiceCreatePlaytestBody.js'
Expand All @@ -32,6 +33,7 @@ import { PlaytesthubServiceUploadCodesBody } from '../../generated-definitions/P
import { V1AdminGetPlaytestResponse } from '../../generated-definitions/V1AdminGetPlaytestResponse.js'
import { V1ApproveApplicantResponse } from '../../generated-definitions/V1ApproveApplicantResponse.js'
import { V1ChangeAdtBuildResponse } from '../../generated-definitions/V1ChangeAdtBuildResponse.js'
import { V1CheckAdtBuildResponse } from '../../generated-definitions/V1CheckAdtBuildResponse.js'
import { V1CompleteAdtLinkResponse } from '../../generated-definitions/V1CompleteAdtLinkResponse.js'
import { V1CreateAnnouncementResponse } from '../../generated-definitions/V1CreateAnnouncementResponse.js'
import { V1CreatePlaytestResponse } from '../../generated-definitions/V1CreatePlaytestResponse.js'
Expand Down Expand Up @@ -87,6 +89,7 @@ export const Key_PlaytesthubServiceAdmin = {
Participants_ByPlaytestId: 'Playtesthubapi.PlaytesthubServiceAdmin.Participants_ByPlaytestId',
Announcements_ByPlaytestId: 'Playtesthubapi.PlaytesthubServiceAdmin.Announcements_ByPlaytestId',
Announcement_ByPlaytestId: 'Playtesthubapi.PlaytesthubServiceAdmin.Announcement_ByPlaytestId',
AdtBuildCheck_ByPlaytestId: 'Playtesthubapi.PlaytesthubServiceAdmin.AdtBuildCheck_ByPlaytestId',
Playtest_ByPlaytestIdTransitionStatu: 'Playtesthubapi.PlaytesthubServiceAdmin.Playtest_ByPlaytestIdTransitionStatu',
AdtBuildChange_ByPlaytestId: 'Playtesthubapi.PlaytesthubServiceAdmin.AdtBuildChange_ByPlaytestId',
SurveyResponses_ByPlaytestId: 'Playtesthubapi.PlaytesthubServiceAdmin.SurveyResponses_ByPlaytestId',
Expand Down Expand Up @@ -1092,6 +1095,49 @@ export const usePlaytesthubServiceAdminApi_CreateAnnouncement_ByPlaytestIdMutati
})
}

/**
* Issues a download URL for the playtest's current adt_build_id (same call as ApproveApplicant) and persists adt_build_status: 'OK' when a URL was minted, 'UNAVAILABLE' when ADT returns build-not-found. Non-ADT playtest → FailedPrecondition. Linkage missing / ADT unreachable → FailedPrecondition / Unavailable (status not overwritten). Side effect: a throwaway download URL is minted on success.
*
* #### Default Query Options
* The default options include:
* ```
* {
* queryKey: [Key_PlaytesthubServiceAdmin.AdtBuildCheck_ByPlaytestId, input]
* }
* ```
*/
export const usePlaytesthubServiceAdminApi_CreateAdtBuildCheck_ByPlaytestIdMutation = (
sdk: AccelByteSDK,
options?: Omit<
UseMutationOptions<
V1CheckAdtBuildResponse,
AxiosError<ApiError>,
SdkSetConfigParam & { playtestId: string; data: PlaytesthubServiceCheckAdtBuildBody }
>,
'mutationKey'
>,
callback?: (data: V1CheckAdtBuildResponse) => void
): UseMutationResult<
V1CheckAdtBuildResponse,
AxiosError<ApiError>,
SdkSetConfigParam & { playtestId: string; data: PlaytesthubServiceCheckAdtBuildBody }
> => {
const mutationFn = async (input: SdkSetConfigParam & { playtestId: string; data: PlaytesthubServiceCheckAdtBuildBody }) => {
const response = await PlaytesthubServiceAdminApi(sdk, {
coreConfig: input.coreConfig,
axiosConfig: input.axiosConfig
}).createAdtBuildCheck_ByPlaytestId(input.playtestId, input.data)
callback?.(response.data)
return response.data
}

return useMutation({
mutationKey: [Key_PlaytesthubServiceAdmin.AdtBuildCheck_ByPlaytestId],
mutationFn,
...options
})
}

export const usePlaytesthubServiceAdminApi_CreatePlaytest_ByPlaytestIdTransitionStatuMutation = (
sdk: AccelByteSDK,
options?: Omit<
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright (c) 2022-2026 AccelByte Inc. All Rights Reserved
* This is licensed software from AccelByte Inc, for limitations
* and restrictions contact your company contract manager.
*/
import { z } from 'zod'

export const PlaytesthubServiceCheckAdtBuildBody = z.any()

export interface PlaytesthubServiceCheckAdtBuildBody extends z.TypeOf<typeof PlaytesthubServiceCheckAdtBuildBody> {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright (c) 2022-2026 AccelByte Inc. All Rights Reserved
* This is licensed software from AccelByte Inc, for limitations
* and restrictions contact your company contract manager.
*/
import { z } from 'zod'
import { V1Playtest } from './V1Playtest.js'

export const V1CheckAdtBuildResponse = z.object({ playtest: V1Playtest.nullish(), healthy: z.boolean().nullish() })

export interface V1CheckAdtBuildResponse extends z.TypeOf<typeof V1CheckAdtBuildResponse> {}
4 changes: 3 additions & 1 deletion admin/src/playtesthubapi/generated-definitions/V1Playtest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ export const V1Playtest = z.object({
adtNamespace: z.string().nullish(),
adtGameId: z.string().nullish(),
adtBuildId: z.string().nullish(),
adtFallbackDownloadUrl: z.string().nullish()
adtFallbackDownloadUrl: z.string().nullish(),
adtBuildStatus: z.string().nullish(),
adtBuildCheckedAt: z.string().nullish()
})

export interface V1Playtest extends z.TypeOf<typeof V1Playtest> {}
36 changes: 34 additions & 2 deletions admin/src/tabs/DistributionTab.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@ const mockGetAdtLinkages = vi.fn()
const mockGetAdtGames = vi.fn()
const mockGetAdtBuilds = vi.fn()
const mockChangeAdtBuild = vi.fn()
const mockCheckAdtBuild = vi.fn()

vi.mock('../playtesthubapi/generated-admin/queries/PlaytesthubServiceAdmin.query', () => ({
Key_PlaytesthubServiceAdmin: {
Playtests: 'playtests',
Codes_ByPlaytestId: 'codes-by-playtest-id',
GamesAdt_ByAdtLinkageId: 'adt-games-by-linkage-id',
BuildsAdt_ByAdtLinkageId: 'adt-builds-by-linkage-id',
AdtBuildChange_ByPlaytestId: 'adt-build-change-by-playtest-id'
AdtBuildChange_ByPlaytestId: 'adt-build-change-by-playtest-id',
AdtBuildCheck_ByPlaytestId: 'adt-build-check-by-playtest-id'
},
usePlaytesthubServiceAdminApi_GetCodes_ByPlaytestId: (...a: unknown[]) => mockGetCodes(...a),
usePlaytesthubServiceAdminApi_CreateCodesUpload_ByPlaytestIdMutation: (...a: unknown[]) => mockUploadCodes(...a),
Expand All @@ -32,7 +34,8 @@ vi.mock('../playtesthubapi/generated-admin/queries/PlaytesthubServiceAdmin.query
usePlaytesthubServiceAdminApi_GetAdtLinkages: (...a: unknown[]) => mockGetAdtLinkages(...a),
usePlaytesthubServiceAdminApi_GetGamesAdt_ByAdtLinkageId: (...a: unknown[]) => mockGetAdtGames(...a),
usePlaytesthubServiceAdminApi_GetBuildsAdt_ByAdtLinkageId: (...a: unknown[]) => mockGetAdtBuilds(...a),
usePlaytesthubServiceAdminApi_CreateAdtBuildChange_ByPlaytestIdMutation: (...a: unknown[]) => mockChangeAdtBuild(...a)
usePlaytesthubServiceAdminApi_CreateAdtBuildChange_ByPlaytestIdMutation: (...a: unknown[]) => mockChangeAdtBuild(...a),
usePlaytesthubServiceAdminApi_CreateAdtBuildCheck_ByPlaytestIdMutation: (...a: unknown[]) => mockCheckAdtBuild(...a)
}))

// Stub the build picker so its onPick prop can be driven deterministically
Expand Down Expand Up @@ -84,6 +87,7 @@ beforeEach(() => {
mockGetAdtGames.mockReturnValue({ data: { games: [{ id: 'game-1', name: 'Starfield Dev' }] }, isLoading: false, error: null })
mockGetAdtBuilds.mockReturnValue({ data: { builds: [] }, isLoading: false, error: null })
mockChangeAdtBuild.mockReturnValue({ mutate: vi.fn(), isPending: false, isError: false, error: null })
mockCheckAdtBuild.mockReturnValue({ mutate: vi.fn(), isPending: false, isError: false, error: null })
})

describe('ADTPanel (Change Build)', () => {
Expand Down Expand Up @@ -113,3 +117,31 @@ describe('ADTPanel (Change Build)', () => {
)
})
})

describe('ADTPanel (build health surfacing)', () => {
it('shows no health alert before the build has been checked', () => {
renderTab(ADT_PT)
expect(screen.queryByTestId('adt-build-health-alert')).not.toBeInTheDocument()
})

it('surfaces an error alert when the build is UNAVAILABLE', () => {
renderTab({ ...ADT_PT, adtBuildStatus: 'UNAVAILABLE', adtBuildCheckedAt: '2026-05-26T10:00:00Z' })
const alert = screen.getByTestId('adt-build-health-alert')
expect(alert).toHaveTextContent(/no longer available in ADT/i)
expect(alert).toHaveTextContent(/fallback download URL or change the build/i)
})

it('surfaces a success alert when the build is OK', () => {
renderTab({ ...ADT_PT, adtBuildStatus: 'OK', adtBuildCheckedAt: '2026-05-26T10:00:00Z' })
expect(screen.getByTestId('adt-build-health-alert')).toHaveTextContent(/available in ADT/i)
})

it('calls CheckADTBuild with the playtestId when Check build is clicked', async () => {
const mutate = vi.fn()
mockCheckAdtBuild.mockReturnValue({ mutate, isPending: false, isError: false, error: null })
renderTab(ADT_PT)
const user = userEvent.setup()
await user.click(screen.getByTestId('adt-check-build-btn'))
await waitFor(() => expect(mutate).toHaveBeenCalledWith({ playtestId: 'pt-adt', data: {} }))
})
})
Loading
Loading