Companion option uploadHeaders (#5981)

closes #5921

also improve tests that currently depend on eachother

todo
- [x] docs https://github.com/transloadit/uppy.io/pull/394
This commit is contained in:
Mikael Finstad 2025-11-03 18:34:07 +08:00 committed by GitHub
parent 5e166a101d
commit 0c8dd19dc2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 126 additions and 77 deletions

View file

@ -0,0 +1,5 @@
---
"@uppy/companion": patch
---
New Companion option `uploadHeaders` which can be used to include a static set of headers with every request sent to all upload destinations.

View file

@ -148,10 +148,18 @@ export default class Uploader {
* @property {number} [chunkSize]
* @property {string} [providerName]
*
* @param {UploaderOptions} options
* @param {UploaderOptions} optionsIn
*/
constructor(options) {
validateOptions(options)
constructor(optionsIn) {
validateOptions(optionsIn)
const options = {
...optionsIn,
headers: {
...optionsIn.headers,
...optionsIn.companionOptions.uploadHeaders,
},
}
this.providerName = options.providerName
this.options = options
@ -682,7 +690,7 @@ export default class Uploader {
try {
const httpMethod =
(this.options.httpMethod || '').toUpperCase() === 'PUT' ? 'put' : 'post'
const runRequest = await got[httpMethod]
const runRequest = got[httpMethod]
const response = await runRequest(url, reqOptions)

View file

@ -215,6 +215,9 @@ const getConfigFromEnv = () => {
process.env.COMPANION_TEST_DYNAMIC_OAUTH_CREDENTIALS === 'true',
testDynamicOauthCredentialsSecret:
process.env.COMPANION_TEST_DYNAMIC_OAUTH_CREDENTIALS_SECRET,
uploadHeaders: process.env.COMPANION_UPLOAD_HEADERS
? JSON.parse(process.env.COMPANION_UPLOAD_HEADERS)
: undefined,
}
}

View file

@ -1,6 +1,6 @@
import nock from 'nock'
import request from 'supertest'
import { afterAll, describe, expect, it, test, vi } from 'vitest'
import { afterAll, afterEach, describe, expect, it, test, vi } from 'vitest'
import packageJson from '../package.json' with { type: 'json' }
import * as tokenService from '../src/server/helpers/jwt.js'
import * as defaults from './fixtures/constants.js'
@ -37,8 +37,10 @@ const authData = {
const token = tokenService.generateEncryptedAuthToken(authData, secret)
const OAUTH_STATE = 'some-cool-nice-encrytpion'
afterAll(() => {
afterEach(() => {
nock.cleanAll()
})
afterAll(() => {
nock.restore()
})

View file

@ -1,6 +1,14 @@
import nock from 'nock'
import request from 'supertest'
import { afterAll, describe, expect, test, vi } from 'vitest'
import {
afterAll,
afterEach,
beforeEach,
describe,
expect,
test,
vi,
} from 'vitest'
import * as tokenService from '../src/server/helpers/jwt.js'
import { nockZoomRevoke, expects as zoomExpects } from './fixtures/zoom.js'
import { getServer } from './mockserver.js'
@ -21,32 +29,35 @@ const authData = {
}
const token = tokenService.generateEncryptedAuthToken(authData, secret)
afterAll(() => {
afterEach(() => {
nock.cleanAll()
})
afterAll(() => {
nock.restore()
})
describe('providers requests with remote oauth keys', () => {
// mocking request module used to fetch custom oauth credentials
nock('http://localhost:2111')
.post('/zoom-keys')
// @ts-ignore
.reply((uri, { provider, parameters }) => {
if (provider !== 'zoom' || parameters !== 'ZOOM-CREDENTIALS-PARAMS')
return [400]
beforeEach(() => {
// mocking request module used to fetch custom oauth credentials
nock('http://localhost:2111')
.post('/zoom-keys')
// @ts-ignore
.reply((uri, { provider, parameters }) => {
if (provider !== 'zoom' || parameters !== 'ZOOM-CREDENTIALS-PARAMS')
return [400]
return [
200,
{
credentials: {
key: remoteZoomKey,
secret: remoteZoomSecret,
verificationToken: remoteZoomVerificationToken,
return [
200,
{
credentials: {
key: remoteZoomKey,
secret: remoteZoomSecret,
verificationToken: remoteZoomVerificationToken,
},
},
},
]
})
.persist()
]
})
})
test('zoom logout with remote oauth keys happy path', async () => {
nockZoomRevoke({ key: remoteZoomKey, secret: remoteZoomSecret })
@ -69,6 +80,8 @@ describe('providers requests with remote oauth keys', () => {
})
test('zoom logout with wrong credentials params', async () => {
nockZoomRevoke({ key: remoteZoomKey, secret: remoteZoomSecret })
const params = { params: 'WRONG-ZOOM-CREDENTIALS-PARAMS' }
const encodedParams = Buffer.from(
JSON.stringify(params),

View file

@ -1,17 +1,21 @@
import nock from 'nock'
import request from 'supertest'
import { afterAll, describe, test, vi } from 'vitest'
import { afterAll, afterEach, beforeEach, describe, test, vi } from 'vitest'
import { getServer } from './mockserver.js'
vi.mock('express-prom-bundle')
afterAll(() => {
afterEach(() => {
nock.cleanAll()
})
afterAll(() => {
nock.restore()
})
describe('handle deauthorization callback', () => {
nock('https://api.zoom.us').post('/oauth/data/compliance').reply(200)
beforeEach(() => {
nock('https://api.zoom.us').post('/oauth/data/compliance').reply(200)
})
test('providers without support for callback endpoint', async () => {
return request(await getServer())

View file

@ -1,12 +1,14 @@
import nock from 'nock'
import { afterAll, describe, expect, test } from 'vitest'
import { afterAll, afterEach, describe, expect, test } from 'vitest'
import {
FORBIDDEN_IP_ADDRESS,
getProtectedGot,
} from '../src/server/helpers/request.js'
afterAll(() => {
afterEach(() => {
nock.cleanAll()
})
afterAll(() => {
nock.restore()
})

View file

@ -1,6 +1,14 @@
import nock from 'nock'
import request from 'supertest'
import { afterAll, beforeAll, describe, expect, test, vi } from 'vitest'
import {
afterAll,
afterEach,
beforeEach,
describe,
expect,
test,
vi,
} from 'vitest'
import * as tokenService from '../src/server/helpers/jwt.js'
import * as providerModule from '../src/server/provider/index.js'
import * as defaults from './fixtures/constants.js'
@ -76,16 +84,17 @@ function nockGetCurrentAccount(times = 1) {
})
}
beforeAll(() => {
beforeEach(() => {
const url = new URL(defaults.THUMBNAIL_URL)
nock(url.origin)
.get(url.pathname)
.reply(200, () => '')
.persist()
})
afterAll(() => {
afterEach(() => {
nock.cleanAll()
})
afterAll(() => {
nock.restore()
})
@ -543,13 +552,6 @@ describe('logout of provider', () => {
await runTest('box')
})
test('dropbox', async () => {
nock('https://api.dropboxapi.com')
.post('/2/auth/token/revoke')
.reply(200, {})
await runTest('dropbox')
})
test('drive', async () => {
nock('https://accounts.google.com')
.post('/o/oauth2/revoke?token=token+value')

View file

@ -1,9 +1,7 @@
import { once } from 'node:events'
import fs from 'node:fs'
import { createServer } from 'node:http'
import { Readable } from 'node:stream'
import nock from 'nock'
import { afterAll, describe, expect, test, vi } from 'vitest'
import { afterAll, afterEach, describe, expect, test, vi } from 'vitest'
import Emitter from '../src/server/emitter/index.js'
import Uploader, { ValidationError } from '../src/server/Uploader.js'
import standalone from '../src/standalone/index.js'
@ -12,8 +10,10 @@ import * as socketClient from './mocksocket.js'
vi.mock('tus-js-client')
vi.mock('express-prom-bundle')
afterAll(() => {
afterEach(() => {
nock.cleanAll()
})
afterAll(() => {
nock.restore()
})
@ -24,7 +24,7 @@ const { companionOptions } = standalone()
const mockReq = {}
describe('uploader with tus protocol', () => {
describe('uploader', () => {
test('uploader respects uploadUrls', async () => {
const opts = {
endpoint: 'http://localhost/files',
@ -207,12 +207,14 @@ describe('uploader with tus protocol', () => {
useFormData,
includeSize = true,
address = 'localhost',
// @ts-ignore
extraCompanionOpts,
} = {}) {
const fileContent = Buffer.from('Some file content')
const stream = Readable.from([fileContent])
const opts = {
companionOptions,
companionOptions: { ...companionOptions, ...extraCompanionOpts },
endpoint: `http://${address}`,
protocol: 'multipart',
size: includeSize ? fileContent.length : undefined,
@ -227,32 +229,33 @@ describe('uploader with tus protocol', () => {
}
test('upload functions with xhr protocol', async () => {
let alreadyCalled = false
// We are creating our own test server for this test
// instead of using nock because of a bug when passing a Node.js stream to got.
// Ref: https://github.com/nock/nock/issues/2595
const server = createServer((req, res) => {
if (alreadyCalled) throw new Error('already called')
alreadyCalled = true
if (req.url === '/' && req.method === 'POST') {
res.writeHead(200)
res.end('OK')
}
}).listen()
try {
await once(server, 'listening')
nock('http://localhost').post('/').reply(200, 'OK')
const ret = await runMultipartTest()
expect(ret).toMatchObject({
url: null,
extraData: { response: expect.anything(), bytesUploaded: 17 },
})
})
const ret = await runMultipartTest({
// @ts-ignore
address: `localhost:${server.address().port}`,
})
expect(ret).toMatchObject({
url: null,
extraData: { response: expect.anything(), bytesUploaded: 17 },
})
} finally {
server.close()
}
test('header companion option gets passed along to destination endpoint', async () => {
nock('http://localhost')
.post('/')
.matchHeader('header-a', '1')
.matchHeader('header-b', '2')
.reply(200, () => '')
const ret = await runMultipartTest({
// @ts-ignore
extraCompanionOpts: {
uploadHeaders: { 'header-a': '1', 'header-b': '2' },
},
})
expect(ret).toMatchObject({
url: null,
extraData: { response: expect.anything(), bytesUploaded: 17 },
})
expect(ret.extraData.response?.headers?.['header-a']).toBeUndefined() // headers sent to destination, not received back
})
const formDataNoMetaMatch =

View file

@ -1,6 +1,6 @@
import nock from 'nock'
import request from 'supertest'
import { afterAll, beforeAll, describe, expect, test, vi } from 'vitest'
import { afterAll, afterEach, describe, expect, test, vi } from 'vitest'
import { getServer } from './mockserver.js'
@ -18,14 +18,15 @@ vi.mock('../src/server/helpers/request.js', async () => {
const getMockServer = async () =>
getServer({ COMPANION_CLIENT_SOCKET_CONNECT_TIMEOUT: '0' })
beforeAll(() => {
const nockUrl = () =>
nock('http://url.myendpoint.com')
.get('/files')
.reply(200, () => '')
})
afterAll(() => {
afterEach(() => {
nock.cleanAll()
})
afterAll(() => {
nock.restore()
})
@ -52,6 +53,8 @@ describe('url meta', () => {
})
test.each(invalids)('return 400 for invalid url', async (urlCase) => {
nockUrl()
return request(await getMockServer())
.post('/url/meta')
.set('Content-Type', 'application/json')
@ -65,6 +68,8 @@ describe('url meta', () => {
describe('url get', () => {
test('url download gets instanitated', async () => {
nockUrl()
return request(await getMockServer())
.post('/url/get')
.set('Content-Type', 'application/json')
@ -80,6 +85,8 @@ describe('url get', () => {
test.each(invalids)(
'downloads are not instantiated for invalid urls',
async (urlCase) => {
nockUrl()
return request(await getMockServer())
.post('/url/get')
.set('Content-Type', 'application/json')