Add comprehensive authentication tests for PR #227

- Add thorough test coverage for authorizeAnalytics function
- Add tests for authorizeShareToken and getTaskById with share token support
- Add tests for authorizeMessageShareToken and getMessages with share token support
- Cover security scenarios, error handling, and edge cases
- Address @mrubens request for authentication code testing in modal hash navigation PR
- Fix TypeScript any types to use unknown types for better type safety
This commit is contained in:
Roo Code 2025-07-08 11:51:37 +00:00
parent 1e265eaba3
commit 780cc07123
3 changed files with 1300 additions and 0 deletions

View file

@ -0,0 +1,319 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { authorizeAnalytics } from '../auth';
// Mock Clerk auth
vi.mock('@clerk/nextjs/server', () => ({
auth: vi.fn(),
}));
describe('authorizeAnalytics', () => {
let mockAuth: ReturnType<typeof vi.fn>;
beforeEach(async () => {
vi.clearAllMocks();
mockAuth = vi.mocked((await import('@clerk/nextjs/server')).auth);
});
describe('Personal account access', () => {
it('should allow personal user to access their own data', async () => {
mockAuth.mockResolvedValue({
userId: 'user-123',
orgId: null,
orgRole: null,
});
const result = await authorizeAnalytics({
requestedOrgId: null,
requestedUserId: 'user-123',
});
expect(result).toEqual({
authOrgId: null,
authUserId: 'user-123',
orgRole: null,
isAdmin: false,
});
});
it('should allow personal user to access data without specifying userId', async () => {
mockAuth.mockResolvedValue({
userId: 'user-123',
orgId: null,
orgRole: null,
});
const result = await authorizeAnalytics({
requestedOrgId: null,
requestedUserId: null,
});
expect(result).toEqual({
authOrgId: null,
authUserId: 'user-123',
orgRole: null,
isAdmin: false,
});
});
it('should throw error when personal user tries to access other user data', async () => {
mockAuth.mockResolvedValue({
userId: 'user-123',
orgId: null,
orgRole: null,
});
await expect(
authorizeAnalytics({
requestedOrgId: null,
requestedUserId: 'other-user-456',
}),
).rejects.toThrow(
'Unauthorized: Personal users can only access their own data',
);
});
it('should throw error when no user is authenticated', async () => {
mockAuth.mockResolvedValue({
userId: null,
orgId: null,
orgRole: null,
});
await expect(
authorizeAnalytics({
requestedOrgId: null,
requestedUserId: null,
}),
).rejects.toThrow('Unauthorized: User required');
});
});
describe('Organization account access', () => {
it('should allow org member to access org data', async () => {
mockAuth.mockResolvedValue({
userId: 'user-123',
orgId: 'org-456',
orgRole: 'org:member',
});
const result = await authorizeAnalytics({
requestedOrgId: 'org-456',
requestedUserId: null,
});
expect(result).toEqual({
authOrgId: 'org-456',
authUserId: 'user-123',
orgRole: 'org:member',
isAdmin: false,
});
});
it('should allow org admin to access org data', async () => {
mockAuth.mockResolvedValue({
userId: 'user-123',
orgId: 'org-456',
orgRole: 'org:admin',
});
const result = await authorizeAnalytics({
requestedOrgId: 'org-456',
requestedUserId: null,
});
expect(result).toEqual({
authOrgId: 'org-456',
authUserId: 'user-123',
orgRole: 'org:admin',
isAdmin: true,
});
});
it('should handle invalid org role by defaulting to member', async () => {
mockAuth.mockResolvedValue({
userId: 'user-123',
orgId: 'org-456',
orgRole: 'invalid-role',
});
const result = await authorizeAnalytics({
requestedOrgId: 'org-456',
requestedUserId: null,
});
expect(result).toEqual({
authOrgId: 'org-456',
authUserId: 'user-123',
orgRole: 'org:member',
isAdmin: false,
});
});
it('should throw error when user is not in the requested org', async () => {
mockAuth.mockResolvedValue({
userId: 'user-123',
orgId: 'org-456',
orgRole: 'org:member',
});
await expect(
authorizeAnalytics({
requestedOrgId: 'different-org-789',
requestedUserId: null,
}),
).rejects.toThrow('Unauthorized: Invalid organization access');
});
it('should throw error when user has no org but requests org data', async () => {
mockAuth.mockResolvedValue({
userId: 'user-123',
orgId: null,
orgRole: null,
});
await expect(
authorizeAnalytics({
requestedOrgId: 'org-456',
requestedUserId: null,
}),
).rejects.toThrow('Unauthorized: Invalid organization access');
});
it('should throw error when no user is authenticated for org access', async () => {
mockAuth.mockResolvedValue({
userId: null,
orgId: 'org-456',
orgRole: 'org:member',
});
await expect(
authorizeAnalytics({
requestedOrgId: 'org-456',
requestedUserId: null,
}),
).rejects.toThrow('Unauthorized: Invalid organization access');
});
});
describe('Admin access requirements', () => {
it('should allow admin when requireAdmin is true', async () => {
mockAuth.mockResolvedValue({
userId: 'user-123',
orgId: 'org-456',
orgRole: 'org:admin',
});
const result = await authorizeAnalytics({
requestedOrgId: 'org-456',
requestedUserId: null,
requireAdmin: true,
});
expect(result).toEqual({
authOrgId: 'org-456',
authUserId: 'user-123',
orgRole: 'org:admin',
isAdmin: true,
});
});
it('should throw error when requireAdmin is true but user is not admin', async () => {
mockAuth.mockResolvedValue({
userId: 'user-123',
orgId: 'org-456',
orgRole: 'org:member',
});
await expect(
authorizeAnalytics({
requestedOrgId: 'org-456',
requestedUserId: null,
requireAdmin: true,
}),
).rejects.toThrow('Unauthorized: Administrator access required');
});
it('should not require admin for personal accounts even when requireAdmin is true', async () => {
mockAuth.mockResolvedValue({
userId: 'user-123',
orgId: null,
orgRole: null,
});
const result = await authorizeAnalytics({
requestedOrgId: null,
requestedUserId: 'user-123',
requireAdmin: true,
});
expect(result).toEqual({
authOrgId: null,
authUserId: 'user-123',
orgRole: null,
isAdmin: false,
});
});
});
describe('Edge cases', () => {
it('should handle undefined orgRole', async () => {
mockAuth.mockResolvedValue({
userId: 'user-123',
orgId: 'org-456',
orgRole: undefined,
});
const result = await authorizeAnalytics({
requestedOrgId: 'org-456',
requestedUserId: null,
});
expect(result).toEqual({
authOrgId: 'org-456',
authUserId: 'user-123',
orgRole: 'org:member',
isAdmin: false,
});
});
it('should handle null orgRole', async () => {
mockAuth.mockResolvedValue({
userId: 'user-123',
orgId: 'org-456',
orgRole: null,
});
const result = await authorizeAnalytics({
requestedOrgId: 'org-456',
requestedUserId: null,
});
expect(result).toEqual({
authOrgId: 'org-456',
authUserId: 'user-123',
orgRole: 'org:member',
isAdmin: false,
});
});
it('should handle empty string orgRole', async () => {
mockAuth.mockResolvedValue({
userId: 'user-123',
orgId: 'org-456',
orgRole: '',
});
const result = await authorizeAnalytics({
requestedOrgId: 'org-456',
requestedUserId: null,
});
expect(result).toEqual({
authOrgId: 'org-456',
authUserId: 'user-123',
orgRole: 'org:member',
isAdmin: false,
});
});
});
});

