first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,657 @@
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import type { IExecuteFunctions } from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
createDatapoint,
|
||||
getAllDatapoints,
|
||||
updateDatapoint,
|
||||
deleteDatapoint,
|
||||
createCharge,
|
||||
uncleGoal,
|
||||
createAllDatapoints,
|
||||
getSingleDatapoint,
|
||||
getGoal,
|
||||
getAllGoals,
|
||||
getArchivedGoals,
|
||||
createGoal,
|
||||
updateGoal,
|
||||
refreshGoal,
|
||||
shortCircuitGoal,
|
||||
stepDownGoal,
|
||||
cancelStepDownGoal,
|
||||
getUser,
|
||||
type Datapoint,
|
||||
} from '../Beeminder.node.functions';
|
||||
import * as GenericFunctions from '../GenericFunctions';
|
||||
|
||||
// Mock the GenericFunctions
|
||||
jest.mock('../GenericFunctions');
|
||||
const mockedGenericFunctions = jest.mocked(GenericFunctions);
|
||||
|
||||
describe('Beeminder Node Functions', () => {
|
||||
let mockContext: IExecuteFunctions;
|
||||
|
||||
beforeEach(() => {
|
||||
mockContext = mock<IExecuteFunctions>();
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
describe('Datapoint Operations', () => {
|
||||
describe('createDatapoint', () => {
|
||||
it('should create a datapoint with required parameters', async () => {
|
||||
const mockResponse = { id: '123', value: 10, timestamp: 1234567890 };
|
||||
mockedGenericFunctions.beeminderApiRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const data = {
|
||||
goalName: 'testgoal',
|
||||
value: 10,
|
||||
};
|
||||
|
||||
const result = await createDatapoint.call(mockContext, data);
|
||||
|
||||
expect(mockedGenericFunctions.beeminderApiRequest).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/users/me/goals/testgoal/datapoints.json',
|
||||
data,
|
||||
{},
|
||||
);
|
||||
expect(result).toBe(mockResponse);
|
||||
});
|
||||
|
||||
it('should create a datapoint with all optional parameters', async () => {
|
||||
const mockResponse = { id: '123', value: 10, timestamp: 1234567890 };
|
||||
mockedGenericFunctions.beeminderApiRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const data = {
|
||||
goalName: 'testgoal',
|
||||
value: 10,
|
||||
timestamp: 1234567890,
|
||||
comment: 'Test comment',
|
||||
requestid: 'req123',
|
||||
};
|
||||
|
||||
const result = await createDatapoint.call(mockContext, data);
|
||||
|
||||
expect(mockedGenericFunctions.beeminderApiRequest).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/users/me/goals/testgoal/datapoints.json',
|
||||
data,
|
||||
{},
|
||||
);
|
||||
expect(result).toBe(mockResponse);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAllDatapoints', () => {
|
||||
it('should get all datapoints when count is not specified', async () => {
|
||||
const mockResponse = [{ id: '1' }, { id: '2' }];
|
||||
mockedGenericFunctions.beeminderApiRequestAllItems.mockResolvedValue(mockResponse);
|
||||
|
||||
const data = { goalName: 'testgoal' };
|
||||
|
||||
const result = await getAllDatapoints.call(mockContext, data);
|
||||
|
||||
expect(mockedGenericFunctions.beeminderApiRequestAllItems).toHaveBeenCalledWith(
|
||||
'GET',
|
||||
'/users/me/goals/testgoal/datapoints.json',
|
||||
{},
|
||||
data,
|
||||
);
|
||||
expect(result).toBe(mockResponse);
|
||||
});
|
||||
|
||||
it('should get limited datapoints when count is specified', async () => {
|
||||
const mockResponse = [{ id: '1' }];
|
||||
mockedGenericFunctions.beeminderApiRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const data = { goalName: 'testgoal', count: 1 };
|
||||
|
||||
const result = await getAllDatapoints.call(mockContext, data);
|
||||
|
||||
expect(mockedGenericFunctions.beeminderApiRequest).toHaveBeenCalledWith(
|
||||
'GET',
|
||||
'/users/me/goals/testgoal/datapoints.json',
|
||||
{},
|
||||
data,
|
||||
);
|
||||
expect(result).toBe(mockResponse);
|
||||
});
|
||||
|
||||
it('should handle optional parameters', async () => {
|
||||
const mockResponse = [{ id: '1' }];
|
||||
mockedGenericFunctions.beeminderApiRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const data = {
|
||||
goalName: 'testgoal',
|
||||
count: 5,
|
||||
sort: 'id',
|
||||
page: 2,
|
||||
per: 10,
|
||||
};
|
||||
|
||||
const result = await getAllDatapoints.call(mockContext, data);
|
||||
|
||||
expect(mockedGenericFunctions.beeminderApiRequest).toHaveBeenCalledWith(
|
||||
'GET',
|
||||
'/users/me/goals/testgoal/datapoints.json',
|
||||
{},
|
||||
data,
|
||||
);
|
||||
expect(result).toBe(mockResponse);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateDatapoint', () => {
|
||||
it('should update a datapoint with required parameters', async () => {
|
||||
const mockResponse = { id: '123', value: 15 };
|
||||
mockedGenericFunctions.beeminderApiRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const data = {
|
||||
goalName: 'testgoal',
|
||||
datapointId: '123',
|
||||
};
|
||||
|
||||
const result = await updateDatapoint.call(mockContext, data);
|
||||
|
||||
expect(mockedGenericFunctions.beeminderApiRequest).toHaveBeenCalledWith(
|
||||
'PUT',
|
||||
'/users/me/goals/testgoal/datapoints/123.json',
|
||||
data,
|
||||
{},
|
||||
);
|
||||
expect(result).toBe(mockResponse);
|
||||
});
|
||||
|
||||
it('should update a datapoint with all optional parameters', async () => {
|
||||
const mockResponse = { id: '123', value: 15 };
|
||||
mockedGenericFunctions.beeminderApiRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const data = {
|
||||
goalName: 'testgoal',
|
||||
datapointId: '123',
|
||||
value: 15,
|
||||
comment: 'Updated comment',
|
||||
timestamp: 1234567890,
|
||||
};
|
||||
|
||||
const result = await updateDatapoint.call(mockContext, data);
|
||||
|
||||
expect(mockedGenericFunctions.beeminderApiRequest).toHaveBeenCalledWith(
|
||||
'PUT',
|
||||
'/users/me/goals/testgoal/datapoints/123.json',
|
||||
data,
|
||||
{},
|
||||
);
|
||||
expect(result).toBe(mockResponse);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteDatapoint', () => {
|
||||
it('should delete a datapoint', async () => {
|
||||
const mockResponse = { success: true };
|
||||
mockedGenericFunctions.beeminderApiRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const data = {
|
||||
goalName: 'testgoal',
|
||||
datapointId: '123',
|
||||
};
|
||||
|
||||
const result = await deleteDatapoint.call(mockContext, data);
|
||||
|
||||
expect(mockedGenericFunctions.beeminderApiRequest).toHaveBeenCalledWith(
|
||||
'DELETE',
|
||||
'/users/me/goals/testgoal/datapoints/123.json',
|
||||
);
|
||||
expect(result).toBe(mockResponse);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createAllDatapoints', () => {
|
||||
it('should create multiple datapoints', async () => {
|
||||
const mockResponse = { created: 2 };
|
||||
mockedGenericFunctions.beeminderApiRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const datapoints: Datapoint[] = [
|
||||
{ timestamp: 1234567890, value: 10, comment: 'First' },
|
||||
{ timestamp: 1234567891, value: 20, comment: 'Second' },
|
||||
];
|
||||
|
||||
const data = {
|
||||
goalName: 'testgoal',
|
||||
datapoints,
|
||||
};
|
||||
|
||||
const result = await createAllDatapoints.call(mockContext, data);
|
||||
|
||||
expect(mockedGenericFunctions.beeminderApiRequest).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/users/me/goals/testgoal/datapoints/create_all.json',
|
||||
{ datapoints },
|
||||
{},
|
||||
);
|
||||
expect(result).toBe(mockResponse);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getSingleDatapoint', () => {
|
||||
it('should get a single datapoint', async () => {
|
||||
const mockResponse = { id: '123', value: 10 };
|
||||
mockedGenericFunctions.beeminderApiRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const data = {
|
||||
goalName: 'testgoal',
|
||||
datapointId: '123',
|
||||
};
|
||||
|
||||
const result = await getSingleDatapoint.call(mockContext, data);
|
||||
|
||||
expect(mockedGenericFunctions.beeminderApiRequest).toHaveBeenCalledWith(
|
||||
'GET',
|
||||
'/users/me/goals/testgoal/datapoints/123.json',
|
||||
);
|
||||
expect(result).toBe(mockResponse);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Goal Operations', () => {
|
||||
describe('getGoal', () => {
|
||||
it('should get a goal with basic parameters', async () => {
|
||||
const mockResponse = { slug: 'testgoal', title: 'Test Goal' };
|
||||
mockedGenericFunctions.beeminderApiRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const data = { goalName: 'testgoal' };
|
||||
|
||||
const result = await getGoal.call(mockContext, data);
|
||||
|
||||
expect(mockedGenericFunctions.beeminderApiRequest).toHaveBeenCalledWith(
|
||||
'GET',
|
||||
'/users/me/goals/testgoal.json',
|
||||
{},
|
||||
data,
|
||||
);
|
||||
expect(result).toBe(mockResponse);
|
||||
});
|
||||
|
||||
it('should get a goal with optional parameters', async () => {
|
||||
const mockResponse = { slug: 'testgoal', title: 'Test Goal' };
|
||||
mockedGenericFunctions.beeminderApiRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const data = {
|
||||
goalName: 'testgoal',
|
||||
datapoints: true,
|
||||
emaciated: false,
|
||||
};
|
||||
|
||||
const result = await getGoal.call(mockContext, data);
|
||||
|
||||
expect(mockedGenericFunctions.beeminderApiRequest).toHaveBeenCalledWith(
|
||||
'GET',
|
||||
'/users/me/goals/testgoal.json',
|
||||
{},
|
||||
data,
|
||||
);
|
||||
expect(result).toBe(mockResponse);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAllGoals', () => {
|
||||
it('should get all goals without parameters', async () => {
|
||||
const mockResponse = [{ slug: 'goal1' }, { slug: 'goal2' }];
|
||||
mockedGenericFunctions.beeminderApiRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await getAllGoals.call(mockContext);
|
||||
|
||||
expect(mockedGenericFunctions.beeminderApiRequest).toHaveBeenCalledWith(
|
||||
'GET',
|
||||
'/users/me/goals.json',
|
||||
{},
|
||||
{},
|
||||
);
|
||||
expect(result).toBe(mockResponse);
|
||||
});
|
||||
|
||||
it('should get all goals with emaciated parameter', async () => {
|
||||
const mockResponse = [{ slug: 'goal1' }, { slug: 'goal2' }];
|
||||
mockedGenericFunctions.beeminderApiRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const data = { emaciated: true };
|
||||
|
||||
const result = await getAllGoals.call(mockContext, data);
|
||||
|
||||
expect(mockedGenericFunctions.beeminderApiRequest).toHaveBeenCalledWith(
|
||||
'GET',
|
||||
'/users/me/goals.json',
|
||||
{},
|
||||
data,
|
||||
);
|
||||
expect(result).toBe(mockResponse);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getArchivedGoals', () => {
|
||||
it('should get archived goals without parameters', async () => {
|
||||
const mockResponse = [{ slug: 'archived1' }];
|
||||
mockedGenericFunctions.beeminderApiRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const result = await getArchivedGoals.call(mockContext);
|
||||
|
||||
expect(mockedGenericFunctions.beeminderApiRequest).toHaveBeenCalledWith(
|
||||
'GET',
|
||||
'/users/me/goals/archived.json',
|
||||
{},
|
||||
{},
|
||||
);
|
||||
expect(result).toBe(mockResponse);
|
||||
});
|
||||
|
||||
it('should get archived goals with emaciated parameter', async () => {
|
||||
const mockResponse = [{ slug: 'archived1' }];
|
||||
mockedGenericFunctions.beeminderApiRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const data = { emaciated: true };
|
||||
|
||||
const result = await getArchivedGoals.call(mockContext, data);
|
||||
|
||||
expect(mockedGenericFunctions.beeminderApiRequest).toHaveBeenCalledWith(
|
||||
'GET',
|
||||
'/users/me/goals/archived.json',
|
||||
{},
|
||||
data,
|
||||
);
|
||||
expect(result).toBe(mockResponse);
|
||||
});
|
||||
});
|
||||
|
||||
describe('createGoal', () => {
|
||||
it('should create a goal with required parameters', async () => {
|
||||
const mockResponse = { slug: 'newgoal', id: 'goal123' };
|
||||
mockedGenericFunctions.beeminderApiRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const data = {
|
||||
slug: 'newgoal',
|
||||
title: 'New Goal',
|
||||
goal_type: 'hustler',
|
||||
gunits: 'hours',
|
||||
};
|
||||
|
||||
const result = await createGoal.call(mockContext, data);
|
||||
|
||||
expect(mockedGenericFunctions.beeminderApiRequest).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/users/me/goals.json',
|
||||
data,
|
||||
{},
|
||||
);
|
||||
expect(result).toBe(mockResponse);
|
||||
});
|
||||
|
||||
it('should create a goal with all optional parameters', async () => {
|
||||
const mockResponse = { slug: 'newgoal', id: 'goal123' };
|
||||
mockedGenericFunctions.beeminderApiRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const data = {
|
||||
slug: 'newgoal',
|
||||
title: 'New Goal',
|
||||
goal_type: 'hustler',
|
||||
gunits: 'hours',
|
||||
goaldate: 1234567890,
|
||||
goalval: 100,
|
||||
rate: 1,
|
||||
initval: 0,
|
||||
secret: false,
|
||||
datapublic: true,
|
||||
datasource: 'manual',
|
||||
dryrun: false,
|
||||
tags: ['productivity', 'work'],
|
||||
};
|
||||
|
||||
const result = await createGoal.call(mockContext, data);
|
||||
|
||||
expect(mockedGenericFunctions.beeminderApiRequest).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/users/me/goals.json',
|
||||
data,
|
||||
{},
|
||||
);
|
||||
expect(result).toBe(mockResponse);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateGoal', () => {
|
||||
it('should update a goal with goalName', async () => {
|
||||
const mockResponse = { slug: 'testgoal', title: 'Updated Title' };
|
||||
mockedGenericFunctions.beeminderApiRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const data = {
|
||||
goalName: 'testgoal',
|
||||
title: 'Updated Title',
|
||||
};
|
||||
|
||||
const result = await updateGoal.call(mockContext, data);
|
||||
|
||||
expect(mockedGenericFunctions.beeminderApiRequest).toHaveBeenCalledWith(
|
||||
'PUT',
|
||||
'/users/me/goals/testgoal.json',
|
||||
data,
|
||||
{},
|
||||
);
|
||||
expect(result).toBe(mockResponse);
|
||||
});
|
||||
|
||||
it('should update a goal with all optional parameters', async () => {
|
||||
const mockResponse = { slug: 'testgoal' };
|
||||
mockedGenericFunctions.beeminderApiRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const data = {
|
||||
goalName: 'testgoal',
|
||||
title: 'Updated Title',
|
||||
yaxis: 'Hours worked',
|
||||
tmin: '08:00',
|
||||
tmax: '18:00',
|
||||
secret: true,
|
||||
datapublic: false,
|
||||
roadall: { rate: 2 },
|
||||
datasource: 'api',
|
||||
tags: ['work', 'productivity'],
|
||||
};
|
||||
|
||||
const result = await updateGoal.call(mockContext, data);
|
||||
|
||||
expect(mockedGenericFunctions.beeminderApiRequest).toHaveBeenCalledWith(
|
||||
'PUT',
|
||||
'/users/me/goals/testgoal.json',
|
||||
data,
|
||||
{},
|
||||
);
|
||||
expect(result).toBe(mockResponse);
|
||||
});
|
||||
});
|
||||
|
||||
describe('refreshGoal', () => {
|
||||
it('should refresh a goal', async () => {
|
||||
const mockResponse = { success: true };
|
||||
mockedGenericFunctions.beeminderApiRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const data = { goalName: 'testgoal' };
|
||||
|
||||
const result = await refreshGoal.call(mockContext, data);
|
||||
|
||||
expect(mockedGenericFunctions.beeminderApiRequest).toHaveBeenCalledWith(
|
||||
'GET',
|
||||
'/users/me/goals/testgoal/refresh_graph.json',
|
||||
);
|
||||
expect(result).toBe(mockResponse);
|
||||
});
|
||||
});
|
||||
|
||||
describe('shortCircuitGoal', () => {
|
||||
it('should short circuit a goal', async () => {
|
||||
const mockResponse = { success: true };
|
||||
mockedGenericFunctions.beeminderApiRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const data = { goalName: 'testgoal' };
|
||||
|
||||
const result = await shortCircuitGoal.call(mockContext, data);
|
||||
|
||||
expect(mockedGenericFunctions.beeminderApiRequest).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/users/me/goals/testgoal/shortcircuit.json',
|
||||
);
|
||||
expect(result).toBe(mockResponse);
|
||||
});
|
||||
});
|
||||
|
||||
describe('stepDownGoal', () => {
|
||||
it('should step down a goal', async () => {
|
||||
const mockResponse = { success: true };
|
||||
mockedGenericFunctions.beeminderApiRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const data = { goalName: 'testgoal' };
|
||||
|
||||
const result = await stepDownGoal.call(mockContext, data);
|
||||
|
||||
expect(mockedGenericFunctions.beeminderApiRequest).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/users/me/goals/testgoal/stepdown.json',
|
||||
);
|
||||
expect(result).toBe(mockResponse);
|
||||
});
|
||||
});
|
||||
|
||||
describe('cancelStepDownGoal', () => {
|
||||
it('should cancel step down for a goal', async () => {
|
||||
const mockResponse = { success: true };
|
||||
mockedGenericFunctions.beeminderApiRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const data = { goalName: 'testgoal' };
|
||||
|
||||
const result = await cancelStepDownGoal.call(mockContext, data);
|
||||
|
||||
expect(mockedGenericFunctions.beeminderApiRequest).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/users/me/goals/testgoal/cancel_stepdown.json',
|
||||
);
|
||||
expect(result).toBe(mockResponse);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Charge Operations', () => {
|
||||
describe('createCharge', () => {
|
||||
it('should create a charge with required amount', async () => {
|
||||
const mockResponse = { id: 'charge123', amount: 5 };
|
||||
mockedGenericFunctions.beeminderApiRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const data = { amount: 5 };
|
||||
|
||||
const result = await createCharge.call(mockContext, data);
|
||||
|
||||
expect(mockedGenericFunctions.beeminderApiRequest).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/charges.json',
|
||||
{
|
||||
user_id: 'me',
|
||||
amount: 5,
|
||||
},
|
||||
{},
|
||||
);
|
||||
expect(result).toBe(mockResponse);
|
||||
});
|
||||
|
||||
it('should create a charge with all optional parameters', async () => {
|
||||
const mockResponse = { id: 'charge123', amount: 10 };
|
||||
mockedGenericFunctions.beeminderApiRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const data = {
|
||||
amount: 10,
|
||||
note: 'Penalty charge',
|
||||
dryrun: true,
|
||||
};
|
||||
|
||||
const result = await createCharge.call(mockContext, data);
|
||||
|
||||
expect(mockedGenericFunctions.beeminderApiRequest).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/charges.json',
|
||||
{
|
||||
user_id: 'me',
|
||||
amount: 10,
|
||||
note: 'Penalty charge',
|
||||
dryrun: true,
|
||||
},
|
||||
{},
|
||||
);
|
||||
expect(result).toBe(mockResponse);
|
||||
});
|
||||
|
||||
it('should not include undefined optional parameters', async () => {
|
||||
const mockResponse = { id: 'charge123', amount: 5 };
|
||||
mockedGenericFunctions.beeminderApiRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const data = {
|
||||
amount: 5,
|
||||
note: undefined,
|
||||
dryrun: undefined,
|
||||
};
|
||||
|
||||
const result = await createCharge.call(mockContext, data);
|
||||
|
||||
expect(mockedGenericFunctions.beeminderApiRequest).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/charges.json',
|
||||
{
|
||||
user_id: 'me',
|
||||
amount: 5,
|
||||
},
|
||||
{},
|
||||
);
|
||||
expect(result).toBe(mockResponse);
|
||||
});
|
||||
});
|
||||
|
||||
describe('uncleGoal', () => {
|
||||
it('should uncle a goal', async () => {
|
||||
const mockResponse = { success: true };
|
||||
mockedGenericFunctions.beeminderApiRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const data = { goalName: 'testgoal' };
|
||||
|
||||
const result = await uncleGoal.call(mockContext, data);
|
||||
|
||||
expect(mockedGenericFunctions.beeminderApiRequest).toHaveBeenCalledWith(
|
||||
'POST',
|
||||
'/users/me/goals/testgoal/uncleme.json',
|
||||
);
|
||||
expect(result).toBe(mockResponse);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('User Operations', () => {
|
||||
describe('getUser', () => {
|
||||
it('should get user information', async () => {
|
||||
const mockResponse = { username: 'testuser', goals: [] };
|
||||
mockedGenericFunctions.beeminderApiRequest.mockResolvedValue(mockResponse);
|
||||
|
||||
const data = {
|
||||
associations: true,
|
||||
diff_since: 1234567890,
|
||||
skinny: false,
|
||||
emaciated: false,
|
||||
datapoints_count: 10,
|
||||
};
|
||||
|
||||
const result = await getUser.call(mockContext, data);
|
||||
|
||||
expect(mockedGenericFunctions.beeminderApiRequest).toHaveBeenCalledWith(
|
||||
'GET',
|
||||
'/users/me.json',
|
||||
{},
|
||||
data,
|
||||
);
|
||||
expect(result).toBe(mockResponse);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,124 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import type { WorkflowTestData } from 'n8n-workflow';
|
||||
import nock from 'nock';
|
||||
|
||||
const userInfo = {
|
||||
username: 'test',
|
||||
timezone: 'Europe/Zurich',
|
||||
goals: ['test3333', 'test333'],
|
||||
created_at: 1520089425,
|
||||
updated_at: 1753992556,
|
||||
urgency_load: 43,
|
||||
deadbeat: false,
|
||||
has_authorized_fitbit: false,
|
||||
default_leadtime: 0,
|
||||
default_alertstart: 34200,
|
||||
default_deadline: -43260,
|
||||
subscription: 'infinibee',
|
||||
subs_downto: 'infinibee',
|
||||
subs_freq: 24,
|
||||
subs_lifetime: null,
|
||||
remaining_subs_credit: 31,
|
||||
id: '35555555555',
|
||||
};
|
||||
|
||||
const chargeInfo = {
|
||||
amount: 10,
|
||||
id: {
|
||||
$oid: '688bcd7cf0168a11bee246ffff',
|
||||
},
|
||||
note: 'Created by test-test-oauth2',
|
||||
status: null,
|
||||
username: 'test',
|
||||
};
|
||||
|
||||
const goalInfo = {
|
||||
slug: 'test3333',
|
||||
title: 'test title',
|
||||
description: null,
|
||||
goalval: 1000,
|
||||
rate: 1,
|
||||
rah: null,
|
||||
callback_url: null,
|
||||
tags: [],
|
||||
recent_data: [
|
||||
{
|
||||
id: '688bc5fef0168a11bee24691',
|
||||
timestamp: 1754042339,
|
||||
daystamp: '20250801',
|
||||
value: 0,
|
||||
comment: 'initial datapoint of 0 on the 1st',
|
||||
updated_at: 1753990654,
|
||||
requestid: null,
|
||||
origin: 'nihilo',
|
||||
creator: '',
|
||||
is_dummy: false,
|
||||
is_initial: true,
|
||||
urtext: null,
|
||||
fulltext: '2025-Aug-01 entered at 21:37 on 2025-Jul-31 ex nihilo',
|
||||
canonical: '01 0 "initial datapoint of 0 on the 1st"',
|
||||
created_at: '2025-07-31T19:37:34.000Z',
|
||||
},
|
||||
],
|
||||
dueby: null,
|
||||
};
|
||||
|
||||
const newDatapoint = {
|
||||
id: '688bc54ef0168a11bee2468b',
|
||||
timestamp: 1753990478,
|
||||
daystamp: '20250801',
|
||||
value: 1,
|
||||
comment: '',
|
||||
updated_at: 1753990478,
|
||||
requestid: null,
|
||||
origin: 'test-test-oauth2',
|
||||
creator: 'testuser',
|
||||
is_dummy: false,
|
||||
is_initial: false,
|
||||
urtext: null,
|
||||
fulltext: '2025-Aug-01 entered at 21:34 on 2025-Jul-31 by test-ser via test-test-oauth2',
|
||||
canonical: '01 1',
|
||||
created_at: '2025-07-31T19:34:38.000Z',
|
||||
status: 'created',
|
||||
};
|
||||
|
||||
describe('Execute Beeminder Node', () => {
|
||||
const testHarness = new NodeTestHarness();
|
||||
|
||||
beforeEach(() => {
|
||||
const beeminderNock = nock('https://www.beeminder.com');
|
||||
beeminderNock.get('/api/v1/users/me.json').reply(200, userInfo);
|
||||
beeminderNock.post('/api/v1/charges.json').reply(200, chargeInfo);
|
||||
beeminderNock.get('/api/v1/users/me/goals.json').reply(200, [goalInfo]);
|
||||
beeminderNock.post('/api/v1/users/me/goals.json').reply(200, goalInfo);
|
||||
beeminderNock
|
||||
.post(`/api/v1/users/me/goals/${goalInfo.slug}/datapoints.json`)
|
||||
.reply(200, newDatapoint);
|
||||
beeminderNock
|
||||
.put(`/api/v1/users/me/goals/${goalInfo.slug}/datapoints/${newDatapoint.id}.json`)
|
||||
.reply(200, newDatapoint);
|
||||
beeminderNock
|
||||
.delete(`/api/v1/users/me/goals/${goalInfo.slug}/datapoints/${newDatapoint.id}.json`)
|
||||
.reply(200, newDatapoint);
|
||||
});
|
||||
|
||||
const testData: WorkflowTestData = {
|
||||
description: 'Execute operations',
|
||||
input: {
|
||||
workflowData: testHarness.readWorkflowJSON('workflow.json'),
|
||||
},
|
||||
output: {
|
||||
nodeData: {
|
||||
'Get user information': [[{ json: userInfo }]],
|
||||
'Create a charge': [[{ json: chargeInfo }]],
|
||||
'Get many goals': [[{ json: goalInfo }]],
|
||||
'Create a new goal': [[{ json: goalInfo }]],
|
||||
'Create datapoint for goal': [[{ json: newDatapoint }]],
|
||||
'Update a datapoint': [[{ json: newDatapoint }]],
|
||||
'Delete a datapoint': [[{ json: newDatapoint }]],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
testHarness.setupTest(testData, { credentials: { beeminderApi: {} } });
|
||||
});
|
||||
@@ -0,0 +1,238 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [-848, -400],
|
||||
"id": "852d5569-78ec-4d61-8b9c-8bdbe963fe6e",
|
||||
"name": "When clicking ‘Execute workflow’"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"authentication": "oAuth2",
|
||||
"resource": "goal",
|
||||
"operation": "create",
|
||||
"slug": "test333",
|
||||
"title": "test title",
|
||||
"gunits": "unit",
|
||||
"additionalFields": {
|
||||
"goalval": 1000,
|
||||
"rate": 1
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.beeminder",
|
||||
"typeVersion": 1,
|
||||
"position": [48, -400],
|
||||
"id": "0fbc833e-f4fb-430e-a130-9fd6655a35f1",
|
||||
"name": "Create a new goal",
|
||||
"credentials": {
|
||||
"beeminderOAuth2Api": {
|
||||
"id": "tXjFNZhKeJFjFOl7",
|
||||
"name": "Beeminder account 3"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"authentication": "oAuth2",
|
||||
"goalName": "={{ $json.slug }}",
|
||||
"additionalFields": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.beeminder",
|
||||
"typeVersion": 1,
|
||||
"position": [272, -400],
|
||||
"id": "b143d8f4-399c-47e9-9497-002497d0a2b3",
|
||||
"name": "Create datapoint for goal",
|
||||
"credentials": {
|
||||
"beeminderOAuth2Api": {
|
||||
"id": "tXjFNZhKeJFjFOl7",
|
||||
"name": "Beeminder account 3"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"authentication": "oAuth2",
|
||||
"operation": "delete",
|
||||
"goalName": "={{ $('Create a new goal').item.json.slug }}",
|
||||
"datapointId": "={{ $json.id }}"
|
||||
},
|
||||
"type": "n8n-nodes-base.beeminder",
|
||||
"typeVersion": 1,
|
||||
"position": [720, -400],
|
||||
"id": "69ef60fd-c66b-4cba-aaf6-96e4fd453ac2",
|
||||
"name": "Delete a datapoint",
|
||||
"credentials": {
|
||||
"beeminderOAuth2Api": {
|
||||
"id": "tXjFNZhKeJFjFOl7",
|
||||
"name": "Beeminder account 3"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"authentication": "oAuth2",
|
||||
"resource": "goal",
|
||||
"operation": "getAll",
|
||||
"additionalFields": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.beeminder",
|
||||
"typeVersion": 1,
|
||||
"position": [-176, -400],
|
||||
"id": "1bdce766-3434-4e43-81bb-80a313ef4f2d",
|
||||
"name": "Get many goals",
|
||||
"credentials": {
|
||||
"beeminderOAuth2Api": {
|
||||
"id": "tXjFNZhKeJFjFOl7",
|
||||
"name": "Beeminder account 3"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"authentication": "oAuth2",
|
||||
"resource": "user",
|
||||
"additionalFields": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.beeminder",
|
||||
"typeVersion": 1,
|
||||
"position": [-624, -400],
|
||||
"id": "25bb239d-d990-4b82-9416-a282cc00f467",
|
||||
"name": "Get user information",
|
||||
"credentials": {
|
||||
"beeminderOAuth2Api": {
|
||||
"id": "tXjFNZhKeJFjFOl7",
|
||||
"name": "Beeminder account 3"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"authentication": "oAuth2",
|
||||
"resource": "charge",
|
||||
"amount": 10,
|
||||
"additionalFields": {
|
||||
"dryrun": true
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.beeminder",
|
||||
"typeVersion": 1,
|
||||
"position": [-400, -400],
|
||||
"id": "2fa9ea2e-e4e7-4291-bb6d-75a6bc15bc4e",
|
||||
"name": "Create a charge",
|
||||
"credentials": {
|
||||
"beeminderOAuth2Api": {
|
||||
"id": "tXjFNZhKeJFjFOl7",
|
||||
"name": "Beeminder account 3"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"authentication": "oAuth2",
|
||||
"operation": "update",
|
||||
"goalName": "={{ $('Create a new goal').item.json.slug }}",
|
||||
"datapointId": "={{ $json.id }}",
|
||||
"updateFields": {}
|
||||
},
|
||||
"type": "n8n-nodes-base.beeminder",
|
||||
"typeVersion": 1,
|
||||
"position": [496, -400],
|
||||
"id": "55d4490f-97a2-405b-8745-421291d0c82e",
|
||||
"name": "Update a datapoint",
|
||||
"credentials": {
|
||||
"beeminderOAuth2Api": {
|
||||
"id": "tXjFNZhKeJFjFOl7",
|
||||
"name": "Beeminder account 3"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"When clicking ‘Execute workflow’": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get user information",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Create a new goal": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Create datapoint for goal",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Create datapoint for goal": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Update a datapoint",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get many goals": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Create a new goal",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Get user information": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Create a charge",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Create a charge": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Get many goals",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Update a datapoint": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Delete a datapoint",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"pinData": {
|
||||
"When clicking ‘Execute workflow’": [{}]
|
||||
},
|
||||
"meta": {
|
||||
"templateCredsSetupCompleted": true,
|
||||
"instanceId": "f0e9801eba0feea6a9ddf9beeabe34b0843eae42a1dbc62eaadd68e8f576be64"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user