uploads.test.ts 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. import fs from 'fs';
  2. import type { ResponseLike } from '@opencode-ai/sdk/internal/to-file';
  3. import { toFile } from '@opencode-ai/sdk/core/uploads';
  4. import { File } from 'node:buffer';
  5. class MyClass {
  6. name: string = 'foo';
  7. }
  8. function mockResponse({ url, content }: { url: string; content?: Blob }): ResponseLike {
  9. return {
  10. url,
  11. blob: async () => content || new Blob([]),
  12. };
  13. }
  14. describe('toFile', () => {
  15. it('throws a helpful error for mismatched types', async () => {
  16. await expect(
  17. // @ts-expect-error intentionally mismatched type
  18. toFile({ foo: 'string' }),
  19. ).rejects.toThrowErrorMatchingInlineSnapshot(
  20. `"Unexpected data type: object; constructor: Object; props: ["foo"]"`,
  21. );
  22. await expect(
  23. // @ts-expect-error intentionally mismatched type
  24. toFile(new MyClass()),
  25. ).rejects.toThrowErrorMatchingInlineSnapshot(
  26. `"Unexpected data type: object; constructor: MyClass; props: ["name"]"`,
  27. );
  28. });
  29. it('disallows string at the type-level', async () => {
  30. // @ts-expect-error we intentionally do not type support for `string`
  31. // to help people avoid passing a file path
  32. const file = await toFile('contents');
  33. expect(file.text()).resolves.toEqual('contents');
  34. });
  35. it('extracts a file name from a Response', async () => {
  36. const response = mockResponse({ url: 'https://example.com/my/audio.mp3' });
  37. const file = await toFile(response);
  38. expect(file.name).toEqual('audio.mp3');
  39. });
  40. it('extracts a file name from a File', async () => {
  41. const input = new File(['foo'], 'input.jsonl');
  42. const file = await toFile(input);
  43. expect(file.name).toEqual('input.jsonl');
  44. });
  45. it('extracts a file name from a ReadStream', async () => {
  46. const input = fs.createReadStream('tests/uploads.test.ts');
  47. const file = await toFile(input);
  48. expect(file.name).toEqual('uploads.test.ts');
  49. });
  50. it('does not copy File objects', async () => {
  51. const input = new File(['foo'], 'input.jsonl', { type: 'jsonl' });
  52. const file = await toFile(input);
  53. expect(file).toBe(input);
  54. expect(file.name).toEqual('input.jsonl');
  55. expect(file.type).toBe('jsonl');
  56. });
  57. it('is assignable to File and Blob', async () => {
  58. const input = new File(['foo'], 'input.jsonl', { type: 'jsonl' });
  59. const result = await toFile(input);
  60. const file: File = result;
  61. const blob: Blob = result;
  62. void file, blob;
  63. });
  64. });
  65. describe('missing File error message', () => {
  66. let prevGlobalFile: unknown;
  67. let prevNodeFile: unknown;
  68. beforeEach(() => {
  69. // The file shim captures the global File object when it's first imported.
  70. // Reset modules before each test so we can test the error thrown when it's undefined.
  71. jest.resetModules();
  72. const buffer = require('node:buffer');
  73. // @ts-ignore
  74. prevGlobalFile = globalThis.File;
  75. prevNodeFile = buffer.File;
  76. // @ts-ignore
  77. globalThis.File = undefined;
  78. buffer.File = undefined;
  79. });
  80. afterEach(() => {
  81. // Clean up
  82. // @ts-ignore
  83. globalThis.File = prevGlobalFile;
  84. require('node:buffer').File = prevNodeFile;
  85. jest.resetModules();
  86. });
  87. test('is thrown', async () => {
  88. const uploads = await import('@opencode-ai/sdk/core/uploads');
  89. await expect(
  90. uploads.toFile(mockResponse({ url: 'https://example.com/my/audio.mp3' })),
  91. ).rejects.toMatchInlineSnapshot(
  92. `[Error: \`File\` is not defined as a global, which is required for file uploads.]`,
  93. );
  94. });
  95. });