最新消息:Welcome to the puzzle paradise for programmers! Here, a well-designed puzzle awaits you. From code logic puzzles to algorithmic challenges, each level is closely centered on the programmer's expertise and skills. Whether you're a novice programmer or an experienced tech guru, you'll find your own challenges on this site. In the process of solving puzzles, you can not only exercise your thinking skills, but also deepen your understanding and application of programming knowledge. Come to start this puzzle journey full of wisdom and challenges, with many programmers to compete with each other and show your programming wisdom! Translated with DeepL.com (free version)

node.js - MSW Mocking Readable Streams - Stack Overflow

matteradmin5PV0评论

I am trying to write a unit test by using the Mock Service Worker (msw) to mock an exceljs Passthrough Readable Stream, but I am getting this error:

 FAIL  __tests__/excel.test.js > getReadableStream > should return a readable stream for an HTTP URL
Error: Can't find end of central directory : is this a zip file ? If it is, see .html
 ❯ ZipEntries.readEndOfCentral node_modules/jszip/lib/zipEntries.js:167:23
 ❯ ZipEntries.load node_modules/jszip/lib/zipEntries.js:255:14
 ❯ node_modules/jszip/lib/load.js:48:24
 ❯ XLSX.load node_modules/exceljs/lib/xlsx/xlsx.js:279:17
 ❯ Module.readExcel helpers/excel.js:51:5
     49| 
     50|     const workbook = new ExcelJS.Workbook()
     51|     await workbook.xlsx.read(stream)
       |     ^
     52| 
     53|     return workbook
 ❯ __tests__/excel.test.js:95:19

excel.test.js

import { PassThrough } from 'node:stream'
import ExcelJS from 'exceljs'
import { describe, it, expect, beforeAll, afterAll, afterEach } from 'vitest'
import { setupServer } from 'msw/node'
import { http, HttpResponse } from 'msw'
import { readExcel, getReadableStream } from '../excel.js'
import data from './__fixtures__/data.json'

const sheetName = 'Sheet1'

// Create a mock server
const server = setupServer()

// Start the server before all tests
beforeAll(() => server.listen())

// Close the server after all tests
afterAll(() => server.close())

// Reset handlers after each test
afterEach(() => server.resetHandlers())

export async function generateReadableStreamFromJson (jsonData) {
  const workbook = new ExcelJS.Workbook()
  const worksheet = workbook.addWorksheet(sheetName)

  if (jsonData.length > 0) {
    // Add header row
    const header = Object.keys(jsonData[0])
    worksheet.addRow(header)

    // Add data rows
    jsonData.forEach((data) => {
      const rowValues = Object.values(data).map(value => value === null ? '' : value)
      worksheet.addRow(rowValues)
    })
  } else {
    // Add header row with empty data
    worksheet.addRow([])
  }

  // Create a PassThrough stream
  const stream = new PassThrough()

  try {
    // Write the workbook to the stream
    await workbook.xlsx.write(stream)
    stream.end()
  } catch (error) {
    stream.emit('error', error)
  }

  return stream
}

function validateExcelDataAgainstJson (excelData, jsonData) {
  const [headerRow, ...dataRows] = excelData[0].data

  // Transform Excel data into key-value objects
  const excelObjects = dataRows.map((row) => {
    const obj = {}
    headerRow.slice(1).forEach((header, colIndex) => {
      obj[header] = row[colIndex + 1]
    })
    return obj
  })

  // Compare the transformed Excel data against the JSON data
  expect(excelObjects).toHaveLength(jsonData.length)
  jsonData.forEach((jsonObj, index) => {
    const excelObj = excelObjects[index]
    Object.keys(jsonObj).forEach((key) => {
      expect(excelObj[key]).toEqual(jsonObj[key] ?? '')
    })
  })
}

describe('getReadableStream', () => {
  it('should return a readable stream for an HTTP URL', async () => {
    const url = '.xlsx'
    const readableStream = await generateReadableStreamFromJson(data)

    server.use(
      http.get(url, () => {
        return new HttpResponse(readableStream, {
          headers: {
            'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
          },
        })
      })
    )

    const stream = await getReadableStream(url)

    const excel = await readExcel(stream)
    validateExcelDataAgainstJson(excel, data)
  })
})

excel.js

import { URL } from 'node:url'
import { createReadStream } from 'node:fs'
import path from 'node:path'
import { Readable } from 'node:stream'
import ExcelJS from 'exceljs'

export async function getReadableStream (input) {
  if (typeof input !== 'string' || input.trim() === '') {
    throw new Error('Invalid input: Must be a non-empty string or a readable stream')
  }

  try {
    const parsedUrl = new URL(input)

    switch (parsedUrl.protocol) {
      case 'http:':
      case 'https:': {
        const response = await fetch(input)
        if (!response.ok) {
          throw new Error(`Network request failed for ${input} with status code ${response.status}`)
        }
        if (!response.body || typeof response.body.pipe !== 'function') {
          throw new Error(`Response body is not a readable stream for ${input}`)
        }
        return response.body
      }
      case 'file:':
        return createReadStream(parsedUrl.pathname)
      default:
        throw new Error(`Unsupported protocol for URL: ${input}. Must use HTTP, HTTPS, or file protocol`)
    }
  } catch (e) {
    // If the URL constructor throws an error or the protocol is unsupported, assume it's a local file path
    console.warn(`Failed to parse URL: ${e.message}. Assuming local file path: ${input}`)
    return createReadStream(path.resolve(input))
  }
}

export async function readExcel (input) {
  try {
    let stream
    if (typeof input === 'string') {
      stream = await getReadableStream(input)
    } else if (input instanceof Readable) {
      stream = input
    } else {
      throw new Error('Invalid input: Must be a URL, file path, or readable stream')
    }

    const workbook = new ExcelJS.Workbook()
    await workbook.xlsx.read(stream)

    return workbook
  } catch (e) {
    console.error(`Failed to read Excel file from ${input}`, e)
    throw e
  }
}

package.json

{
  "name": "excel-stream-test",
  "description": "exceljs test",
  "version": "1.0.0",
  "license": "UNLICENSED",
  "main": "index.js",
  "type": "module",
  "scripts": {
    "test": "vitest run --passWithNoTests"
  },
  "engines": {
    "node": ">=22.0.0"
  },
  "dependencies": {
    "exceljs": "4.4.0"
  },
  "devDependencies": {
    "@vitest/coverage-v8": "2.1.5",
    "msw": "2.6.5",
    "vite": "5.4.11",
    "vitest": "2.1.5"
  }
}

data.json

[
  {
    "field_1": "1"
  },
  {
    "field_1": "2"
  }
]
Post a comment

comment list (0)

  1. No comments so far