Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/usdk-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,5 @@ jobs:

- name: Install dependencies
run: cd packages/usdk && pnpm install --no-optional --no-frozen-lockfile
- name: Run USDK command
run: cd packages/usdk && ./usdk.js --version
- name: Run USDK tests
run: cd packages/usdk && pnpm run test
5 changes: 5 additions & 0 deletions packages/usdk/jest.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
testEnvironment: 'node',
moduleFileExtensions: ['js', 'mjs'],
testMatch: ['**/tests/**/*.test.js'],
};
4 changes: 2 additions & 2 deletions packages/usdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"type": "module",
"main": "module.mjs",
"scripts": {
"preinstall": "[[ $npm_config_global == 'true' ]] || exit 0; cd packages/upstreet-agent && pnpm install --ignore-workspace --no-frozen-lockfile"
"preinstall": "[[ $npm_config_global == 'true' ]] || exit 0; cd packages/upstreet-agent && pnpm install --ignore-workspace --no-frozen-lockfile",
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
},
"keywords": [
"ai",
Expand Down Expand Up @@ -85,7 +86,6 @@
"gitignore-parser": "0.0.2",
"hono": "^4.6.9",
"javascript-time-ago": "^2.5.11",
"jest": "^29.7.0",
"jimp": "^1.6.0",
"json5": "^2.2.3",
"jszip": "^3.10.1",
Expand Down
28 changes: 28 additions & 0 deletions packages/usdk/tests/commands/login.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { login } from '../../lib/login.mjs';
import { jest } from '@jest/globals';

describe('login', () => {

it('should successfully login when valid code parameter is provided', async () => {
const mockLoginData = {
id: 'test-id',
jwt: 'test-jwt'
};
const encodedData = Buffer.from(JSON.stringify(mockLoginData)).toString('base64');

const result = await login({ code: encodedData });

expect(result).toEqual({
id: 'test-id',
jwt: 'test-jwt'
});
});

it('should reject when invalid base64 code is provided', async () => {
const invalidCode = 'invalid-base64-!@#$';

await expect(async () => {
await login({ code: invalidCode });
}).rejects.toThrow(SyntaxError);
});
});
57 changes: 57 additions & 0 deletions packages/usdk/tests/commands/logout.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { jest } from '@jest/globals';

// We need to mock before importing the module that uses it
const mockGetLoginJwt = jest.fn();
jest.unstable_mockModule('../../util/login-util.mjs', () => ({
getLoginJwt: mockGetLoginJwt
}));

// Import after mocking
const { logout } = await import('../../lib/login.mjs');

describe('logout', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('should return true when jwt exists', async () => {
mockGetLoginJwt.mockResolvedValue('mock-jwt-token');

const result = await logout({});

expect(result).toBe(true);
expect(mockGetLoginJwt).toHaveBeenCalledTimes(1);
});

it('should return false when jwt is empty', async () => {
mockGetLoginJwt.mockResolvedValue('');

const result = await logout({});

expect(result).toBe(false);
expect(mockGetLoginJwt).toHaveBeenCalledTimes(1);
});

it('should return false when jwt is null', async () => {
mockGetLoginJwt.mockResolvedValue(null);

const result = await logout({});

expect(result).toBe(false);
expect(mockGetLoginJwt).toHaveBeenCalledTimes(1);
});

it('should return false when jwt is undefined', async () => {
const result = await logout({});

expect(result).toBe(false);
expect(mockGetLoginJwt).toHaveBeenCalledTimes(1);
});

it('should handle rejected jwt promise', async () => {
mockGetLoginJwt.mockRejectedValue(new Error('Failed to get JWT'));

await expect(logout({})).rejects.toThrow('Failed to get JWT');
expect(mockGetLoginJwt).toHaveBeenCalledTimes(1);
});
});
143 changes: 143 additions & 0 deletions packages/usdk/tests/commands/pull.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import { jest } from '@jest/globals';