View file

@ -0,0 +1,441 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { getTaskById, getTasks } from '../events';
// Mock dependencies
vi.mock('@/lib/server', () => ({
analytics: {
query: vi.fn(),
},
}));
vi.mock('@/actions/auth', () => ({
authorizeAnalytics: vi.fn(),
}));
vi.mock('@roo-code-cloud/db/server', () => ({
getUsersById: vi.fn(),
db: {
select: vi.fn(),
},
taskShares: {},
eq: vi.fn(),
}));
vi.mock('@/lib/task-sharing', () => ({
isValidShareToken: vi.fn(),
isShareExpired: vi.fn(),
}));
vi.mock('@clerk/nextjs/server', () => ({
auth: vi.fn(),
}));
vi.mock('@/types', () => ({
TaskShareVisibility: {
PUBLIC: 'public',
ORGANIZATION: 'organization',
PRIVATE: 'private',
},
}));
describe('Share Token Authorization in Events', () => {
let mockAnalytics: unknown;
let mockAuthorizeAnalytics: unknown;
let mockGetUsersById: unknown;
let mockDb: unknown;
let mockIsValidShareToken: unknown;
let mockIsShareExpired: unknown;
let mockAuth: unknown;
beforeEach(async () => {
vi.clearAllMocks();
const { analytics } = await import('@/lib/server');
mockAnalytics = vi.mocked(analytics);
const { authorizeAnalytics } = await import('@/actions/auth');
mockAuthorizeAnalytics = vi.mocked(authorizeAnalytics);
const { getUsersById, db } = await import('@roo-code-cloud/db/server');
mockGetUsersById = vi.mocked(getUsersById);
mockDb = vi.mocked(db);
const { isValidShareToken, isShareExpired } = await import(
'@/lib/task-sharing'
);
mockIsValidShareToken = vi.mocked(isValidShareToken);
mockIsShareExpired = vi.mocked(isShareExpired);
const { auth } = await import('@clerk/nextjs/server');
mockAuth = vi.mocked(auth);
// Default mock implementations
mockGetUsersById.mockResolvedValue({
'user-123': {
id: 'user-123',
name: 'Test User',
email: 'test@example.com',
},
});
mockAnalytics.query.mockResolvedValue({
json: () =>
Promise.resolve([
{
taskId: 'task-123',
userId: 'user-123',
provider: 'openai',
model: 'gpt-4',
mode: 'code',
completed: true,
tokens: 1000,
cost: 0.02,
timestamp: 1640995200,
title: 'Test Task',
repositoryUrl: 'https://github.com/test/repo',
repositoryName: 'test-repo',
defaultBranch: 'main',
},
]),
});
});
describe('authorizeShareToken function', () => {
it('should return null for invalid share token format', async () => {
mockIsValidShareToken.mockReturnValue(false);
const result = await getTasks({
shareToken: 'invalid-token',
taskId: 'task-123',
});
expect(result).toEqual({ tasks: [], hasMore: false });
expect(mockIsValidShareToken).toHaveBeenCalledWith('invalid-token');
});
it('should return null when share not found in database', async () => {
mockIsValidShareToken.mockReturnValue(true);
mockDb.select.mockReturnValue({
from: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
limit: vi.fn().mockResolvedValue([]), // No share found
}),
}),
});
const result = await getTasks({
shareToken: 'valid-token',
taskId: 'task-123',
});
expect(result).toEqual({ tasks: [], hasMore: false });
});
it('should return null when share is expired', async () => {
mockIsValidShareToken.mockReturnValue(true);
mockIsShareExpired.mockReturnValue(true);
const mockShare = {
shareToken: 'valid-token',
taskId: 'task-123',
orgId: 'org-456',
visibility: 'public',
expiresAt: new Date('2023-01-01'),
};
mockDb.select.mockReturnValue({
from: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
limit: vi.fn().mockResolvedValue([{ share: mockShare }]),
}),
}),
});
const result = await getTasks({
shareToken: 'valid-token',
taskId: 'task-123',
});
expect(result).toEqual({ tasks: [], hasMore: false });
expect(mockIsShareExpired).toHaveBeenCalledWith(mockShare.expiresAt);
});
it('should validate organization access for org-scoped shares', async () => {
mockIsValidShareToken.mockReturnValue(true);
mockIsShareExpired.mockReturnValue(false);
const mockShare = {
shareToken: 'valid-token',
taskId: 'task-123',
orgId: 'org-456',
visibility: 'organization',
expiresAt: new Date('2025-01-01'),
};
mockDb.select.mockReturnValue({
from: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
limit: vi.fn().mockResolvedValue([{ share: mockShare }]),
}),
}),
});
// Mock auth to return different org
mockAuth.mockResolvedValue({
userId: 'user-123',
orgId: 'different-org-789',
});
const result = await getTasks({
shareToken: 'valid-token',
taskId: 'task-123',
});
expect(result).toEqual({ tasks: [], hasMore: false });
expect(mockAuth).toHaveBeenCalled();
});
it('should allow access for valid organization share with matching org', async () => {
mockIsValidShareToken.mockReturnValue(true);
mockIsShareExpired.mockReturnValue(false);
const mockShare = {
shareToken: 'valid-token',
taskId: 'task-123',
orgId: 'org-456',
visibility: 'organization',
expiresAt: new Date('2025-01-01'),
};
mockDb.select.mockReturnValue({
from: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
limit: vi.fn().mockResolvedValue([{ share: mockShare }]),
}),
}),
});
// Mock auth to return matching org
mockAuth.mockResolvedValue({
userId: 'user-123',
orgId: 'org-456',
});
const result = await getTasks({
shareToken: 'valid-token',
taskId: 'task-123',
});
expect(result.tasks).toHaveLength(1);
expect(result.tasks[0].taskId).toBe('task-123');
});
it('should allow access for valid public share', async () => {
mockIsValidShareToken.mockReturnValue(true);
mockIsShareExpired.mockReturnValue(false);
const mockShare = {
shareToken: 'valid-token',
taskId: 'task-123',
orgId: 'org-456',
visibility: 'public',
expiresAt: new Date('2025-01-01'),
};
mockDb.select.mockReturnValue({
from: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
limit: vi.fn().mockResolvedValue([{ share: mockShare }]),
}),
}),
});
const result = await getTasks({
shareToken: 'valid-token',
taskId: 'task-123',
});
expect(result.tasks).toHaveLength(1);
expect(result.tasks[0].taskId).toBe('task-123');
// Should not call auth for public shares
expect(mockAuth).not.toHaveBeenCalled();
});
it('should reject access when share token task does not match requested task', async () => {
mockIsValidShareToken.mockReturnValue(true);
mockIsShareExpired.mockReturnValue(false);
const mockShare = {
shareToken: 'valid-token',
taskId: 'task-123',
orgId: 'org-456',
visibility: 'public',
expiresAt: new Date('2025-01-01'),
};
mockDb.select.mockReturnValue({
from: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
limit: vi.fn().mockResolvedValue([{ share: mockShare }]),
}),
}),
});
const result = await getTasks({
shareToken: 'valid-token',
taskId: 'different-task-456', // Different task ID
});
expect(result).toEqual({ tasks: [], hasMore: false });
});
it('should handle database errors gracefully', async () => {
mockIsValidShareToken.mockReturnValue(true);
mockDb.select.mockImplementation(() => {
throw new Error('Database connection failed');
});
const result = await getTasks({
shareToken: 'valid-token',
taskId: 'task-123',
});
expect(result).toEqual({ tasks: [], hasMore: false });
});
it('should handle auth errors gracefully for organization shares', async () => {
mockIsValidShareToken.mockReturnValue(true);
mockIsShareExpired.mockReturnValue(false);
const mockShare = {
shareToken: 'valid-token',
taskId: 'task-123',
orgId: 'org-456',
visibility: 'organization',
expiresAt: new Date('2025-01-01'),
};
mockDb.select.mockReturnValue({
from: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
limit: vi.fn().mockResolvedValue([{ share: mockShare }]),
}),
}),
});
mockAuth.mockRejectedValue(new Error('Auth service unavailable'));
const result = await getTasks({
shareToken: 'valid-token',
taskId: 'task-123',
});
expect(result).toEqual({ tasks: [], hasMore: false });
});
});
describe('getTaskById with share token', () => {
it('should return task when valid share token is provided', async () => {
mockIsValidShareToken.mockReturnValue(true);
mockIsShareExpired.mockReturnValue(false);
const mockShare = {
shareToken: 'valid-token',
taskId: 'task-123',
orgId: 'org-456',
visibility: 'public',
expiresAt: new Date('2025-01-01'),
};
mockDb.select.mockReturnValue({
from: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
limit: vi.fn().mockResolvedValue([{ share: mockShare }]),
}),
}),
});
const result = await getTaskById({
taskId: 'task-123',
shareToken: 'valid-token',
});
expect(result).toBeTruthy();
expect(result?.taskId).toBe('task-123');
});
it('should return null when share token is invalid', async () => {
mockIsValidShareToken.mockReturnValue(false);
const result = await getTaskById({
taskId: 'task-123',
shareToken: 'invalid-token',
});
expect(result).toBeNull();
});
it('should return null when task ID does not match share', async () => {
mockIsValidShareToken.mockReturnValue(true);
mockIsShareExpired.mockReturnValue(false);
const mockShare = {
shareToken: 'valid-token',
taskId: 'task-123',
orgId: 'org-456',
visibility: 'public',
expiresAt: new Date('2025-01-01'),
};
mockDb.select.mockReturnValue({
from: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
limit: vi.fn().mockResolvedValue([{ share: mockShare }]),
}),
}),
});
const result = await getTaskById({
taskId: 'different-task-456',
shareToken: 'valid-token',
});
expect(result).toBeNull();
});
});
describe('Normal authentication flow (without share token)', () => {
it('should use normal authorization when no share token provided', async () => {
mockAuthorizeAnalytics.mockResolvedValue({
authUserId: 'user-123',
isAdmin: false,
});
const result = await getTasks({
orgId: 'org-456',
userId: 'user-123',
});
expect(mockAuthorizeAnalytics).toHaveBeenCalledWith({
requestedOrgId: 'org-456',
requestedUserId: 'user-123',
});
expect(result.tasks).toHaveLength(1);
});
it('should not call share token validation when no share token provided', async () => {
mockAuthorizeAnalytics.mockResolvedValue({
authUserId: 'user-123',
isAdmin: false,
});
await getTasks({
orgId: 'org-456',
userId: 'user-123',
});
expect(mockIsValidShareToken).not.toHaveBeenCalled();
expect(mockDb.select).not.toHaveBeenCalled();
});
});
});

