@@ -4,7 +4,7 @@ import { CensusType, EnvOptions, PublishedElection, VocdoniSDKClient, WeightedCe
44import { act } from 'react'
55import * as browserModule from '~providers/browser'
66import { ClientContext , ClientProvider , useClient } from '~providers/client'
7- import { fetchSignInfo } from '~providers/csp'
7+ import { fetchCheckMembership , fetchSignInfo } from '~providers/csp'
88import { TestProvider , onlyProps , properProps } from '~providers/test-utils'
99import * as webWorkerModule from '~providers/worker/webWorker'
1010import { ElectionProvider , useElection } from './ElectionProvider'
@@ -17,13 +17,23 @@ vi.mock('~providers/csp', () => ({
1717 at : new Date ( ) . toISOString ( ) ,
1818 } )
1919 ) ,
20+ fetchCheckMembership : vi . fn ( ( ) => Promise . resolve ( { belongs : true , hasVoted : false } ) ) ,
2021 vote : vi . fn ( ) ,
2122} ) )
2223
2324describe ( '<ElectionProvider />' , ( ) => {
2425 beforeEach ( ( ) => {
2526 localStorage . removeItem ( 'csp_token' )
26- vi . mocked ( fetchSignInfo ) . mockClear ( )
27+ // mockReset (not mockClear) drains any *Once queued implementations so they
28+ // can't leak into later tests, then we re-establish the default behaviour.
29+ vi . mocked ( fetchSignInfo ) . mockReset ( )
30+ vi . mocked ( fetchSignInfo ) . mockResolvedValue ( {
31+ address : '0x0' ,
32+ nullifier : '0xdeadbeef' ,
33+ at : new Date ( ) . toISOString ( ) ,
34+ } )
35+ vi . mocked ( fetchCheckMembership ) . mockReset ( )
36+ vi . mocked ( fetchCheckMembership ) . mockResolvedValue ( { belongs : true , hasVoted : false } )
2737 } )
2838
2939 it ( 'renders child elements' , ( ) => {
@@ -876,9 +886,65 @@ describe('<ElectionProvider />', () => {
876886 localStorage . removeItem ( 'csp_token' )
877887 } )
878888
879- it ( 'enables voting when CSP token is set after initial render and sign-info returns 401' , async ( ) => {
880- vi . mocked ( fetchSignInfo ) . mockRejectedValueOnce ( { status : 401 } )
889+ it ( 'gates CSP voters out of census when bundle check reports they do not belong' , async ( ) => {
890+ localStorage . setItem ( 'csp_token' , 'token' )
891+ vi . mocked ( fetchCheckMembership ) . mockResolvedValue ( { belongs : false , hasVoted : false } )
892+
893+ const signer = Wallet . createRandom ( )
894+ const client = new VocdoniSDKClient ( {
895+ env : EnvOptions . STG ,
896+ wallet : signer ,
897+ } )
898+ client . voteService . info = vi . fn ( ) . mockResolvedValue ( { voteID : null , overwriteCount : 0 } )
899+
900+ const census = new WeightedCensus ( )
901+ census . type = CensusType . CSP
902+ census . censusURI = 'https://csp.example/api'
903+
904+ // @ts -ignore
905+ const election = PublishedElection . build ( {
906+ id : 'csp-election-not-belonging' ,
907+ title : 'test' ,
908+ description : 'test' ,
909+ endDate : new Date ( ) ,
910+ census,
911+ electionType : { anonymous : false } ,
912+ voteType : { maxVoteOverwrites : 0 , maxCount : 1 , maxValue : 1 } ,
913+ } )
914+
915+ const wrapper = ( props : any ) => {
916+ return (
917+ < TestProvider >
918+ < ClientProvider { ...onlyProps ( props ) } >
919+ < ElectionProvider { ...properProps ( props ) } />
920+ </ ClientProvider >
921+ </ TestProvider >
922+ )
923+ }
924+
925+ const { result } = renderHook ( ( ) => useElection ( ) , {
926+ wrapper,
927+ initialProps : { election, fetchCensus : true , client, signer } ,
928+ } )
929+
930+ await waitFor ( ( ) => {
931+ expect ( result . current . loaded . census ) . toBeTruthy ( )
932+ } )
881933
934+ expect ( fetchCheckMembership ) . toHaveBeenCalledWith (
935+ expect . objectContaining ( {
936+ endpoint : 'https://csp.example/api' ,
937+ authToken : 'token' ,
938+ electionId : 'csp-election-not-belonging' ,
939+ } )
940+ )
941+ expect ( result . current . isInCensus ) . toBeFalsy ( )
942+ expect ( result . current . isAbleToVote ) . toBeFalsy ( )
943+
944+ localStorage . removeItem ( 'csp_token' )
945+ } )
946+
947+ it ( 'enables voting when the CSP token is set after the initial render' , async ( ) => {
882948 const client = new VocdoniSDKClient ( {
883949 env : EnvOptions . STG ,
884950 } )
@@ -925,9 +991,9 @@ describe('<ElectionProvider />', () => {
925991 expect ( result . current . isAbleToVote ) . toBeTruthy ( )
926992 } )
927993
928- it ( 'treats 401 on CSP sign info as no prior vote ' , async ( ) => {
994+ it ( 'grants full allowance to a CSP member who has not voted (check hasVoted=false) ' , async ( ) => {
929995 localStorage . setItem ( 'csp_token' , 'token' )
930- vi . mocked ( fetchSignInfo ) . mockRejectedValueOnce ( { status : 401 } )
996+ vi . mocked ( fetchCheckMembership ) . mockResolvedValue ( { belongs : true , hasVoted : false } )
931997
932998 const signer = Wallet . createRandom ( )
933999 const client = new VocdoniSDKClient ( {
@@ -941,7 +1007,7 @@ describe('<ElectionProvider />', () => {
9411007
9421008 // @ts -ignore
9431009 const election = PublishedElection . build ( {
944- id : 'csp-election-401 ' ,
1010+ id : 'csp-election-not-voted ' ,
9451011 title : 'test' ,
9461012 description : 'test' ,
9471013 endDate : new Date ( ) ,
@@ -981,15 +1047,18 @@ describe('<ElectionProvider />', () => {
9811047 } )
9821048
9831049 expect ( result . current . election . voted ) . toBeNull ( )
984- expect ( result . current . election . loaded . voted ) . toBeFalsy ( )
9851050 expect ( result . current . election . votesLeft ) . toBe ( 1 )
9861051 expect ( result . current . election . isAbleToVote ) . toBeTruthy ( )
1052+ // membership check resolved hasVoted directly, so no nullifier lookup is needed
1053+ expect ( fetchSignInfo ) . not . toHaveBeenCalled ( )
9871054
9881055 localStorage . removeItem ( 'csp_token' )
9891056 } )
9901057
9911058 it ( 'sets CSP votesLeft to 0 when vote exists and overwrites are disabled' , async ( ) => {
9921059 localStorage . setItem ( 'csp_token' , 'token' )
1060+ // hasVoted=true forces the exact-overwrite resolution via the nullifier
1061+ vi . mocked ( fetchCheckMembership ) . mockResolvedValue ( { belongs : true , hasVoted : true } )
9931062
9941063 const signer = Wallet . createRandom ( )
9951064 const client = new VocdoniSDKClient ( {
@@ -1106,7 +1175,7 @@ describe('<ElectionProvider />', () => {
11061175 } )
11071176
11081177 await waitFor ( ( ) => {
1109- expect ( result . current . election . loaded . voted ) . toBeTruthy ( )
1178+ expect ( result . current . election . isAbleToVote ) . toBe ( false )
11101179 } )
11111180
11121181 expect ( result . current . election . isInCensus ) . toBeFalsy ( )
0 commit comments