// Mock all dependencies before importing modules
const mockGetUserIdForJwt = jest.fn();
const mockCleanDir = jest.fn();
const mockExtractZip = jest.fn();
const mockNpmInstall = jest.fn();
const mockFetch = jest.fn();

jest.unstable_mockModule('../../packages/upstreet-agent/packages/react-agents/util/jwt-utils.mjs', () => ({
getUserIdForJwt: mockGetUserIdForJwt
}));

jest.unstable_mockModule('../../lib/directory-util.mjs', () => ({
cleanDir: mockCleanDir
}));

jest.unstable_mockModule('../../lib/zip-util.mjs', () => ({
extractZip: mockExtractZip
}));

jest.unstable_mockModule('../../lib/npm-util.mjs', () => ({
npmInstall: mockNpmInstall
}));

// Import modules after mocking
const { pull } = await import('../../lib/pull.mjs');
const { aiProxyHost } = await import('../../packages/upstreet-agent/packages/react-agents/util/endpoints.mjs');

describe('pull command', () => {
// Setup and teardown
beforeEach(() => {
// Clear all mocks before each test
jest.clearAllMocks();
global.fetch = mockFetch;
global.console.log = jest.fn();
global.console.warn = jest.fn();
});

test('should throw error if not logged in', async () => {
const args = { _: ['agentId'] };
const opts = { jwt: null };

await expect(pull(args, opts)).rejects.toThrow('You must be logged in to pull.');
});

test('should handle successful pull with existing directory', async () => {
// Mock setup
mockGetUserIdForJwt.mockResolvedValue('testUserId');
mockFetch.mockResolvedValue({
ok: true,
arrayBuffer: () => Promise.resolve(new ArrayBuffer(8)),
});

const args = {
_: ['agentId', '/test/dir'],
force: true,
forceNoConfirm: true,
};
const opts = { jwt: 'testJwt' };

await pull(args, opts);

// Verify calls
expect(mockCleanDir).toHaveBeenCalledWith('/test/dir', {
force: true,
forceNoConfirm: true,
});
expect(mockExtractZip).toHaveBeenCalled();
expect(mockNpmInstall).toHaveBeenCalledWith('/test/dir');
expect(mockFetch).toHaveBeenCalledWith(
`https://${aiProxyHost}/agents/agentId/source`,
{
headers: {
Authorization: 'Bearer testJwt',
},
}
);
});

test('should skip npm install when noInstall flag is true', async () => {
// Mock setup
mockGetUserIdForJwt.mockResolvedValue('testUserId');
mockFetch.mockResolvedValue({
ok: true,
arrayBuffer: () => Promise.resolve(new ArrayBuffer(8)),
});

const args = {
_: ['agentId', '/test/dir'],
noInstall: true,
};
const opts = { jwt: 'testJwt' };

await pull(args, opts);

expect(mockNpmInstall).not.toHaveBeenCalled();
});

test('should handle failed fetch request', async () => {
// Mock setup
mockGetUserIdForJwt.mockResolvedValue('testUserId');
mockFetch.mockResolvedValue({
ok: false,
text: () => Promise.resolve('Error message'),
});
const mockExit = jest.spyOn(process, 'exit').mockImplementation(() => {});

const args = { _: ['agentId', '/test/dir'] };
const opts = { jwt: 'testJwt' };

await pull(args, opts);

expect(console.warn).toHaveBeenCalledWith('pull request error', 'Error message');
expect(mockExit).toHaveBeenCalledWith(1);

mockExit.mockRestore();
});

test('should handle events dispatch', async () => {
// Mock setup
mockGetUserIdForJwt.mockResolvedValue('testUserId');
mockFetch.mockResolvedValue({
ok: true,
arrayBuffer: () => Promise.resolve(new ArrayBuffer(8)),
});

const mockDispatchEvent = jest.fn();
const args = {
_: ['agentId', '/test/dir'],
events: {
dispatchEvent: mockDispatchEvent,
},
};
const opts = { jwt: 'testJwt' };

await pull(args, opts);

expect(mockDispatchEvent).toHaveBeenCalledWith(
expect.any(MessageEvent)
);
});
});
114 changes: 114 additions & 0 deletions packages/usdk/tests/commands/status.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { jest } from '@jest/globals';

