Merge branch 'main' into fix/show-reputation-history-related-to-user

This commit is contained in:
HSIAO PO WEN
2024-09-18 18:12:45 +08:00
committed by GitHub
11 changed files with 205 additions and 24 deletions

View File

@@ -1,7 +1,8 @@
import { renderHook, waitFor } from '@testing-library/react'
import { wrapper } from '@/utils/test-helpers/wrapper'
import { renderHook, waitFor } from '@testing-library/react'
import { useEpoch } from './useEpoch'
// Mock useUserState
jest.mock('@/features/core/hooks/useUserState/useUserState', () => ({
useUserState: () => ({
userState: {
@@ -13,10 +14,22 @@ jest.mock('@/features/core/hooks/useUserState/useUserState', () => ({
}),
}))
// Mock useRelayConfig
jest.mock('@/features/core/hooks/useRelayConfig/useRelayConfig', () => ({
useRelayConfig: () => ({
data: {
EPOCH_LENGTH: 300, // 300 seconds
},
isPending: false,
isSuccess: true,
}),
}))
describe('useEpoch', () => {
it('should get epoch time information', async () => {
const { result } = renderHook(useEpoch, { wrapper })
await waitFor(() => expect(result.current.isPending).toBe(false))
await waitFor(() => expect(result.current.currentEpoch).toBe(2))
await waitFor(() => expect(result.current.remainingTime).toBe(120000))
await waitFor(() => expect(result.current.epochLength).toBe(300000))

View File

@@ -1,14 +1,18 @@
import { QueryKeys } from '@/constants/queryKeys'
import { useUserState } from '@/features/core'
import { useRelayConfig, useUserState } from '@/features/core'
import { useQuery } from '@tanstack/react-query'
import isNull from 'lodash/isNull'
import isUndefined from 'lodash/isUndefined'
import { useEffect, useMemo } from 'react'
const epochLength = 300000 // 300000 ms
export function useEpoch() {
const { isPending: isUserStatePending, userState } = useUserState()
const { data: config, isPending: isConfigPending } = useRelayConfig()
// Convert EPOCH_LENGTH from seconds to milliseconds
const epochLength = useMemo(() => {
return config?.EPOCH_LENGTH ? config.EPOCH_LENGTH * 1000 : undefined
}, [config])
const {
isPending: isCurrentEpochPending,
@@ -23,6 +27,7 @@ export function useEpoch() {
return userState.sync.calcCurrentEpoch()
},
staleTime: epochLength,
enabled: !!epochLength,
})
const {
@@ -39,21 +44,27 @@ export function useEpoch() {
return time * 1000
},
staleTime: epochLength,
enabled: !!epochLength,
})
const isPending =
isUserStatePending || isCurrentEpochPending || isRemainingTimePending
isUserStatePending ||
isCurrentEpochPending ||
isRemainingTimePending ||
isConfigPending ||
!epochLength
const epochStartTime = useMemo(() => {
if (
isUndefined(currentEpoch) ||
isNull(currentEpoch) ||
!remainingTime
!remainingTime ||
!epochLength
) {
return 0
}
return Date.now() - (epochLength - remainingTime)
}, [currentEpoch, remainingTime])
}, [currentEpoch, remainingTime, epochLength])
const epochEndTime = useMemo(() => {
if (
@@ -70,7 +81,8 @@ export function useEpoch() {
if (
isUndefined(currentEpoch) ||
isNull(currentEpoch) ||
!remainingTime
!remainingTime ||
!epochLength
) {
return
}
@@ -85,7 +97,13 @@ export function useEpoch() {
return () => {
clearTimeout(timer)
}
}, [currentEpoch, refetchCurrentEpoch, refetchRemainingTime, remainingTime])
}, [
currentEpoch,
refetchCurrentEpoch,
refetchRemainingTime,
remainingTime,
epochLength,
])
return {
isPending,

View File

@@ -5,6 +5,16 @@ import { act, renderHook } from '@testing-library/react'
import nock from 'nock'
import { useCreatePost } from './useCreatePost'
jest.mock('@/features/core/hooks/useRelayConfig/useRelayConfig', () => ({
useRelayConfig: () => ({
data: {
EPOCH_LENGTH: 300,
},
isPending: false,
isSuccess: true,
}),
}))
jest.mock('@/features/core/hooks/useWeb3Provider/useWeb3Provider', () => ({
useWeb3Provider: () => ({
getGuaranteedProvider: () => ({

View File

@@ -5,6 +5,16 @@ import { act, renderHook } from '@testing-library/react'
import nock from 'nock'
import { useVotes } from './useVotes'
jest.mock('@/features/core/hooks/useRelayConfig/useRelayConfig', () => ({
useRelayConfig: () => ({
data: {
EPOCH_LENGTH: 300,
},
isPending: false,
isSuccess: true,
}),
}))
jest.mock('@/features/core/hooks/useUserState/useUserState', () => ({
useUserState: () => ({
userState: {

View File

@@ -1,6 +1,6 @@
import { useUserState } from '@/features/core'
import { useRelayConfig, useUserState } from '@/features/core'
import dayjs from 'dayjs'
import { useCallback, useState } from 'react'
import { useCallback, useMemo, useState } from 'react'
import {
EpochDateService,
FromToEpoch,
@@ -9,14 +9,20 @@ import {
export function useDatePicker() {
const { userState } = useUserState()
const { data: config } = useRelayConfig()
const [startDate, setStartDate] = useState<undefined | Date>(undefined)
const [endDate, setEndDate] = useState<undefined | Date>(undefined)
const [fromToEpoch, setFromToEpoch] = useState<FromToEpoch>(
new InvalidFromToEpoch(),
)
// Calculate epochLength in milliseconds
const epochLength = useMemo(() => {
return config?.EPOCH_LENGTH ? config.EPOCH_LENGTH * 1000 : undefined
}, [config])
const updateFromToEpoch = useCallback(async () => {
if (!userState) {
if (!userState || !epochLength) {
setFromToEpoch(new InvalidFromToEpoch())
return
}
@@ -25,9 +31,10 @@ export function useDatePicker() {
startDate,
endDate,
userState.sync,
epochLength,
),
)
}, [startDate, endDate, userState])
}, [startDate, endDate, userState, epochLength])
const onChange = (dates: [Date | null, Date | null]) => {
const [start, end] = dates

View File

@@ -15,6 +15,7 @@ describe('EpochDateService', () => {
it('should execute properly', () => {
const synchronizer =
new MockSynchronizer() as unknown as Synchronizer
const epochLength = 300000 // 5 minutes in milliseconds
// service start time: 1720656000000 = 1721088000000 - 0 - 300000 * 1440
// service start time: 2024-07-11T00:00:00.000Z
@@ -23,25 +24,45 @@ describe('EpochDateService', () => {
// at service start & now
const date0 = new Date('2024-07-11T00:00:00Z')
expect(
EpochDateService.calcEpochByDate(mockNow, date0, synchronizer),
EpochDateService.calcEpochByDate(
mockNow,
date0,
synchronizer,
epochLength,
),
).toBe(0)
// between service start & now
const date1 = new Date('2024-07-11T00:15:00Z')
expect(
EpochDateService.calcEpochByDate(mockNow, date1, synchronizer),
EpochDateService.calcEpochByDate(
mockNow,
date1,
synchronizer,
epochLength,
),
).toBe(3) // 724 * 300000 + 1720655850000
// before service start
const date2 = new Date('2024-07-10T23:59:00Z')
expect(
EpochDateService.calcEpochByDate(mockNow, date2, synchronizer),
EpochDateService.calcEpochByDate(
mockNow,
date2,
synchronizer,
epochLength,
),
).toBe(0)
// future
const date3 = new Date('2024-07-17T00:00:00Z')
expect(
EpochDateService.calcEpochByDate(mockNow, date3, synchronizer),
EpochDateService.calcEpochByDate(
mockNow,
date3,
synchronizer,
epochLength,
),
).toBe(1440 + 12 * 24)
})
})

View File

@@ -9,21 +9,32 @@ export class EpochDateService {
start: Date | undefined,
end: Date | undefined,
synchoronizer: Synchronizer,
epochLength: number,
) {
if (!!start && !!end && EpochDateService.isValidDateRange(start, end)) {
const [from, to] = EpochDateService.calcEpochsByDates(
[start, end],
synchoronizer,
epochLength,
)
return new ValidFromToEpoch(from, to)
}
return new InvalidFromToEpoch()
}
static calcEpochsByDates(dates: Date[], synchoronizer: Synchronizer) {
static calcEpochsByDates(
dates: Date[],
synchoronizer: Synchronizer,
epochLength: number,
) {
const now = Date.now()
return dates.map((date) =>
EpochDateService.calcEpochByDate(now, date, synchoronizer),
EpochDateService.calcEpochByDate(
now,
date,
synchoronizer,
epochLength,
),
)
}
@@ -31,8 +42,8 @@ export class EpochDateService {
now: number,
date: Date,
synchoronizer: Synchronizer,
epochLength: number,
) {
const epochLength = 300000
const currentEpoch = synchoronizer.calcCurrentEpoch()
const remainingTime = synchoronizer.calcEpochRemainingTime() * 1000
const currentEpochStartTime = now - (epochLength - remainingTime)

View File

@@ -17,6 +17,7 @@ export interface FetchRelayConfigResponse {
UNIREP_ADDRESS: string
APP_ADDRESS: string
ETH_PROVIDER_URL: string
EPOCH_LENGTH: number
}
export type FetchPostsResponse = RelayRawPost[]

View File

@@ -6,6 +6,7 @@ export function buildMockConfigAPI() {
UNIREP_ADDRESS: '0x83cB6AF63eAfEc7998cC601eC3f56d064892b386',
APP_ADDRESS: '0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1',
ETH_PROVIDER_URL: 'http://127.0.0.1:8545',
EPOCH_LENGTH: 300,
}
const expectation = nock(SERVER).get('/api/config').reply(200, response)

View File

@@ -1,12 +1,24 @@
import { DB } from 'anondb/node'
import { Express } from 'express'
import { UNIREP_ADDRESS, APP_ADDRESS, ETH_PROVIDER_URL } from '../config'
import { APP_ADDRESS, ETH_PROVIDER_URL, UNIREP_ADDRESS } from '../config'
import { UnirepSocialSynchronizer } from '../services/singletons/UnirepSocialSynchronizer'
export default (
app: Express,
_: DB,
synchronizer: UnirepSocialSynchronizer
) => {
app.get('/api/config', async (_, res) => {
const epochLength =
await synchronizer.unirepContract.attesterEpochLength(
BigInt(APP_ADDRESS).toString()
)
export default (app: Express) => {
app.get('/api/config', (_, res) =>
res.json({
UNIREP_ADDRESS,
APP_ADDRESS,
ETH_PROVIDER_URL,
EPOCH_LENGTH: epochLength,
})
)
})
}

View File

@@ -0,0 +1,77 @@
import { Unirep, UnirepApp } from '@unirep-app/contracts/typechain-types'
import { DB } from 'anondb'
import { expect } from 'chai'
import { ethers } from 'hardhat'
import { ETH_PROVIDER_URL } from '../src/config'
import { UnirepSocialSynchronizer } from '../src/services/singletons/UnirepSocialSynchronizer'
import { deployContracts, startServer, stopServer } from './environment'
describe('GET /api/config', function () {
let snapshot: any
let express: ChaiHttp.Agent
let unirep: Unirep
let app: UnirepApp
let db: DB
let prover: any
let provider: any
let sync: UnirepSocialSynchronizer
before(async function () {
snapshot = await ethers.provider.send('evm_snapshot', [])
// deploy contracts
const { unirep: _unirep, app: _app } = await deployContracts(100000)
// start server
const {
db: _db,
prover: _prover,
provider: _provider,
synchronizer,
chaiServer,
} = await startServer(_unirep, _app)
express = chaiServer
unirep = _unirep
db = _db
app = _app
prover = _prover
provider = _provider
sync = synchronizer
})
after(async function () {
await stopServer('config', snapshot, sync, express)
})
it('should return the correct configuration', async function () {
const res = await express
.get('/api/config')
.set('content-type', 'application/json')
expect(res).to.have.status(200)
expect(res.body).to.have.property('UNIREP_ADDRESS')
expect(res.body).to.have.property('APP_ADDRESS')
expect(res.body).to.have.property('ETH_PROVIDER_URL')
expect(res.body).to.have.property('EPOCH_LENGTH')
expect(res.body.UNIREP_ADDRESS).to.equal(unirep.address)
expect(res.body.APP_ADDRESS).to.equal(app.address)
expect(res.body.ETH_PROVIDER_URL).to.equal(ETH_PROVIDER_URL)
const expectedEpochLength =
await sync.unirepContract.attesterEpochLength(
BigInt(app.address).toString()
)
expect(res.body.EPOCH_LENGTH).to.equal(expectedEpochLength)
})
it('should return the correct data types', async function () {
const res = await express
.get('/api/config')
.set('content-type', 'application/json')
expect(res).to.have.status(200)
expect(res.body.UNIREP_ADDRESS).to.be.a('string')
expect(res.body.APP_ADDRESS).to.be.a('string')
expect(res.body.ETH_PROVIDER_URL).to.be.a('string')
expect(res.body.EPOCH_LENGTH).to.be.a('number')
})
})