View file

@ -0,0 +1,540 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { getMessages } from '../messages';
// Mock dependencies
vi.mock('@/lib/server', () => ({
analytics: {
query: vi.fn(),
},
}));
vi.mock('@/actions/auth', () => ({
authorizeAnalytics: vi.fn(),
}));
vi.mock('@roo-code-cloud/db/server', () => ({
db: {
select: vi.fn(),
},
taskShares: {},
eq: vi.fn(),
}));
vi.mock('@/lib/task-sharing', () => ({
isValidShareToken: vi.fn(),
isShareExpired: vi.fn(),
}));
vi.mock('@clerk/nextjs/server', () => ({
auth: vi.fn(),
}));
vi.mock('@/types', () => ({
TaskShareVisibility: {
PUBLIC: 'public',
ORGANIZATION: 'organization',
PRIVATE: 'private',
},
}));
describe('Share Token Authorization in Messages', () => {
let mockAnalytics: unknown;
let mockAuthorizeAnalytics: unknown;
let mockDb: unknown;
let mockIsValidShareToken: unknown;
let mockIsShareExpired: unknown;
let mockAuth: unknown;
beforeEach(async () => {
vi.clearAllMocks();
const { analytics } = await import('@/lib/server');
mockAnalytics = vi.mocked(analytics);
const { authorizeAnalytics } = await import('@/actions/auth');
mockAuthorizeAnalytics = vi.mocked(authorizeAnalytics);
const { db } = await import('@roo-code-cloud/db/server');
mockDb = vi.mocked(db);
const { isValidShareToken, isShareExpired } = await import(
'@/lib/task-sharing'
);
mockIsValidShareToken = vi.mocked(isValidShareToken);
mockIsShareExpired = vi.mocked(isShareExpired);
const { auth } = await import('@clerk/nextjs/server');
mockAuth = vi.mocked(auth);
// Default mock implementations
mockAnalytics.query.mockResolvedValue({
json: () =>
Promise.resolve([
{
id: 'msg-1',
orgId: 'org-456',
userId: 'user-123',
taskId: 'task-123',
mode: 'code',
ts: 1640995200000,
type: 'ask',
ask: 'What should I do?',
say: null,
text: 'What should I do?',
reasoning: null,
partial: false,
timestamp: 1640995200,
},
{
id: 'msg-2',
orgId: 'org-456',
userId: 'user-123',
taskId: 'task-123',
mode: 'code',
ts: 1640995260000,
type: 'say',
ask: null,
say: 'I can help you with that.',
text: 'I can help you with that.',
reasoning: 'User needs assistance',
partial: false,
timestamp: 1640995260,
},
]),
});
});
describe('authorizeMessageShareToken function', () => {
it('should return null for invalid share token format', async () => {
mockIsValidShareToken.mockReturnValue(false);
const result = await getMessages(
'task-123',
'org-456',
'user-123',
'invalid-token',
);
expect(result).toEqual([]);
expect(mockIsValidShareToken).toHaveBeenCalledWith('invalid-token');
});
it('should return null when share not found in database', async () => {
mockIsValidShareToken.mockReturnValue(true);
mockDb.select.mockReturnValue({
from: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
limit: vi.fn().mockResolvedValue([]), // No share found
}),
}),
});
const result = await getMessages(
'task-123',
'org-456',
'user-123',
'valid-token',
);
expect(result).toEqual([]);
});
it('should return null when share is expired', async () => {
mockIsValidShareToken.mockReturnValue(true);
mockIsShareExpired.mockReturnValue(true);
const mockShare = {
shareToken: 'valid-token',
taskId: 'task-123',
orgId: 'org-456',
visibility: 'public',
expiresAt: new Date('2023-01-01'),
};
mockDb.select.mockReturnValue({
from: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
limit: vi.fn().mockResolvedValue([{ share: mockShare }]),
}),
}),
});
const result = await getMessages(
'task-123',
'org-456',
'user-123',
'valid-token',
);
expect(result).toEqual([]);
expect(mockIsShareExpired).toHaveBeenCalledWith(mockShare.expiresAt);
});
it('should validate organization access for org-scoped shares', async () => {
mockIsValidShareToken.mockReturnValue(true);
mockIsShareExpired.mockReturnValue(false);
const mockShare = {
shareToken: 'valid-token',
taskId: 'task-123',
orgId: 'org-456',
visibility: 'organization',
expiresAt: new Date('2025-01-01'),
};
mockDb.select.mockReturnValue({
from: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
limit: vi.fn().mockResolvedValue([{ share: mockShare }]),
}),
}),
});
// Mock auth to return different org
mockAuth.mockResolvedValue({
userId: 'user-123',
orgId: 'different-org-789',
});
const result = await getMessages(
'task-123',
'org-456',
'user-123',
'valid-token',
);
expect(result).toEqual([]);
expect(mockAuth).toHaveBeenCalled();
});
it('should allow access for valid organization share with matching org', async () => {
mockIsValidShareToken.mockReturnValue(true);
mockIsShareExpired.mockReturnValue(false);
const mockShare = {
shareToken: 'valid-token',
taskId: 'task-123',
orgId: 'org-456',
visibility: 'organization',
expiresAt: new Date('2025-01-01'),
};
mockDb.select.mockReturnValue({
from: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
limit: vi.fn().mockResolvedValue([{ share: mockShare }]),
}),
}),
});
// Mock auth to return matching org
mockAuth.mockResolvedValue({
userId: 'user-123',
orgId: 'org-456',
});
const result = await getMessages(
'task-123',
'org-456',
'user-123',
'valid-token',
);
expect(result).toHaveLength(2);
expect(result[0].taskId).toBe('task-123');
expect(result[1].taskId).toBe('task-123');
});
it('should allow access for valid public share', async () => {
mockIsValidShareToken.mockReturnValue(true);
mockIsShareExpired.mockReturnValue(false);
const mockShare = {
shareToken: 'valid-token',
taskId: 'task-123',
orgId: 'org-456',
visibility: 'public',
expiresAt: new Date('2025-01-01'),
};
mockDb.select.mockReturnValue({
from: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
limit: vi.fn().mockResolvedValue([{ share: mockShare }]),
}),
}),
});
const result = await getMessages(
'task-123',
'org-456',
'user-123',
'valid-token',
);
expect(result).toHaveLength(2);
expect(result[0].taskId).toBe('task-123');
// Should not call auth for public shares
expect(mockAuth).not.toHaveBeenCalled();
});
it('should reject access when share token task does not match requested task', async () => {
mockIsValidShareToken.mockReturnValue(true);
mockIsShareExpired.mockReturnValue(false);
const mockShare = {
shareToken: 'valid-token',
taskId: 'task-123',
orgId: 'org-456',
visibility: 'public',
expiresAt: new Date('2025-01-01'),
};
mockDb.select.mockReturnValue({
from: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
limit: vi.fn().mockResolvedValue([{ share: mockShare }]),
}),
}),
});
const result = await getMessages(
'different-task-456',
'org-456',
'user-123',
'valid-token',
);
expect(result).toEqual([]);
});
it('should handle database errors gracefully', async () => {
mockIsValidShareToken.mockReturnValue(true);
mockDb.select.mockImplementation(() => {
throw new Error('Database connection failed');
});
const result = await getMessages(
'task-123',
'org-456',
'user-123',
'valid-token',
);
expect(result).toEqual([]);
});
it('should handle auth errors gracefully for organization shares', async () => {
mockIsValidShareToken.mockReturnValue(true);
mockIsShareExpired.mockReturnValue(false);
const mockShare = {
shareToken: 'valid-token',
taskId: 'task-123',
orgId: 'org-456',
visibility: 'organization',
expiresAt: new Date('2025-01-01'),
};
mockDb.select.mockReturnValue({
from: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
limit: vi.fn().mockResolvedValue([{ share: mockShare }]),
}),
}),
});
mockAuth.mockRejectedValue(new Error('Auth service unavailable'));
const result = await getMessages(
'task-123',
'org-456',
'user-123',
'valid-token',
);
expect(result).toEqual([]);
});
it('should handle missing user in auth response for organization shares', async () => {
mockIsValidShareToken.mockReturnValue(true);
mockIsShareExpired.mockReturnValue(false);
const mockShare = {
shareToken: 'valid-token',
taskId: 'task-123',
orgId: 'org-456',
visibility: 'organization',
expiresAt: new Date('2025-01-01'),
};
mockDb.select.mockReturnValue({
from: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
limit: vi.fn().mockResolvedValue([{ share: mockShare }]),
}),
}),
});
// Mock auth to return no user
mockAuth.mockResolvedValue({
userId: null,
orgId: 'org-456',
});
const result = await getMessages(
'task-123',
'org-456',
'user-123',
'valid-token',
);
expect(result).toEqual([]);
});
it('should handle missing org in auth response for organization shares', async () => {
mockIsValidShareToken.mockReturnValue(true);
mockIsShareExpired.mockReturnValue(false);
const mockShare = {
shareToken: 'valid-token',
taskId: 'task-123',
orgId: 'org-456',
visibility: 'organization',
expiresAt: new Date('2025-01-01'),
};
mockDb.select.mockReturnValue({
from: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
limit: vi.fn().mockResolvedValue([{ share: mockShare }]),
}),
}),
});
// Mock auth to return no org
mockAuth.mockResolvedValue({
userId: 'user-123',
orgId: null,
});
const result = await getMessages(
'task-123',
'org-456',
'user-123',
'valid-token',
);
expect(result).toEqual([]);
});
});
describe('Normal authentication flow (without share token)', () => {
it('should use normal authorization when no share token provided', async () => {
mockAuthorizeAnalytics.mockResolvedValue({
authUserId: 'user-123',
isAdmin: false,
});
const result = await getMessages('task-123', 'org-456', 'user-123');
expect(mockAuthorizeAnalytics).toHaveBeenCalledWith({
requestedOrgId: 'org-456',
requestedUserId: 'user-123',
});
expect(result).toHaveLength(2);
});
it('should not call share token validation when no share token provided', async () => {
mockAuthorizeAnalytics.mockResolvedValue({
authUserId: 'user-123',
isAdmin: false,
});
await getMessages('task-123', 'org-456', 'user-123');
expect(mockIsValidShareToken).not.toHaveBeenCalled();
expect(mockDb.select).not.toHaveBeenCalled();
});
it('should handle personal account access (null orgId)', async () => {
mockAuthorizeAnalytics.mockResolvedValue({
authUserId: 'user-123',
isAdmin: false,
});
const result = await getMessages('task-123', null, 'user-123');
expect(mockAuthorizeAnalytics).toHaveBeenCalledWith({
requestedOrgId: null,
requestedUserId: 'user-123',
});
expect(result).toHaveLength(2);
});
});
describe('Query parameter handling', () => {
it('should build correct query for organization messages', async () => {
mockIsValidShareToken.mockReturnValue(true);
mockIsShareExpired.mockReturnValue(false);
const mockShare = {
shareToken: 'valid-token',
taskId: 'task-123',
orgId: 'org-456',
visibility: 'public',
expiresAt: new Date('2025-01-01'),
};
mockDb.select.mockReturnValue({
from: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
limit: vi.fn().mockResolvedValue([{ share: mockShare }]),
}),
}),
});
await getMessages('task-123', 'org-456', 'user-123', 'valid-token');
expect(mockAnalytics.query).toHaveBeenCalledWith({
query: expect.stringContaining('orgId = {orgId: String}'),
format: 'JSONEachRow',
query_params: {
taskId: 'task-123',
orgId: 'org-456',
},
});
});
it('should build correct query for personal account messages', async () => {
mockIsValidShareToken.mockReturnValue(true);
mockIsShareExpired.mockReturnValue(false);
const mockShare = {
shareToken: 'valid-token',
taskId: 'task-123',
orgId: null,
visibility: 'public',
expiresAt: new Date('2025-01-01'),
};
mockDb.select.mockReturnValue({
from: vi.fn().mockReturnValue({
where: vi.fn().mockReturnValue({
limit: vi.fn().mockResolvedValue([{ share: mockShare }]),
}),
}),
});
await getMessages('task-123', null, 'user-123', 'valid-token');
expect(mockAnalytics.query).toHaveBeenCalledWith({
query: expect.stringContaining('orgId IS NULL'),
format: 'JSONEachRow',
query_params: {
taskId: 'task-123',
},
});
});
});
});