diff --git a/tests/InfoQuery.test.js b/tests/InfoQuery.test.js new file mode 100644 index 0000000..2e0c54c --- /dev/null +++ b/tests/InfoQuery.test.js @@ -0,0 +1,48 @@ +const InfoQuery = require("../modules/info/InfoQuery"); + +describe('Connect the InfoQuery', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + it('Checks the error when applicable', async () => { + const [env, instance] = instanceBuilder(); + env.metadataLimiter.schedule.mockRejectedValue({ stderr: "test-error"}); + const result = instance.connect(); + await result; + expect(env.errorHandler.checkError).toBeCalledWith("test-error", "test__id"); + }); + it('Returns null on error', async () => { + const [env, instance] = instanceBuilder(); + env.metadataLimiter.schedule.mockRejectedValue({ stderr: "test-error"}); + const result = instance.connect(); + await expect(result).resolves.toBe(null); + }); + it('Schedules the query', async () => { + const [env, instance] = instanceBuilder(); + const jsonString = "{\"test\": \"data\"}"; + env.metadataLimiter.schedule.mockResolvedValue(jsonString); + const result = instance.connect(); + await result; + expect(env.metadataLimiter.schedule).toBeCalledTimes(1); + }); + it('Returns the parsed query data', async () => { + const [env, instance] = instanceBuilder(); + const jsonString = "{\"test\": \"data\"}"; + env.metadataLimiter.schedule.mockResolvedValue(jsonString); + const result = instance.connect(); + await expect(result).resolves.toMatchObject(JSON.parse(jsonString)) + }); +}); + +function instanceBuilder() { + const env = { + metadataLimiter: { + schedule: jest.fn() + }, + errorHandler: { + checkError: jest.fn() + } + }; + return [env, new InfoQuery("http://url.link", "test__id", env)]; +} + diff --git a/tests/Query.test.js b/tests/Query.test.js new file mode 100644 index 0000000..69d8d93 --- /dev/null +++ b/tests/Query.test.js @@ -0,0 +1,153 @@ +const UserAgent = require('user-agents'); +const Query = require("../modules/types/Query"); +const execa = require('execa'); +const { PassThrough } = require('stream'); + +jest.mock('user-agents'); +jest.mock('execa'); + +beforeEach(() => { + jest.clearAllMocks(); + jest.doMock('execa', () => { + const originalModule = jest.requireActual('execa') + return { + __esModule: true, + ...originalModule, + execa: jest.fn() + } + }); +}); + +describe('ytdl Query', () => { + beforeEach(() => { + execa.mockResolvedValue({stdout: "fake-data"}); + }); + it('adds a random user agent when this setting is enabled', () => { + UserAgent.prototype.toString = jest.fn().mockReturnValue("agent"); + const errorHandlerMock = jest.fn(); + const instance = instanceBuilder(true, null, errorHandlerMock); + return instance.start("https://url.link", [], null).then(() => { + expect(UserAgent.prototype.toString).toBeCalledTimes(1); + expect(execa.mock.calls[0][1]).toContain("--user-agent"); + expect(execa.mock.calls[0][1]).toContain("agent"); + }); + }); + it('adds the cookies argument when specified in settings', () => { + const errorHandlerMock = jest.fn(); + const instance = instanceBuilder(false, "a/path/to/cookies.txt", errorHandlerMock); + return instance.start("https://url.link", [], null).then(() => { + expect(execa.mock.calls[0][1]).toContain("--cookies"); + expect(execa.mock.calls[0][1]).toContain("a/path/to/cookies.txt"); + }); + }); + it('adds the url as final argument', () => { + const errorHandlerMock = jest.fn(); + const instance = instanceBuilder(false, null, errorHandlerMock); + return instance.start("https://url.link", [], null).then(() => { + expect(execa.mock.calls[0][1]).toContain("https://url.link"); + }); + }) + it('adds the no-cache-dir as argument', () => { + const errorHandlerMock = jest.fn(); + const instance = instanceBuilder(false, null, errorHandlerMock); + return instance.start("https://url.link", [], null).then(() => { + expect(execa.mock.calls[0][1]).toContain("--no-cache-dir"); + }); + }); +}) + +describe('Query with live callback', () => { + it('Stops with return value killed when stop() is called', async () => { + const [stdout, stderr, mock] = execaMockBuilder(true); + execa.mockReturnValue(mock) + const errorHandlerMock = jest.fn(); + const callbackMock = jest.fn(); + const instance = instanceBuilder(false, null, errorHandlerMock); + const result = instance.start("https://url.link", [], callbackMock); + setTimeout(() => { + instance.stop(); + }, 100); + await expect(result).resolves.toEqual("killed"); + expect(callbackMock).toBeCalledWith("killed"); + }); + it('Checks the error when stderr gets written to', async () => { + const [stdout, stderr, mock] = execaMockBuilder(false); + execa.mockReturnValue(mock) + console.error = jest.fn(); + const errorHandlerMock = jest.fn(); + const callbackMock = jest.fn(); + const instance = instanceBuilder(false, null, errorHandlerMock); + const result = instance.start("https://url.link", [], callbackMock); + setTimeout(() => { + stderr.emit("data", "test-error"); + }, 100); + setTimeout(() => { + stdout.emit("close"); + }, 100); + await result; + expect(errorHandlerMock).toBeCalledWith("test-error", "test__id"); + }); + it('Resolves "done" when query was successful', async () => { + const [stdout, stderr, mock] = execaMockBuilder(false); + execa.mockReturnValue(mock) + const callbackMock = jest.fn(); + const instance = instanceBuilder(false, null, jest.fn()); + const result = instance.start("https://url.link", [], callbackMock); + setTimeout(() => { + stdout.emit("close"); + }, 100); + await expect(result).resolves.toEqual("done"); + expect(callbackMock).toBeCalledWith("done"); + }); + it('Sends live stdout to the callback', async () => { + const [stdout, stderr, mock] = execaMockBuilder(false); + execa.mockReturnValue(mock); + const callbackMock = jest.fn(); + const instance = instanceBuilder(false, null, jest.fn()); + const result = instance.start("https://url.link", [], callbackMock); + setTimeout(() => { + stdout.emit("data", "test-data"); + stdout.emit("data", "more-test-data"); + stdout.emit("close"); + }, 100); + await result; + expect(callbackMock).toBeCalledWith("test-data"); + expect(callbackMock).toBeCalledWith("more-test-data"); + }); +}); + +describe('Query without callback', () => { + it('Returns the data from the execa call', async () => { + execa.mockResolvedValue({stdout: "fake-data"}); + const errorHandlerMock = jest.fn(); + const instance = instanceBuilder(true, null, errorHandlerMock); + const result = instance.start("https://url.link", [], null) + await expect(result).resolves.toEqual("fake-data"); + }); + it('Returns a stringified empty object on error', async () => { + execa.mockResolvedValue(null); + const errorHandlerMock = jest.fn(); + const instance = instanceBuilder(true, null, errorHandlerMock); + const result = instance.start("https://url.link", [], null) + await expect(result).resolves.toEqual("{}"); + }); + it('Checks the error on error', () => { + execa.mockResolvedValue(null); + const errorHandlerMock = jest.fn(); + const instance = instanceBuilder(true, null, errorHandlerMock); + return instance.start("https://url.link", [], null).then(() => { + expect(errorHandlerMock).toBeCalledTimes(1); + }); + }); +}) + +function execaMockBuilder(killed) { + const stdout = new PassThrough(); + const stderr = new PassThrough(); + const mock = {stdout: stdout, stderr: stderr, cancel: jest.fn(() => { stdout.emit("close") }), killed: killed} + return [stdout, stderr, mock]; +} + +function instanceBuilder(spoofUserAgent, cookiePath, errorHandlerMock) { + return new Query({errorHandler: {checkError: errorHandlerMock}, paths: {ytdl: "a/path/to/ytdl"}, settings: {cookiePath: cookiePath, spoofUserAgent: spoofUserAgent}}, "test__id"); +}