-
Notifications
You must be signed in to change notification settings - Fork 107
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(EU Covid Certificate): [IAGP-5] Types, actions & store for EU C…
…ovid Certificate (#3076) * add placeholders for foldering * wip datamodel * add actions & store * add networking handlers * renaming * fix wrong import * add test * revert * add test * add selector * update certificate types * change selector * add generator * Update ts/features/euCovidCert/saga/index.ts Co-authored-by: Matteo Boschi <[email protected]>
- Loading branch information
1 parent
c8c9267
commit 4a04070
Showing
19 changed files
with
369 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { combineReducers } from "redux"; | ||
import { Action } from "../../../../store/actions/types"; | ||
import { | ||
euCovidCertReducer, | ||
EuCovidCertState | ||
} from "../../../euCovidCert/store/reducers"; | ||
|
||
export type FeaturesState = { | ||
euCovidCert: EuCovidCertState; | ||
}; | ||
|
||
export const featuresReducer = combineReducers<FeaturesState, Action>({ | ||
euCovidCert: euCovidCertReducer | ||
}); |
Empty file.
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import { SagaIterator } from "redux-saga"; | ||
import { takeLatest } from "redux-saga/effects"; | ||
import { euCovidCertificateGet } from "../store/actions"; | ||
import { handleGetEuCovidCertificate } from "./networking/handleGetEuCovidCertificate"; | ||
|
||
/** | ||
* Handle the EU Covid Certificate requests | ||
* @param _ | ||
*/ | ||
export function* watchEUCovidCertificateSaga(_: string): SagaIterator { | ||
// const euCovidCertClient = BackendBpdClient(bpdApiUrlPrefix, bpdBearerToken); | ||
|
||
yield takeLatest(euCovidCertificateGet.request, handleGetEuCovidCertificate); | ||
} |
20 changes: 20 additions & 0 deletions
20
ts/features/euCovidCert/saga/networking/handleGetEuCovidCertificate.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { delay, Effect, put } from "redux-saga/effects"; | ||
import { ActionType } from "typesafe-actions"; | ||
import { euCovidCertificateGet } from "../../store/actions"; | ||
|
||
/** | ||
* Handle the remote call to retrieve the certificate data | ||
* @param action | ||
*/ | ||
export function* handleGetEuCovidCertificate( | ||
action: ActionType<typeof euCovidCertificateGet.request> | ||
): Generator<Effect, void> { | ||
// TODO: add networking logic | ||
yield delay(500); | ||
yield put( | ||
euCovidCertificateGet.success({ | ||
authCode: action.payload, | ||
kind: "notFound" | ||
}) | ||
); | ||
} |
Empty file.
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { ActionType, createAsyncAction } from "typesafe-actions"; | ||
import { GenericError } from "../../../../utils/errors"; | ||
import { EUCovidCertificateAuthCode } from "../../types/EUCovidCertificate"; | ||
import { | ||
EUCovidCertificateResponse, | ||
WithEUCovidCertAuthCode | ||
} from "../../types/EUCovidCertificateResponse"; | ||
|
||
/** | ||
* The user requests the EU Covid certificate, starting from the auth_code | ||
*/ | ||
export const euCovidCertificateGet = createAsyncAction( | ||
"EUCOVIDCERT_REQUEST", | ||
"EUCOVIDCERT_SUCCESS", | ||
"EUCOVIDCERT_FAILURE" | ||
)< | ||
EUCovidCertificateAuthCode, | ||
EUCovidCertificateResponse, | ||
WithEUCovidCertAuthCode<GenericError> | ||
>(); | ||
|
||
export type EuCovidCertActions = ActionType<typeof euCovidCertificateGet>; |
107 changes: 107 additions & 0 deletions
107
ts/features/euCovidCert/store/reducers/__test__/byAuthCode.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import { pot } from "italia-ts-commons"; | ||
import { createStore } from "redux"; | ||
import { applicationChangeState } from "../../../../../store/actions/application"; | ||
import { appReducer } from "../../../../../store/reducers"; | ||
import { GenericError } from "../../../../../utils/errors"; | ||
import { EUCovidCertificateAuthCode } from "../../../types/EUCovidCertificate"; | ||
import { | ||
EUCovidCertificateResponse, | ||
WithEUCovidCertAuthCode | ||
} from "../../../types/EUCovidCertificateResponse"; | ||
import { euCovidCertificateGet } from "../../actions"; | ||
import { euCovidCertificateFromAuthCodeSelector } from "../byAuthCode"; | ||
|
||
const authCode = "authCode1" as EUCovidCertificateAuthCode; | ||
const mockResponseSuccess: EUCovidCertificateResponse = { | ||
authCode, | ||
kind: "notFound" | ||
}; | ||
|
||
const mockFailure: WithEUCovidCertAuthCode<GenericError> = { | ||
authCode, | ||
kind: "generic", | ||
value: new Error("A generic error") | ||
}; | ||
|
||
describe("Test byAuthCode reducer & selector behaviour", () => { | ||
it("Initial state should be pot.none", () => { | ||
const globalState = appReducer(undefined, applicationChangeState("active")); | ||
expect(globalState.features.euCovidCert.byAuthCode).toStrictEqual({}); | ||
expect( | ||
euCovidCertificateFromAuthCodeSelector(globalState, authCode) | ||
).toStrictEqual(pot.none); | ||
}); | ||
it("Should be pot.noneLoading after the first loading action dispatched", () => { | ||
const globalState = appReducer(undefined, applicationChangeState("active")); | ||
const store = createStore(appReducer, globalState as any); | ||
store.dispatch(euCovidCertificateGet.request(authCode)); | ||
|
||
const byAuthCode = store.getState().features.euCovidCert.byAuthCode; | ||
|
||
expect(byAuthCode[authCode]).toStrictEqual(pot.noneLoading); | ||
expect( | ||
euCovidCertificateFromAuthCodeSelector(store.getState(), authCode) | ||
).toStrictEqual(pot.noneLoading); | ||
}); | ||
it("Should be pot.some with the response, after the success action", () => { | ||
const globalState = appReducer(undefined, applicationChangeState("active")); | ||
const store = createStore(appReducer, globalState as any); | ||
store.dispatch(euCovidCertificateGet.request(authCode)); | ||
store.dispatch(euCovidCertificateGet.success(mockResponseSuccess)); | ||
|
||
expect( | ||
store.getState().features.euCovidCert.byAuthCode[authCode] | ||
).toStrictEqual(pot.some(mockResponseSuccess)); | ||
expect( | ||
euCovidCertificateFromAuthCodeSelector(store.getState(), authCode) | ||
).toStrictEqual(pot.some(mockResponseSuccess)); | ||
|
||
store.dispatch(euCovidCertificateGet.request(authCode)); | ||
|
||
expect( | ||
store.getState().features.euCovidCert.byAuthCode[authCode] | ||
).toStrictEqual(pot.someLoading(mockResponseSuccess)); | ||
expect( | ||
euCovidCertificateFromAuthCodeSelector(store.getState(), authCode) | ||
).toStrictEqual(pot.someLoading(mockResponseSuccess)); | ||
|
||
store.dispatch(euCovidCertificateGet.failure(mockFailure)); | ||
|
||
expect( | ||
store.getState().features.euCovidCert.byAuthCode[authCode] | ||
).toStrictEqual(pot.someError(mockResponseSuccess, mockFailure.value)); | ||
expect( | ||
euCovidCertificateFromAuthCodeSelector(store.getState(), authCode) | ||
).toStrictEqual(pot.someError(mockResponseSuccess, mockFailure.value)); | ||
}); | ||
it("Should be pot.noneError after the failure action", () => { | ||
const globalState = appReducer(undefined, applicationChangeState("active")); | ||
const store = createStore(appReducer, globalState as any); | ||
store.dispatch(euCovidCertificateGet.request(authCode)); | ||
store.dispatch(euCovidCertificateGet.failure(mockFailure)); | ||
|
||
expect( | ||
store.getState().features.euCovidCert.byAuthCode[authCode] | ||
).toStrictEqual(pot.noneError(mockFailure.value)); | ||
expect( | ||
euCovidCertificateFromAuthCodeSelector(store.getState(), authCode) | ||
).toStrictEqual(pot.noneError(mockFailure.value)); | ||
|
||
store.dispatch(euCovidCertificateGet.request(authCode)); | ||
expect( | ||
store.getState().features.euCovidCert.byAuthCode[authCode] | ||
).toStrictEqual(pot.noneLoading); | ||
expect( | ||
euCovidCertificateFromAuthCodeSelector(store.getState(), authCode) | ||
).toStrictEqual(pot.noneLoading); | ||
|
||
store.dispatch(euCovidCertificateGet.success(mockResponseSuccess)); | ||
|
||
expect( | ||
store.getState().features.euCovidCert.byAuthCode[authCode] | ||
).toStrictEqual(pot.some(mockResponseSuccess)); | ||
expect( | ||
euCovidCertificateFromAuthCodeSelector(store.getState(), authCode) | ||
).toStrictEqual(pot.some(mockResponseSuccess)); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { pot } from "italia-ts-commons"; | ||
import { createSelector } from "reselect"; | ||
import { getType } from "typesafe-actions"; | ||
import { Action } from "../../../../store/actions/types"; | ||
import { IndexedById } from "../../../../store/helpers/indexer"; | ||
import { | ||
toError, | ||
toLoading, | ||
toSome | ||
} from "../../../../store/reducers/IndexedByIdPot"; | ||
import { GlobalState } from "../../../../store/reducers/types"; | ||
import { EUCovidCertificateAuthCode } from "../../types/EUCovidCertificate"; | ||
import { EUCovidCertificateResponse } from "../../types/EUCovidCertificateResponse"; | ||
import { euCovidCertificateGet } from "../actions"; | ||
|
||
type EuCovidCertByIdState = IndexedById< | ||
pot.Pot<EUCovidCertificateResponse, Error> | ||
>; | ||
|
||
/** | ||
* Store the EU Certificate response status based on the AuthCode used to issue the request | ||
* @param state | ||
* @param action | ||
*/ | ||
export const euCovidCertByAuthCodeReducer = ( | ||
state: EuCovidCertByIdState = {}, | ||
action: Action | ||
): EuCovidCertByIdState => { | ||
switch (action.type) { | ||
case getType(euCovidCertificateGet.request): | ||
return toLoading(action.payload, state); | ||
case getType(euCovidCertificateGet.success): | ||
return toSome(action.payload.authCode, state, action.payload); | ||
case getType(euCovidCertificateGet.failure): | ||
return toError(action.payload.authCode, state, action.payload.value); | ||
} | ||
|
||
return state; | ||
}; | ||
|
||
/** | ||
* From authCode to EUCovidCertificateResponse | ||
*/ | ||
export const euCovidCertificateFromAuthCodeSelector = createSelector( | ||
[ | ||
(state: GlobalState) => state.features.euCovidCert.byAuthCode, | ||
(_: GlobalState, authCode: EUCovidCertificateAuthCode) => authCode | ||
], | ||
(byAuthCode, authCode): pot.Pot<EUCovidCertificateResponse, Error> => | ||
byAuthCode[authCode] ?? pot.none | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { pot } from "italia-ts-commons"; | ||
import { combineReducers } from "redux"; | ||
import { Action } from "../../../../store/actions/types"; | ||
import { IndexedById } from "../../../../store/helpers/indexer"; | ||
import { EUCovidCertificateResponse } from "../../types/EUCovidCertificateResponse"; | ||
import { euCovidCertByAuthCodeReducer } from "./byAuthCode"; | ||
|
||
export type EuCovidCertState = { | ||
byAuthCode: IndexedById<pot.Pot<EUCovidCertificateResponse, Error>>; | ||
}; | ||
|
||
export const euCovidCertReducer = combineReducers<EuCovidCertState, Action>({ | ||
// save, using the AuthCode as key the the pot.Pot<EUCovidCertificateResponse, Error> response | ||
byAuthCode: euCovidCertByAuthCodeReducer | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { IUnitTag } from "italia-ts-commons/lib/units"; | ||
|
||
/** | ||
* The unique ID of a EU Covid Certificate | ||
*/ | ||
type EUCovidCertificateId = string & IUnitTag<"EUCovidCertificateId">; | ||
|
||
/** | ||
* The auth code used to request the EU Covid Certificate, received via message | ||
*/ | ||
export type EUCovidCertificateAuthCode = string & | ||
IUnitTag<"EUCovidCertificateAuthCode">; | ||
|
||
type WithEUCovidCertificateId<T> = T & { | ||
id: EUCovidCertificateId; | ||
}; | ||
|
||
type QRCode = { | ||
mimeType: "image/png" | "image/svg"; | ||
content: string; | ||
}; | ||
|
||
type ValidCertificate = { | ||
kind: "valid"; | ||
qrCode: QRCode; | ||
markdownPreview: string; | ||
markdownDetails: string; | ||
}; | ||
|
||
type ExpiredCertificate = { | ||
kind: "expired"; | ||
}; | ||
|
||
type RevokedCertificate = { | ||
kind: "revoked"; | ||
revokedOn: Date; | ||
}; | ||
|
||
/** | ||
* This type represents the EU Covid Certificate with the different states & data | ||
*/ | ||
export type EUCovidCertificate = WithEUCovidCertificateId< | ||
ValidCertificate | ExpiredCertificate | RevokedCertificate | ||
>; |
65 changes: 65 additions & 0 deletions
65
ts/features/euCovidCert/types/EUCovidCertificateResponse.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import { | ||
EUCovidCertificate, | ||
EUCovidCertificateAuthCode | ||
} from "./EUCovidCertificate"; | ||
|
||
type EUCovidCertificateResponseSuccess = { | ||
kind: "success"; | ||
value: EUCovidCertificate; | ||
}; | ||
|
||
/** | ||
* The required certificated is not found (403) | ||
*/ | ||
type EUCovidCertificateResponseNotFound = { | ||
kind: "notFound"; | ||
}; | ||
|
||
/** | ||
* The required certificate have a wrong format (400) | ||
*/ | ||
type EUCovidCertificateResponseWrongFormat = { | ||
kind: "wrongFormat"; | ||
}; | ||
|
||
/** | ||
* A generic error response was received (500, others, generic error) | ||
*/ | ||
type EUCovidCertificateResponseGenericError = { | ||
kind: "genericError"; | ||
}; | ||
|
||
/** | ||
* The EU Covid certificate service is not operational (410) | ||
*/ | ||
type EUCovidCertificateResponseNotOperational = { | ||
kind: "notOperational"; | ||
}; | ||
|
||
/** | ||
* The EU Covid certificate service is not operational (504) | ||
*/ | ||
type EUCovidCertificateResponseTemporarilyNotAvailable = { | ||
kind: "temporarilyNotAvailable"; | ||
}; | ||
|
||
type EUCovidCertificateResponseFailure = | ||
| EUCovidCertificateResponseNotFound | ||
| EUCovidCertificateResponseWrongFormat | ||
| EUCovidCertificateResponseGenericError | ||
| EUCovidCertificateResponseNotOperational | ||
| EUCovidCertificateResponseTemporarilyNotAvailable; | ||
|
||
/** | ||
* Bind the response with the initial authCode | ||
*/ | ||
export type WithEUCovidCertAuthCode<T> = T & { | ||
authCode: EUCovidCertificateAuthCode; | ||
}; | ||
|
||
/** | ||
* This type represents all the possible remote responses | ||
*/ | ||
export type EUCovidCertificateResponse = WithEUCovidCertAuthCode< | ||
EUCovidCertificateResponseSuccess | EUCovidCertificateResponseFailure | ||
>; |
Oops, something went wrong.