// Mock dependencies before importing the module that uses them
const mockGetUserIdForJwt = jest.fn();
const mockSupabaseStorage = jest.fn();

jest.unstable_mockModule('../../packages/upstreet-agent/packages/react-agents/util/jwt-utils.mjs', () => ({
getUserIdForJwt: mockGetUserIdForJwt
}));

jest.unstable_mockModule('../../packages/upstreet-agent/packages/react-agents/storage/supabase-storage.mjs', () => ({
SupabaseStorage: mockSupabaseStorage
}));

// Import after mocking
const { status } = await import('../../lib/status.mjs');

describe('status', () => {
beforeEach(() => {
jest.clearAllMocks();
});

test('throws error when no JWT provided', async () => {
await expect(status({}, {})).rejects.toThrow('not logged in');
});

test('returns user and wearing data when user has active asset', async () => {
const mockJwt = 'mock-jwt';
const mockUserId = 'user-123';
const mockUserData = {
id: mockUserId,
active_asset: 'asset-123',
};
const mockAssetData = {
id: 'asset-123',
type: 'npc',
name: 'Test Avatar',
};

mockGetUserIdForJwt.mockResolvedValue(mockUserId);

const mockSupabase = {
from: jest.fn().mockReturnThis(),
select: jest.fn().mockReturnThis(),
eq: jest.fn().mockReturnThis(),
maybeSingle: jest.fn(),
};

mockSupabaseStorage.mockImplementation(() => mockSupabase);

mockSupabase.maybeSingle
.mockResolvedValueOnce({ error: null, data: mockUserData })
.mockResolvedValueOnce({ error: null, data: mockAssetData });

const result = await status({}, { jwt: mockJwt });

expect(result).toEqual({
user: mockUserData,
wearing: mockAssetData,
});
expect(mockGetUserIdForJwt).toHaveBeenCalledWith(mockJwt);
expect(mockSupabaseStorage).toHaveBeenCalledWith({ jwt: mockJwt });
});

test('returns only user data when user has no active asset', async () => {
const mockJwt = 'mock-jwt';
const mockUserId = 'user-123';
const mockUserData = {
id: mockUserId,
active_asset: null,
};

mockGetUserIdForJwt.mockResolvedValue(mockUserId);

const mockSupabase = {
from: jest.fn().mockReturnThis(),
select: jest.fn().mockReturnThis(),
eq: jest.fn().mockReturnThis(),
maybeSingle: jest.fn(),
};

mockSupabaseStorage.mockImplementation(() => mockSupabase);
mockSupabase.maybeSingle.mockResolvedValueOnce({ error: null, data: mockUserData });

const result = await status({}, { jwt: mockJwt });

expect(result).toEqual({
user: mockUserData,
wearing: null,
});
});

test('throws error when account query fails', async () => {
const mockJwt = 'mock-jwt';
const mockUserId = 'user-123';
const mockError = 'Database error';

mockGetUserIdForJwt.mockResolvedValue(mockUserId);

const mockSupabase = {
from: jest.fn().mockReturnThis(),
select: jest.fn().mockReturnThis(),
eq: jest.fn().mockReturnThis(),
maybeSingle: jest.fn(),
};

mockSupabaseStorage.mockImplementation(() => mockSupabase);
mockSupabase.maybeSingle.mockResolvedValueOnce({ error: mockError, data: null });

await expect(status({}, { jwt: mockJwt }))
.rejects
.toThrow(`could not get account ${mockUserId}: ${mockError}`);
});
});
Loading
Loading