iT邦幫忙

2023 iThome 鐵人賽

DAY 24
0
Modern Web

從 Next.js 開始的 Functional Programming系列 第 24

D24 - 實作異步流程 (十)

  • 分享至 

  • xImage
  •  

今天的目標是通過昨天的測試,完成步驟3到步驟5

第一筆測試

首先看第一筆測試,我們的目標是當使用者發出 http://localhost/api/v1/users/richard_01 請求時回應 200。

it('should reply 200 with a user object when username is richard_01', async () => {
  //arrange
  const username = 'richard_01'
  const request = new Request(`http://localhost/api/v1/users/${username}`)
  const params = { params: { username } }
  //act
  const response = await GET(request, params)
  //assert
  const status = response.status
  const rawData = await response.json()
  const data = S.parseSync(User.schema)(rawData)
  expect(status).toBe(200)
  expect(data.name).toBe(username)
})

參考以下程式碼,我們只要把昨天的 throw Error('Todo') 換掉就可以輕鬆通過

//src\app\api\v1\users\[username]\route.ts

interface Route {
  params: { username: string }
}

export const GET = async (request: Request, route: Route): Promise<Response> => {
  // throw Error('Todo') << remove
  return NextResponse.json({ // add fake response to pass the test
    _tag: 'Administrator',
    name: 'richard_01',
  })
}

第二筆測試

不過一但加入第二筆測資,結果就又出錯了,當 username 不存在時,應該要回 404 而不是 200

it('should reply 404 when username is richard_x', async () => {
  //arrange
  const username = 'richard_x'
  const request = new Request(`http://localhost/api/v1/users/${username}`)
  const params = { params: { username } }
  //act
  const response = await GET(request, params)
  //assert
  expect(response.status).toBe(404)
})

https://ithelp.ithome.com.tw/upload/images/20231009/20158615WRFZvPHr7e.png
https://ithelp.ithome.com.tw/upload/images/20231009/201586157NGVgIMt7y.png

因此我們得做一個判斷才行,這樣一來就能順利通過測試

export const GET = async (_: Request, route: Route) =>
  route.params.username === 'richard_01'
    ? NextResponse.json({
        _tag: 'Administrator',
        name: 'richard_01',
      })
    : new NextResponse('', { status: 404 })

第三筆測試

第三筆測試會比較困難一點,因為我們這次要在資料庫連不上的情況下回應 500 錯誤。

  describe('in the case of database connecting failure', () => {

    beforeAll(async () => {
      vi.stubEnv('DB_URI', 'mongodb://localhost:54321')
    })
    
    it('should reply 500', async () => {
      //arrange
      const username = 'richard_01'
      const request = new Request(`http://localhost/api/v1/users/${username}`)
      const params = { params: { username } }
      //act
      const response = await GET(request, params)
      //assert
      expect(response.status).toBe(500)
    })
  })

因此我們接下來要真的和資料庫互動才能通過測試。

通過所有測試並重構

以下是通過測試並且初步重構過的程式碼,請參考註解說明。

程式碼請參考 D22/consumer-driven-contract

//src\app\api\v1\users\[username]\route.ts

export const GET = async (_: Request, route: Route) =>
  pipe(
    // 讀取環境變數,因為環境變數屬於不穩定的外部輸入,因此要利用 Schema 做驗證
    // Env.of 的型別會是 (env:unknown) => Effect<never, EnvError, Env>
    Env.of(process.env), 
    // 讀取 env 成功以後嘗試取得資料庫連線
    // 如果已經有連線存在,會直接取用現有連線
    Effect.flatMap(MongooseEx.connect),
    // 等到確定資料庫連線完成,根據 username 找回 UserDocument
    // findOneUser 的型別是 
    // (name: string) => () => Effect<never, DBError, UserDocument | null>
    Effect.flatMap(findOneUser(route.params.username)),
    // 如果結果是 null,把原本正常路線的 null 轉成錯誤路線的 NotFoundError
    Effect.flatMap(validateFounded),
    // 把存在資料庫的 UserDocument 物件,轉換成 User 物件
    Effect.flatMap(UserDocument.toUser),
    Effect.match({
	  // 錯誤的話對各種錯誤結果做 pattern matching,請參考下一個程式碼區塊
      onFailure: matchErrors,
      // 成功的話回應 User
      onSuccess: NextResponse.json,
    }),
    Effect.runPromise
  )
// src\app\api\common\matchErrors.ts

const notFound = (error: ModelError) =>
  NextResponse.json(error, { status: 404 })
const internal = (error: ModelError) =>
  NextResponse.json(error, { status: 500 })

export const matchErrors = (error: ModelError) =>
  pipe(
    M.value(error),
    // 如果發生 DatabaseNotFoundError,
    M.tag('DatabaseNotFoundError', notFound),
    // 即使處理方式都相同,我也喜歡盡量把所有可能列出來,因為使用 orElse 容易造成疏忽
    M.tag('EnvError', internal),
    M.tag('DatabaseUnexpectedError', internal),
    M.tag('MongooseExConnectError', internal),
    M.tag('TransformError', internal),
    M.exhaustive
  )

https://ithelp.ithome.com.tw/upload/images/20231009/20158615uVXZHHEx03.png


上一篇
D23 - 實作異步流程 (九)
下一篇
D25 - 資料傳輸模型
系列文
從 Next.js 開始的 Functional Programming30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言