mirror of https://github.com/buster-so/buster.git
fix all of the bugs
This commit is contained in:
parent
4a1c4f73f0
commit
963bf6b2f2
|
@ -1,6 +1,9 @@
|
||||||
{
|
{
|
||||||
"extends": "@buster/typescript-config/base.json",
|
"extends": "@buster/typescript-config/base.json",
|
||||||
"compilerOptions": {},
|
"compilerOptions": {},
|
||||||
"include": ["scripts/**/*"],
|
"include": ["scripts*"],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"],
|
||||||
}
|
"references": [
|
||||||
|
{ "path": "../../packages/database" }
|
||||||
|
]
|
||||||
|
}
|
|
@ -66,6 +66,8 @@ const mockMessage: Message = {
|
||||||
updatedAt: new Date().toISOString(),
|
updatedAt: new Date().toISOString(),
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
feedback: null,
|
feedback: null,
|
||||||
|
postProcessingMessage: null,
|
||||||
|
triggerRunId: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('buildChatWithMessages', () => {
|
describe('buildChatWithMessages', () => {
|
||||||
|
|
|
@ -36,9 +36,9 @@ describe('Chat Message Redo Integration Tests', () => {
|
||||||
await db.insert(organizations).values({
|
await db.insert(organizations).values({
|
||||||
id: testOrgId,
|
id: testOrgId,
|
||||||
name: 'Test Organization for Redo',
|
name: 'Test Organization for Redo',
|
||||||
slug: 'test-org-redo',
|
restrictNewUserInvitations: false,
|
||||||
createdBy: testUserId,
|
defaultRole: 'workspace_admin',
|
||||||
updatedBy: testUserId,
|
domains: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
// Create test user
|
// Create test user
|
||||||
|
@ -49,7 +49,6 @@ describe('Chat Message Redo Integration Tests', () => {
|
||||||
email: 'test-redo@example.com',
|
email: 'test-redo@example.com',
|
||||||
name: 'Test User Redo',
|
name: 'Test User Redo',
|
||||||
avatarUrl: null,
|
avatarUrl: null,
|
||||||
metadata: {},
|
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,8 @@ const mockMessage: Message = {
|
||||||
updatedAt: new Date().toISOString(),
|
updatedAt: new Date().toISOString(),
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
feedback: null,
|
feedback: null,
|
||||||
|
postProcessingMessage: null,
|
||||||
|
triggerRunId: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mock database functions
|
// Mock database functions
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import type { Organization, User } from '@buster/database';
|
import type { User } from '@buster/database';
|
||||||
|
import type { Organization, OrganizationRole } from '@buster/server-shared/organization';
|
||||||
import { HTTPException } from 'hono/http-exception';
|
import { HTTPException } from 'hono/http-exception';
|
||||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||||
import { addApprovedDomainsHandler } from './add-approved-domains';
|
import { addApprovedDomainsHandler } from './add-approved-domains';
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { db, eq, organizations } from '@buster/database';
|
import { db, eq, organizations } from '@buster/database';
|
||||||
import type { Organization, User } from '@buster/database';
|
import type { User } from '@buster/database';
|
||||||
|
import type { Organization, OrganizationRole } from '@buster/server-shared/organization';
|
||||||
import { HTTPException } from 'hono/http-exception';
|
import { HTTPException } from 'hono/http-exception';
|
||||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||||
import { getApprovedDomainsHandler } from './get-approved-domains';
|
import { getApprovedDomainsHandler } from './get-approved-domains';
|
||||||
|
@ -68,7 +69,7 @@ describe('getApprovedDomainsHandler (integration)', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work for users with different roles', async () => {
|
it('should work for users with different roles', async () => {
|
||||||
const roles = ['querier', 'data_admin', 'workspace_admin'];
|
const roles: OrganizationRole[] = ['querier', 'data_admin', 'workspace_admin'];
|
||||||
|
|
||||||
for (const role of roles) {
|
for (const role of roles) {
|
||||||
let roleUser: User | undefined;
|
let roleUser: User | undefined;
|
||||||
|
@ -153,7 +154,7 @@ describe('getApprovedDomainsHandler (integration)', () => {
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
expect(orgAfter[0]?.domains).toEqual(originalOrg.domains);
|
expect(orgAfter[0]?.domains).toEqual(originalOrg.domains);
|
||||||
expect(new Date(orgAfter[0]?.updatedAt).toISOString()).toEqual(
|
expect(new Date(orgAfter[0]!.updatedAt).toISOString()).toEqual(
|
||||||
new Date(originalOrg.updatedAt).toISOString()
|
new Date(originalOrg.updatedAt).toISOString()
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { db, eq, organizations } from '@buster/database';
|
import { db, eq, organizations } from '@buster/database';
|
||||||
import type { Organization, User } from '@buster/database';
|
import type { User } from '@buster/database';
|
||||||
|
import type { OrganizationRole } from '@buster/server-shared/organization';
|
||||||
|
import type { InferSelectModel } from 'drizzle-orm';
|
||||||
import { HTTPException } from 'hono/http-exception';
|
import { HTTPException } from 'hono/http-exception';
|
||||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||||
import { getWorkspaceSettingsHandler } from './get-workspace-settings';
|
import { getWorkspaceSettingsHandler } from './get-workspace-settings';
|
||||||
|
@ -12,6 +14,8 @@ import {
|
||||||
createUserWithoutOrganization,
|
createUserWithoutOrganization,
|
||||||
} from './test-db-utils';
|
} from './test-db-utils';
|
||||||
|
|
||||||
|
type Organization = InferSelectModel<typeof organizations>;
|
||||||
|
|
||||||
describe('getWorkspaceSettingsHandler (integration)', () => {
|
describe('getWorkspaceSettingsHandler (integration)', () => {
|
||||||
let testUser: User;
|
let testUser: User;
|
||||||
let testOrg: Organization;
|
let testOrg: Organization;
|
||||||
|
@ -61,7 +65,12 @@ describe('getWorkspaceSettingsHandler (integration)', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should work for users with different roles', async () => {
|
it('should work for users with different roles', async () => {
|
||||||
const roles = ['querier', 'restricted_querier', 'data_admin', 'workspace_admin'];
|
const roles: OrganizationRole[] = [
|
||||||
|
'querier',
|
||||||
|
'restricted_querier',
|
||||||
|
'data_admin',
|
||||||
|
'workspace_admin',
|
||||||
|
];
|
||||||
|
|
||||||
for (const role of roles) {
|
for (const role of roles) {
|
||||||
const roleUser = await createTestUserInDb();
|
const roleUser = await createTestUserInDb();
|
||||||
|
@ -169,7 +178,7 @@ describe('getWorkspaceSettingsHandler (integration)', () => {
|
||||||
originalOrg.restrictNewUserInvitations
|
originalOrg.restrictNewUserInvitations
|
||||||
);
|
);
|
||||||
expect(orgAfter[0]?.defaultRole).toEqual(originalOrg.defaultRole);
|
expect(orgAfter[0]?.defaultRole).toEqual(originalOrg.defaultRole);
|
||||||
expect(new Date(orgAfter[0]?.updatedAt).toISOString()).toEqual(originalOrg.updatedAt);
|
expect(new Date(orgAfter[0]!.updatedAt).toISOString()).toEqual(originalOrg.updatedAt);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should always return empty default_datasets array', async () => {
|
it('should always return empty default_datasets array', async () => {
|
||||||
|
|
|
@ -67,7 +67,7 @@ describe('getWorkspaceSettingsHandler', () => {
|
||||||
const orgWithDifferentSettings = {
|
const orgWithDifferentSettings = {
|
||||||
...mockOrg,
|
...mockOrg,
|
||||||
restrictNewUserInvitations: false,
|
restrictNewUserInvitations: false,
|
||||||
defaultRole: 'data_admin',
|
defaultRole: 'data_admin' as const,
|
||||||
};
|
};
|
||||||
vi.mocked(securityUtils.fetchOrganization).mockResolvedValue(orgWithDifferentSettings);
|
vi.mocked(securityUtils.fetchOrganization).mockResolvedValue(orgWithDifferentSettings);
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import type { Organization, User } from '@buster/database';
|
import type { User, organizations } from '@buster/database';
|
||||||
|
import type { InferSelectModel } from 'drizzle-orm';
|
||||||
import { HTTPException } from 'hono/http-exception';
|
import { HTTPException } from 'hono/http-exception';
|
||||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||||
import { removeApprovedDomainsHandler } from './remove-approved-domains';
|
import { removeApprovedDomainsHandler } from './remove-approved-domains';
|
||||||
|
@ -12,6 +13,8 @@ import {
|
||||||
getOrganizationFromDb,
|
getOrganizationFromDb,
|
||||||
} from './test-db-utils';
|
} from './test-db-utils';
|
||||||
|
|
||||||
|
type Organization = InferSelectModel<typeof organizations>;
|
||||||
|
|
||||||
describe('removeApprovedDomainsHandler (integration)', () => {
|
describe('removeApprovedDomainsHandler (integration)', () => {
|
||||||
let testUser: User;
|
let testUser: User;
|
||||||
let testOrg: Organization;
|
let testOrg: Organization;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import type { Organization, User } from '@buster/database';
|
import type { User, organizations } from '@buster/database';
|
||||||
import { db, usersToOrganizations } from '@buster/database';
|
import type { OrganizationRole } from '@buster/server-shared/organization';
|
||||||
import { eq } from 'drizzle-orm';
|
import type { InferSelectModel } from 'drizzle-orm';
|
||||||
import { HTTPException } from 'hono/http-exception';
|
import { HTTPException } from 'hono/http-exception';
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
import {
|
import {
|
||||||
|
@ -15,6 +15,8 @@ import {
|
||||||
} from './test-db-utils';
|
} from './test-db-utils';
|
||||||
import { updateWorkspaceSettingsHandler } from './update-workspace-settings';
|
import { updateWorkspaceSettingsHandler } from './update-workspace-settings';
|
||||||
|
|
||||||
|
type Organization = InferSelectModel<typeof organizations>;
|
||||||
|
|
||||||
describe('updateWorkspaceSettingsHandler (integration)', () => {
|
describe('updateWorkspaceSettingsHandler (integration)', () => {
|
||||||
let testUser: User;
|
let testUser: User;
|
||||||
let testOrg: Organization;
|
let testOrg: Organization;
|
||||||
|
@ -45,7 +47,7 @@ describe('updateWorkspaceSettingsHandler (integration)', () => {
|
||||||
|
|
||||||
const request = {
|
const request = {
|
||||||
restrict_new_user_invitations: true,
|
restrict_new_user_invitations: true,
|
||||||
default_role: 'data_admin',
|
default_role: 'data_admin' as const,
|
||||||
};
|
};
|
||||||
const result = await updateWorkspaceSettingsHandler(request, testUser);
|
const result = await updateWorkspaceSettingsHandler(request, testUser);
|
||||||
|
|
||||||
|
@ -72,7 +74,7 @@ describe('updateWorkspaceSettingsHandler (integration)', () => {
|
||||||
expect(result1.default_role).toBe('querier'); // Should remain unchanged
|
expect(result1.default_role).toBe('querier'); // Should remain unchanged
|
||||||
|
|
||||||
// Update only default_role
|
// Update only default_role
|
||||||
const request2 = { default_role: 'data_admin' };
|
const request2 = { default_role: 'data_admin' as const };
|
||||||
const result2 = await updateWorkspaceSettingsHandler(request2, testUser);
|
const result2 = await updateWorkspaceSettingsHandler(request2, testUser);
|
||||||
|
|
||||||
expect(result2.restrict_new_user_invitations).toBe(true); // Should keep previous update
|
expect(result2.restrict_new_user_invitations).toBe(true); // Should keep previous update
|
||||||
|
@ -88,7 +90,7 @@ describe('updateWorkspaceSettingsHandler (integration)', () => {
|
||||||
|
|
||||||
const request = {
|
const request = {
|
||||||
restrict_new_user_invitations: true,
|
restrict_new_user_invitations: true,
|
||||||
default_role: 'viewer',
|
default_role: 'viewer' as const,
|
||||||
};
|
};
|
||||||
const result = await updateWorkspaceSettingsHandler(request, testUser);
|
const result = await updateWorkspaceSettingsHandler(request, testUser);
|
||||||
|
|
||||||
|
@ -122,7 +124,7 @@ describe('updateWorkspaceSettingsHandler (integration)', () => {
|
||||||
|
|
||||||
describe('Error Cases', () => {
|
describe('Error Cases', () => {
|
||||||
it('should return 403 for non-workspace-admin users', async () => {
|
it('should return 403 for non-workspace-admin users', async () => {
|
||||||
const roles = ['querier', 'restricted_querier', 'data_admin', 'viewer'];
|
const roles: OrganizationRole[] = ['querier', 'restricted_querier', 'data_admin', 'viewer'];
|
||||||
|
|
||||||
for (const role of roles) {
|
for (const role of roles) {
|
||||||
// Create a fresh organization for each role test to avoid conflicts
|
// Create a fresh organization for each role test to avoid conflicts
|
||||||
|
@ -193,7 +195,7 @@ describe('updateWorkspaceSettingsHandler (integration)', () => {
|
||||||
|
|
||||||
const request = {
|
const request = {
|
||||||
restrict_new_user_invitations: true,
|
restrict_new_user_invitations: true,
|
||||||
default_role: 'viewer',
|
default_role: 'viewer' as const,
|
||||||
};
|
};
|
||||||
await updateWorkspaceSettingsHandler(request, testUser);
|
await updateWorkspaceSettingsHandler(request, testUser);
|
||||||
|
|
||||||
|
@ -209,13 +211,13 @@ describe('updateWorkspaceSettingsHandler (integration)', () => {
|
||||||
const originalName = testOrg.name;
|
const originalName = testOrg.name;
|
||||||
const originalCreatedAt = testOrg.createdAt;
|
const originalCreatedAt = testOrg.createdAt;
|
||||||
|
|
||||||
const request = { default_role: 'data_admin' };
|
const request = { default_role: 'data_admin' as const };
|
||||||
await updateWorkspaceSettingsHandler(request, testUser);
|
await updateWorkspaceSettingsHandler(request, testUser);
|
||||||
|
|
||||||
const updatedOrg = await getOrganizationFromDb(testOrg.id);
|
const updatedOrg = await getOrganizationFromDb(testOrg.id);
|
||||||
expect(updatedOrg?.domains).toEqual(originalDomains);
|
expect(updatedOrg?.domains).toEqual(originalDomains);
|
||||||
expect(updatedOrg?.name).toBe(originalName);
|
expect(updatedOrg?.name).toBe(originalName);
|
||||||
expect(new Date(updatedOrg?.createdAt).toISOString()).toBe(originalCreatedAt);
|
expect(new Date(updatedOrg!.createdAt).toISOString()).toBe(originalCreatedAt);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should update organization's updatedAt timestamp", async () => {
|
it("should update organization's updatedAt timestamp", async () => {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import type { OrganizationRole } from '@buster/server-shared/organization';
|
||||||
import type { UpdateWorkspaceSettingsRequest } from '@buster/server-shared/security';
|
import type { UpdateWorkspaceSettingsRequest } from '@buster/server-shared/security';
|
||||||
import { describe, expect, it } from 'vitest';
|
import { describe, expect, it } from 'vitest';
|
||||||
import { WorkspaceSettingsService } from './workspace-settings-service';
|
import { WorkspaceSettingsService } from './workspace-settings-service';
|
||||||
|
@ -9,8 +10,8 @@ describe('WorkspaceSettingsService', () => {
|
||||||
it('should map snake_case to camelCase fields', () => {
|
it('should map snake_case to camelCase fields', () => {
|
||||||
const settings = {
|
const settings = {
|
||||||
restrictNewUserInvitations: true,
|
restrictNewUserInvitations: true,
|
||||||
defaultRole: 'member',
|
defaultRole: 'workspace_admin',
|
||||||
};
|
} as Parameters<typeof service.formatWorkspaceSettingsResponse>[0];
|
||||||
const result = service.formatWorkspaceSettingsResponse(settings);
|
const result = service.formatWorkspaceSettingsResponse(settings);
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
restrict_new_user_invitations: true,
|
restrict_new_user_invitations: true,
|
||||||
|
@ -22,8 +23,8 @@ describe('WorkspaceSettingsService', () => {
|
||||||
it('should include empty default_datasets array', () => {
|
it('should include empty default_datasets array', () => {
|
||||||
const settings = {
|
const settings = {
|
||||||
restrictNewUserInvitations: false,
|
restrictNewUserInvitations: false,
|
||||||
defaultRole: 'admin',
|
defaultRole: 'admin' as OrganizationRole,
|
||||||
};
|
} as Parameters<typeof service.formatWorkspaceSettingsResponse>[0];
|
||||||
const result = service.formatWorkspaceSettingsResponse(settings);
|
const result = service.formatWorkspaceSettingsResponse(settings);
|
||||||
expect(result.default_datasets).toEqual([]);
|
expect(result.default_datasets).toEqual([]);
|
||||||
});
|
});
|
||||||
|
@ -31,12 +32,12 @@ describe('WorkspaceSettingsService', () => {
|
||||||
it('should handle all boolean values correctly', () => {
|
it('should handle all boolean values correctly', () => {
|
||||||
const settingsTrue = {
|
const settingsTrue = {
|
||||||
restrictNewUserInvitations: true,
|
restrictNewUserInvitations: true,
|
||||||
defaultRole: 'member',
|
defaultRole: 'member' as OrganizationRole,
|
||||||
};
|
} as Parameters<typeof service.formatWorkspaceSettingsResponse>[0];
|
||||||
const settingsFalse = {
|
const settingsFalse = {
|
||||||
restrictNewUserInvitations: false,
|
restrictNewUserInvitations: false,
|
||||||
defaultRole: 'member',
|
defaultRole: 'member' as OrganizationRole,
|
||||||
};
|
} as Parameters<typeof service.formatWorkspaceSettingsResponse>[0];
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
service.formatWorkspaceSettingsResponse(settingsTrue).restrict_new_user_invitations
|
service.formatWorkspaceSettingsResponse(settingsTrue).restrict_new_user_invitations
|
||||||
|
@ -57,9 +58,9 @@ describe('WorkspaceSettingsService', () => {
|
||||||
];
|
];
|
||||||
|
|
||||||
for (const role of roles) {
|
for (const role of roles) {
|
||||||
const settings = {
|
const settings: Parameters<typeof service.formatWorkspaceSettingsResponse>[0] = {
|
||||||
restrictNewUserInvitations: false,
|
restrictNewUserInvitations: false,
|
||||||
defaultRole: role,
|
defaultRole: role as OrganizationRole,
|
||||||
};
|
};
|
||||||
const result = service.formatWorkspaceSettingsResponse(settings);
|
const result = service.formatWorkspaceSettingsResponse(settings);
|
||||||
expect(result.default_role).toBe(role);
|
expect(result.default_role).toBe(role);
|
||||||
|
@ -88,8 +89,8 @@ describe('WorkspaceSettingsService', () => {
|
||||||
|
|
||||||
it('should handle partial updates (only default_role)', () => {
|
it('should handle partial updates (only default_role)', () => {
|
||||||
const request: UpdateWorkspaceSettingsRequest = {
|
const request: UpdateWorkspaceSettingsRequest = {
|
||||||
default_role: 'admin',
|
default_role: 'admin' as OrganizationRole,
|
||||||
};
|
} as Parameters<typeof service.buildUpdateData>[0];
|
||||||
const result = service.buildUpdateData(request);
|
const result = service.buildUpdateData(request);
|
||||||
|
|
||||||
expect(result).toHaveProperty('defaultRole', 'admin');
|
expect(result).toHaveProperty('defaultRole', 'admin');
|
||||||
|
@ -99,8 +100,8 @@ describe('WorkspaceSettingsService', () => {
|
||||||
it('should handle full updates', () => {
|
it('should handle full updates', () => {
|
||||||
const request: UpdateWorkspaceSettingsRequest = {
|
const request: UpdateWorkspaceSettingsRequest = {
|
||||||
restrict_new_user_invitations: false,
|
restrict_new_user_invitations: false,
|
||||||
default_role: 'member',
|
default_role: 'member' as OrganizationRole,
|
||||||
};
|
} as Parameters<typeof service.buildUpdateData>[0];
|
||||||
const result = service.buildUpdateData(request);
|
const result = service.buildUpdateData(request);
|
||||||
|
|
||||||
expect(result).toHaveProperty('restrictNewUserInvitations', false);
|
expect(result).toHaveProperty('restrictNewUserInvitations', false);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { db, slackIntegrations } from '@buster/database';
|
import { db, slackIntegrations } from '@buster/database';
|
||||||
|
import type { GetChannelsResponse, SlackErrorResponse } from '@buster/server-shared/slack';
|
||||||
import { eq } from 'drizzle-orm';
|
import { eq } from 'drizzle-orm';
|
||||||
import { Hono } from 'hono';
|
import { Hono } from 'hono';
|
||||||
import type { Context } from 'hono';
|
import type { Context } from 'hono';
|
||||||
|
@ -90,7 +91,12 @@ describe.skipIf(skipIfNoEnv)('Slack Channels Integration Tests', () => {
|
||||||
app.use('*', async (c, next) => {
|
app.use('*', async (c, next) => {
|
||||||
const path = c.req.path;
|
const path = c.req.path;
|
||||||
if (path.includes('/channels')) {
|
if (path.includes('/channels')) {
|
||||||
(c as Context).set('busterUser', { id: testUserId });
|
(c as Context).set('busterUser', {
|
||||||
|
id: testUserId,
|
||||||
|
name: 'Test User',
|
||||||
|
email: 'test@example.com',
|
||||||
|
avatarUrl: 'https://example.com/avatar.png',
|
||||||
|
});
|
||||||
(c as Context).set('organizationId', testOrganizationId);
|
(c as Context).set('organizationId', testOrganizationId);
|
||||||
}
|
}
|
||||||
await next();
|
await next();
|
||||||
|
@ -115,7 +121,7 @@ describe.skipIf(skipIfNoEnv)('Slack Channels Integration Tests', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.status).toBe(404);
|
expect(response.status).toBe(404);
|
||||||
const data = await response.json();
|
const data = (await response.json()) as SlackErrorResponse;
|
||||||
expect(data.error).toBe('No active Slack integration found');
|
expect(data.error).toBe('No active Slack integration found');
|
||||||
expect(data.code).toBe('INTEGRATION_NOT_FOUND');
|
expect(data.code).toBe('INTEGRATION_NOT_FOUND');
|
||||||
});
|
});
|
||||||
|
@ -150,14 +156,14 @@ describe.skipIf(skipIfNoEnv)('Slack Channels Integration Tests', () => {
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
createdIntegrationIds.push(integration.id);
|
createdIntegrationIds.push(integration!.id);
|
||||||
|
|
||||||
const response = await app.request('/api/v2/slack/channels', {
|
const response = await app.request('/api/v2/slack/channels', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
expect(response.status).toBe(200);
|
||||||
const data = await response.json();
|
const data = (await response.json()) as GetChannelsResponse;
|
||||||
expect(data.channels).toBeDefined();
|
expect(data.channels).toBeDefined();
|
||||||
expect(Array.isArray(data.channels)).toBe(true);
|
expect(Array.isArray(data.channels)).toBe(true);
|
||||||
|
|
||||||
|
@ -166,7 +172,7 @@ describe.skipIf(skipIfNoEnv)('Slack Channels Integration Tests', () => {
|
||||||
expect(data.channels[0]).toHaveProperty('id');
|
expect(data.channels[0]).toHaveProperty('id');
|
||||||
expect(data.channels[0]).toHaveProperty('name');
|
expect(data.channels[0]).toHaveProperty('name');
|
||||||
// Should not have other properties (only id and name as requested)
|
// Should not have other properties (only id and name as requested)
|
||||||
expect(Object.keys(data.channels[0])).toEqual(['id', 'name']);
|
expect(Object.keys(data.channels[0]!)).toEqual(['id', 'name']);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up the secret if we created one
|
// Clean up the secret if we created one
|
||||||
|
@ -200,7 +206,7 @@ describe.skipIf(skipIfNoEnv)('Slack Channels Integration Tests', () => {
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
createdIntegrationIds.push(integration.id);
|
createdIntegrationIds.push(integration!.id);
|
||||||
|
|
||||||
await app.request('/api/v2/slack/channels', {
|
await app.request('/api/v2/slack/channels', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
@ -210,11 +216,11 @@ describe.skipIf(skipIfNoEnv)('Slack Channels Integration Tests', () => {
|
||||||
const [updated] = await db
|
const [updated] = await db
|
||||||
.select()
|
.select()
|
||||||
.from(slackIntegrations)
|
.from(slackIntegrations)
|
||||||
.where(eq(slackIntegrations.id, integration.id));
|
.where(eq(slackIntegrations.id, integration!.id));
|
||||||
|
|
||||||
expect(updated.lastUsedAt).toBeTruthy();
|
expect(updated!.lastUsedAt).toBeTruthy();
|
||||||
expect(new Date(updated.lastUsedAt!).getTime()).toBeGreaterThan(
|
expect(new Date(updated!.lastUsedAt!).getTime()).toBeGreaterThan(
|
||||||
new Date(integration.createdAt).getTime()
|
new Date(integration!.createdAt).getTime()
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -231,7 +237,12 @@ describe.skipIf(skipIfNoEnv)('Slack Channels Integration Tests', () => {
|
||||||
const testApp = new Hono();
|
const testApp = new Hono();
|
||||||
testApp.use('*', async (c, next) => {
|
testApp.use('*', async (c, next) => {
|
||||||
if (c.req.path.includes('/channels')) {
|
if (c.req.path.includes('/channels')) {
|
||||||
(c as Context).set('busterUser', { id: testUserId });
|
(c as Context).set('busterUser', {
|
||||||
|
id: testUserId,
|
||||||
|
name: 'Test User',
|
||||||
|
email: 'test@example.com',
|
||||||
|
avatarUrl: 'https://example.com/avatar.png',
|
||||||
|
});
|
||||||
(c as Context).set('organizationId', testOrganizationId);
|
(c as Context).set('organizationId', testOrganizationId);
|
||||||
}
|
}
|
||||||
await next();
|
await next();
|
||||||
|
@ -243,7 +254,7 @@ describe.skipIf(skipIfNoEnv)('Slack Channels Integration Tests', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.status).toBe(503);
|
expect(response.status).toBe(503);
|
||||||
const data = await response.json();
|
const data = (await response.json()) as SlackErrorResponse;
|
||||||
expect(data.error).toBe('Slack integration is not configured');
|
expect(data.error).toBe('Slack integration is not configured');
|
||||||
expect(data.code).toBe('INTEGRATION_NOT_CONFIGURED');
|
expect(data.code).toBe('INTEGRATION_NOT_CONFIGURED');
|
||||||
|
|
||||||
|
@ -270,14 +281,14 @@ describe.skipIf(skipIfNoEnv)('Slack Channels Integration Tests', () => {
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
createdIntegrationIds.push(integration.id);
|
createdIntegrationIds.push(integration!.id);
|
||||||
|
|
||||||
const response = await app.request('/api/v2/slack/channels', {
|
const response = await app.request('/api/v2/slack/channels', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.status).toBe(500);
|
expect(response.status).toBe(500);
|
||||||
const data = await response.json();
|
const data = (await response.json()) as SlackErrorResponse;
|
||||||
expect(data.error).toBe('Failed to retrieve authentication token');
|
expect(data.error).toBe('Failed to retrieve authentication token');
|
||||||
expect(data.code).toBe('TOKEN_RETRIEVAL_ERROR');
|
expect(data.code).toBe('TOKEN_RETRIEVAL_ERROR');
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
import { db, slackIntegrations } from '@buster/database';
|
import { db, slackIntegrations } from '@buster/database';
|
||||||
|
import type {
|
||||||
|
GetIntegrationResponse,
|
||||||
|
InitiateOAuthResponse,
|
||||||
|
RemoveIntegrationResponse,
|
||||||
|
SlackErrorResponse,
|
||||||
|
} from '@buster/server-shared/slack';
|
||||||
import { and, eq } from 'drizzle-orm';
|
import { and, eq } from 'drizzle-orm';
|
||||||
import { Hono } from 'hono';
|
import { Hono } from 'hono';
|
||||||
import type { Context } from 'hono';
|
import type { Context } from 'hono';
|
||||||
|
@ -110,7 +116,12 @@ describe.skipIf(skipIfNoEnv)('SlackHandler Integration Tests', () => {
|
||||||
// Set auth context for protected routes
|
// Set auth context for protected routes
|
||||||
const path = c.req.path;
|
const path = c.req.path;
|
||||||
if (path.includes('/auth/init') || path.includes('/integration')) {
|
if (path.includes('/auth/init') || path.includes('/integration')) {
|
||||||
(c as Context).set('busterUser', { id: testUserId });
|
(c as Context).set('busterUser', {
|
||||||
|
id: testUserId,
|
||||||
|
name: 'Test User',
|
||||||
|
email: 'test@example.com',
|
||||||
|
avatarUrl: 'https://example.com/avatar.png',
|
||||||
|
});
|
||||||
(c as Context).set('organizationId', testOrganizationId);
|
(c as Context).set('organizationId', testOrganizationId);
|
||||||
}
|
}
|
||||||
await next();
|
await next();
|
||||||
|
@ -145,7 +156,12 @@ describe.skipIf(skipIfNoEnv)('SlackHandler Integration Tests', () => {
|
||||||
const testApp = new Hono();
|
const testApp = new Hono();
|
||||||
testApp.use('*', async (c, next) => {
|
testApp.use('*', async (c, next) => {
|
||||||
if (c.req.path.includes('/auth/init')) {
|
if (c.req.path.includes('/auth/init')) {
|
||||||
(c as Context).set('busterUser', { id: testUserId });
|
(c as Context).set('busterUser', {
|
||||||
|
id: testUserId,
|
||||||
|
name: 'Test User',
|
||||||
|
email: 'test@example.com',
|
||||||
|
avatarUrl: 'https://example.com/avatar.png',
|
||||||
|
});
|
||||||
(c as Context).set('organizationId', testOrganizationId);
|
(c as Context).set('organizationId', testOrganizationId);
|
||||||
}
|
}
|
||||||
await next();
|
await next();
|
||||||
|
@ -161,7 +177,7 @@ describe.skipIf(skipIfNoEnv)('SlackHandler Integration Tests', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.status).toBe(503);
|
expect(response.status).toBe(503);
|
||||||
const data = await response.json();
|
const data = (await response.json()) as SlackErrorResponse;
|
||||||
expect(data.error).toBe('Slack integration is not enabled');
|
expect(data.error).toBe('Slack integration is not enabled');
|
||||||
expect(data.code).toBe('INTEGRATION_DISABLED');
|
expect(data.code).toBe('INTEGRATION_DISABLED');
|
||||||
|
|
||||||
|
@ -221,8 +237,8 @@ describe.skipIf(skipIfNoEnv)('SlackHandler Integration Tests', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
expect(response.status).toBe(200);
|
||||||
const data = await response.json();
|
const data = (await response.json()) as InitiateOAuthResponse;
|
||||||
expect(data.authUrl).toContain('https://slack.com/oauth');
|
expect(data.auth_url).toContain('https://slack.com/oauth');
|
||||||
expect(data.state).toBeTruthy();
|
expect(data.state).toBeTruthy();
|
||||||
|
|
||||||
// Verify pending integration was created in database
|
// Verify pending integration was created in database
|
||||||
|
@ -232,12 +248,12 @@ describe.skipIf(skipIfNoEnv)('SlackHandler Integration Tests', () => {
|
||||||
.where(eq(slackIntegrations.organizationId, testOrganizationId));
|
.where(eq(slackIntegrations.organizationId, testOrganizationId));
|
||||||
|
|
||||||
expect(pending).toBeTruthy();
|
expect(pending).toBeTruthy();
|
||||||
createdIntegrationIds.push(pending.id);
|
createdIntegrationIds.push(pending!.id);
|
||||||
|
|
||||||
expect(pending.status).toBe('pending');
|
expect(pending!.status).toBe('pending');
|
||||||
expect(pending.userId).toBe(testUserId);
|
expect(pending!.userId).toBe(testUserId);
|
||||||
expect(pending.oauthState).toBeTruthy();
|
expect(pending!.oauthState).toBeTruthy();
|
||||||
expect(pending.oauthMetadata).toMatchObject({
|
expect(pending!.oauthMetadata).toMatchObject({
|
||||||
returnUrl: '/dashboard',
|
returnUrl: '/dashboard',
|
||||||
source: 'settings',
|
source: 'settings',
|
||||||
projectId: '550e8400-e29b-41d4-a716-446655440000',
|
projectId: '550e8400-e29b-41d4-a716-446655440000',
|
||||||
|
@ -261,7 +277,7 @@ describe.skipIf(skipIfNoEnv)('SlackHandler Integration Tests', () => {
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
createdIntegrationIds.push(existing.id);
|
createdIntegrationIds.push(existing!.id);
|
||||||
|
|
||||||
const response = await app.request('/api/v2/slack/auth/init', {
|
const response = await app.request('/api/v2/slack/auth/init', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
@ -272,7 +288,7 @@ describe.skipIf(skipIfNoEnv)('SlackHandler Integration Tests', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.status).toBe(409);
|
expect(response.status).toBe(409);
|
||||||
const data = await response.json();
|
const data = (await response.json()) as SlackErrorResponse;
|
||||||
expect(data.error).toBe('Organization already has an active Slack integration');
|
expect(data.error).toBe('Organization already has an active Slack integration');
|
||||||
expect(data.code).toBe('INTEGRATION_EXISTS');
|
expect(data.code).toBe('INTEGRATION_EXISTS');
|
||||||
});
|
});
|
||||||
|
@ -317,7 +333,7 @@ describe.skipIf(skipIfNoEnv)('SlackHandler Integration Tests', () => {
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
createdIntegrationIds.push(pending.id);
|
createdIntegrationIds.push(pending!.id);
|
||||||
|
|
||||||
const response = await app.request(
|
const response = await app.request(
|
||||||
`/api/v2/slack/auth/callback?code=test-code&state=${testState}`,
|
`/api/v2/slack/auth/callback?code=test-code&state=${testState}`,
|
||||||
|
@ -333,14 +349,14 @@ describe.skipIf(skipIfNoEnv)('SlackHandler Integration Tests', () => {
|
||||||
const [activated] = await db
|
const [activated] = await db
|
||||||
.select()
|
.select()
|
||||||
.from(slackIntegrations)
|
.from(slackIntegrations)
|
||||||
.where(eq(slackIntegrations.id, pending.id));
|
.where(eq(slackIntegrations.id, pending!.id));
|
||||||
|
|
||||||
expect(activated.status).toBe('active');
|
expect(activated!.status).toBe('active');
|
||||||
expect(activated.teamName).toBe('Test Workspace');
|
expect(activated!.teamName).toBe('Test Workspace');
|
||||||
expect(activated.tokenVaultKey).toBe(`slack-token-${pending.id}`);
|
expect(activated!.tokenVaultKey).toBe(`slack-token-${pending!.id}`);
|
||||||
expect(activated.installedAt).toBeTruthy();
|
expect(activated!.installedAt).toBeTruthy();
|
||||||
expect(activated.oauthState).toBeNull();
|
expect(activated!.oauthState).toBeNull();
|
||||||
expect(activated.oauthExpiresAt).toBeNull();
|
expect(activated!.oauthExpiresAt).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle invalid state', async () => {
|
it('should handle invalid state', async () => {
|
||||||
|
@ -390,19 +406,19 @@ describe.skipIf(skipIfNoEnv)('SlackHandler Integration Tests', () => {
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
createdIntegrationIds.push(integration.id);
|
createdIntegrationIds.push(integration!.id);
|
||||||
|
|
||||||
const response = await app.request('/api/v2/slack/integration', {
|
const response = await app.request('/api/v2/slack/integration', {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
expect(response.status).toBe(200);
|
||||||
const data = await response.json();
|
const data = (await response.json()) as GetIntegrationResponse;
|
||||||
expect(data.connected).toBe(true);
|
expect(data.connected).toBe(true);
|
||||||
expect(data.integration).toBeDefined();
|
expect(data.integration).toBeDefined();
|
||||||
expect(data.integration.id).toBe(integration.id);
|
expect(data.integration!.id).toBe(integration!.id);
|
||||||
expect(data.integration.teamName).toBe('Status Test Workspace');
|
expect(data.integration!.team_name).toBe('Status Test Workspace');
|
||||||
expect(data.integration.teamDomain).toBe('status-test');
|
expect(data.integration!.team_domain).toBe('status-test');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return not connected when no integration exists', async () => {
|
it('should return not connected when no integration exists', async () => {
|
||||||
|
@ -416,7 +432,7 @@ describe.skipIf(skipIfNoEnv)('SlackHandler Integration Tests', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
expect(response.status).toBe(200);
|
||||||
const data = await response.json();
|
const data = (await response.json()) as GetIntegrationResponse;
|
||||||
expect(data.connected).toBe(false);
|
expect(data.connected).toBe(false);
|
||||||
expect(data.integration).toBeUndefined();
|
expect(data.integration).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
@ -459,14 +475,14 @@ describe.skipIf(skipIfNoEnv)('SlackHandler Integration Tests', () => {
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
createdIntegrationIds.push(integration.id);
|
createdIntegrationIds.push(integration!.id);
|
||||||
|
|
||||||
const response = await app.request('/api/v2/slack/integration', {
|
const response = await app.request('/api/v2/slack/integration', {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
expect(response.status).toBe(200);
|
||||||
const data = await response.json();
|
const data = (await response.json()) as RemoveIntegrationResponse;
|
||||||
expect(data.message).toBe('Slack integration removed successfully');
|
expect(data.message).toBe('Slack integration removed successfully');
|
||||||
|
|
||||||
// Wait a moment for the database to update
|
// Wait a moment for the database to update
|
||||||
|
@ -476,13 +492,13 @@ describe.skipIf(skipIfNoEnv)('SlackHandler Integration Tests', () => {
|
||||||
const removedList = await db
|
const removedList = await db
|
||||||
.select()
|
.select()
|
||||||
.from(slackIntegrations)
|
.from(slackIntegrations)
|
||||||
.where(eq(slackIntegrations.id, integration.id));
|
.where(eq(slackIntegrations.id, integration!.id));
|
||||||
|
|
||||||
expect(removedList.length).toBe(1);
|
expect(removedList.length).toBe(1);
|
||||||
const removed = removedList[0];
|
const removed = removedList[0];
|
||||||
expect(removed).toBeDefined();
|
expect(removed).toBeDefined();
|
||||||
expect(removed.status).toBe('revoked');
|
expect(removed!.status).toBe('revoked');
|
||||||
expect(removed.deletedAt).toBeTruthy();
|
expect(removed!.deletedAt).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle non-existent integration', async () => {
|
it('should handle non-existent integration', async () => {
|
||||||
|
@ -499,7 +515,7 @@ describe.skipIf(skipIfNoEnv)('SlackHandler Integration Tests', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(response.status).toBe(404);
|
expect(response.status).toBe(404);
|
||||||
const data = await response.json();
|
const data = (await response.json()) as SlackErrorResponse;
|
||||||
expect(data.error).toBe('No active Slack integration found');
|
expect(data.error).toBe('No active Slack integration found');
|
||||||
expect(data.code).toBe('INTEGRATION_NOT_FOUND');
|
expect(data.code).toBe('INTEGRATION_NOT_FOUND');
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import { getUserOrganizationId } from '@buster/database';
|
import { getUserOrganizationId } from '@buster/database';
|
||||||
|
import { SlackError } from '@buster/server-shared/slack';
|
||||||
import { HTTPException } from 'hono/http-exception';
|
import { HTTPException } from 'hono/http-exception';
|
||||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
import { SlackError, SlackHandler } from './handler';
|
import { SlackHandler } from './handler';
|
||||||
|
|
||||||
// Mock dependencies
|
// Mock dependencies
|
||||||
const mockSlackOAuthService = {
|
const mockSlackOAuthService = {
|
||||||
|
@ -82,7 +83,7 @@ describe('SlackHandler', () => {
|
||||||
// Mock getUserOrganizationId to return org info
|
// Mock getUserOrganizationId to return org info
|
||||||
vi.mocked(getUserOrganizationId).mockResolvedValue({
|
vi.mocked(getUserOrganizationId).mockResolvedValue({
|
||||||
organizationId: 'org-123',
|
organizationId: 'org-123',
|
||||||
userId: 'user-123',
|
role: 'owner',
|
||||||
});
|
});
|
||||||
|
|
||||||
const mockResult = {
|
const mockResult = {
|
||||||
|
@ -115,7 +116,7 @@ describe('SlackHandler', () => {
|
||||||
// Mock getUserOrganizationId to return org info
|
// Mock getUserOrganizationId to return org info
|
||||||
vi.mocked(getUserOrganizationId).mockResolvedValue({
|
vi.mocked(getUserOrganizationId).mockResolvedValue({
|
||||||
organizationId: 'org-123',
|
organizationId: 'org-123',
|
||||||
userId: 'user-123',
|
role: 'owner',
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.mocked(mockSlackOAuthService.initiateOAuth).mockRejectedValue(
|
vi.mocked(mockSlackOAuthService.initiateOAuth).mockRejectedValue(
|
||||||
|
@ -209,7 +210,7 @@ describe('SlackHandler', () => {
|
||||||
// Mock getUserOrganizationId to return org info
|
// Mock getUserOrganizationId to return org info
|
||||||
vi.mocked(getUserOrganizationId).mockResolvedValue({
|
vi.mocked(getUserOrganizationId).mockResolvedValue({
|
||||||
organizationId: 'org-123',
|
organizationId: 'org-123',
|
||||||
userId: 'user-123',
|
role: 'owner',
|
||||||
});
|
});
|
||||||
|
|
||||||
const mockStatus = {
|
const mockStatus = {
|
||||||
|
@ -249,7 +250,7 @@ describe('SlackHandler', () => {
|
||||||
// Mock getUserOrganizationId to return org info
|
// Mock getUserOrganizationId to return org info
|
||||||
vi.mocked(getUserOrganizationId).mockResolvedValue({
|
vi.mocked(getUserOrganizationId).mockResolvedValue({
|
||||||
organizationId: 'org-123',
|
organizationId: 'org-123',
|
||||||
userId: 'user-123',
|
role: 'owner',
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.mocked(mockSlackOAuthService.removeIntegration).mockResolvedValue({
|
vi.mocked(mockSlackOAuthService.removeIntegration).mockResolvedValue({
|
||||||
|
@ -273,7 +274,7 @@ describe('SlackHandler', () => {
|
||||||
// Mock getUserOrganizationId to return org info
|
// Mock getUserOrganizationId to return org info
|
||||||
vi.mocked(getUserOrganizationId).mockResolvedValue({
|
vi.mocked(getUserOrganizationId).mockResolvedValue({
|
||||||
organizationId: 'org-123',
|
organizationId: 'org-123',
|
||||||
userId: 'user-123',
|
role: 'owner',
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.mocked(mockSlackOAuthService.removeIntegration).mockResolvedValue({
|
vi.mocked(mockSlackOAuthService.removeIntegration).mockResolvedValue({
|
||||||
|
|
|
@ -136,12 +136,12 @@ describe.skipIf(skipIfNoDatabase)('Slack Helpers Database Integration Tests', ()
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
createdIntegrationIds.push(integration.id);
|
createdIntegrationIds.push(integration!.id);
|
||||||
|
|
||||||
const result = await slackHelpers.getActiveIntegration(testOrganizationId);
|
const result = await slackHelpers.getActiveIntegration(testOrganizationId);
|
||||||
|
|
||||||
expect(result).toBeTruthy();
|
expect(result).toBeTruthy();
|
||||||
expect(result?.id).toBe(integration.id);
|
expect(result?.id).toBe(integration!.id);
|
||||||
expect(result?.status).toBe('active');
|
expect(result?.status).toBe('active');
|
||||||
expect(result?.teamName).toBe('Test Workspace');
|
expect(result?.teamName).toBe('Test Workspace');
|
||||||
expect(result?.organizationId).toBe(testOrganizationId);
|
expect(result?.organizationId).toBe(testOrganizationId);
|
||||||
|
@ -163,7 +163,7 @@ describe.skipIf(skipIfNoDatabase)('Slack Helpers Database Integration Tests', ()
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
createdIntegrationIds.push(integration.id);
|
createdIntegrationIds.push(integration!.id);
|
||||||
|
|
||||||
const result = await slackHelpers.getActiveIntegration(testOrganizationId);
|
const result = await slackHelpers.getActiveIntegration(testOrganizationId);
|
||||||
expect(result).toBeNull();
|
expect(result).toBeNull();
|
||||||
|
@ -192,14 +192,14 @@ describe.skipIf(skipIfNoDatabase)('Slack Helpers Database Integration Tests', ()
|
||||||
.where(eq(slackIntegrations.id, integrationId));
|
.where(eq(slackIntegrations.id, integrationId));
|
||||||
|
|
||||||
expect(created).toBeTruthy();
|
expect(created).toBeTruthy();
|
||||||
expect(created.organizationId).toBe(testOrganizationId);
|
expect(created!.organizationId).toBe(testOrganizationId);
|
||||||
expect(created.userId).toBe(testUserId);
|
expect(created!.userId).toBe(testUserId);
|
||||||
expect(created.status).toBe('pending');
|
expect(created!.status).toBe('pending');
|
||||||
expect(created.oauthState).toBe(testIds.oauthState);
|
expect(created!.oauthState).toBe(testIds.oauthState);
|
||||||
expect(created.oauthMetadata).toEqual(metadata);
|
expect(created!.oauthMetadata).toEqual(metadata);
|
||||||
|
|
||||||
// Check expiry is set to ~15 minutes
|
// Check expiry is set to ~15 minutes
|
||||||
const expiryTime = new Date(created.oauthExpiresAt!).getTime();
|
const expiryTime = new Date(created!.oauthExpiresAt!).getTime();
|
||||||
const expectedExpiry = Date.now() + 15 * 60 * 1000;
|
const expectedExpiry = Date.now() + 15 * 60 * 1000;
|
||||||
expect(Math.abs(expiryTime - expectedExpiry)).toBeLessThan(30000); // Within 30 seconds
|
expect(Math.abs(expiryTime - expectedExpiry)).toBeLessThan(30000); // Within 30 seconds
|
||||||
});
|
});
|
||||||
|
@ -219,7 +219,7 @@ describe.skipIf(skipIfNoDatabase)('Slack Helpers Database Integration Tests', ()
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
createdIntegrationIds.push(activeIntegration.id);
|
createdIntegrationIds.push(activeIntegration!.id);
|
||||||
|
|
||||||
// Try to create a new pending integration
|
// Try to create a new pending integration
|
||||||
await expect(
|
await expect(
|
||||||
|
@ -248,17 +248,17 @@ describe.skipIf(skipIfNoDatabase)('Slack Helpers Database Integration Tests', ()
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
createdIntegrationIds.push(pending.id);
|
createdIntegrationIds.push(pending!.id);
|
||||||
|
|
||||||
// Update it with OAuth response data
|
// Update it with OAuth response data
|
||||||
await slackHelpers.updateIntegrationAfterOAuth(pending.id, {
|
await slackHelpers.updateIntegrationAfterOAuth(pending!.id, {
|
||||||
teamId: testIds.teamId,
|
teamId: testIds.teamId,
|
||||||
teamName: 'OAuth Workspace',
|
teamName: 'OAuth Workspace',
|
||||||
teamDomain: 'oauth-workspace',
|
teamDomain: 'oauth-workspace',
|
||||||
enterpriseId: testIds.enterpriseId,
|
enterpriseId: testIds.enterpriseId,
|
||||||
botUserId: testIds.botUserId,
|
botUserId: testIds.botUserId,
|
||||||
scope: 'channels:read chat:write channels:join',
|
scope: 'channels:read chat:write channels:join',
|
||||||
tokenVaultKey: `slack-token-${pending.id}`,
|
tokenVaultKey: `slack-token-${pending!.id}`,
|
||||||
installedBySlackUserId: 'U11111',
|
installedBySlackUserId: 'U11111',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -266,20 +266,20 @@ describe.skipIf(skipIfNoDatabase)('Slack Helpers Database Integration Tests', ()
|
||||||
const [updated] = await db
|
const [updated] = await db
|
||||||
.select()
|
.select()
|
||||||
.from(slackIntegrations)
|
.from(slackIntegrations)
|
||||||
.where(eq(slackIntegrations.id, pending.id));
|
.where(eq(slackIntegrations.id, pending!.id));
|
||||||
|
|
||||||
expect(updated.status).toBe('active');
|
expect(updated!.status).toBe('active');
|
||||||
expect(updated.teamId).toBe(testIds.teamId);
|
expect(updated!.teamId).toBe(testIds.teamId);
|
||||||
expect(updated.teamName).toBe('OAuth Workspace');
|
expect(updated!.teamName).toBe('OAuth Workspace');
|
||||||
expect(updated.teamDomain).toBe('oauth-workspace');
|
expect(updated!.teamDomain).toBe('oauth-workspace');
|
||||||
expect(updated.enterpriseId).toBe(testIds.enterpriseId);
|
expect(updated!.enterpriseId).toBe(testIds.enterpriseId);
|
||||||
expect(updated.botUserId).toBe(testIds.botUserId);
|
expect(updated!.botUserId).toBe(testIds.botUserId);
|
||||||
expect(updated.scope).toBe('channels:read chat:write channels:join');
|
expect(updated!.scope).toBe('channels:read chat:write channels:join');
|
||||||
expect(updated.tokenVaultKey).toBe(`slack-token-${pending.id}`);
|
expect(updated!.tokenVaultKey).toBe(`slack-token-${pending!.id}`);
|
||||||
expect(updated.installedBySlackUserId).toBe('U11111');
|
expect(updated!.installedBySlackUserId).toBe('U11111');
|
||||||
expect(updated.installedAt).toBeTruthy();
|
expect(updated!.installedAt).toBeTruthy();
|
||||||
expect(updated.oauthState).toBeNull();
|
expect(updated!.oauthState).toBeNull();
|
||||||
expect(updated.oauthExpiresAt).toBeNull();
|
expect(updated!.oauthExpiresAt).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -302,7 +302,7 @@ describe.skipIf(skipIfNoDatabase)('Slack Helpers Database Integration Tests', ()
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
createdIntegrationIds.push(integration.id);
|
createdIntegrationIds.push(integration!.id);
|
||||||
|
|
||||||
// Update default channel
|
// Update default channel
|
||||||
const defaultChannel = {
|
const defaultChannel = {
|
||||||
|
@ -310,17 +310,17 @@ describe.skipIf(skipIfNoDatabase)('Slack Helpers Database Integration Tests', ()
|
||||||
id: 'C12345',
|
id: 'C12345',
|
||||||
};
|
};
|
||||||
|
|
||||||
await slackHelpers.updateDefaultChannel(integration.id, defaultChannel);
|
await slackHelpers.updateDefaultChannel(integration!.id, defaultChannel);
|
||||||
|
|
||||||
// Verify the update
|
// Verify the update
|
||||||
const [updated] = await db
|
const [updated] = await db
|
||||||
.select()
|
.select()
|
||||||
.from(slackIntegrations)
|
.from(slackIntegrations)
|
||||||
.where(eq(slackIntegrations.id, integration.id));
|
.where(eq(slackIntegrations.id, integration!.id));
|
||||||
|
|
||||||
expect(updated.defaultChannel).toEqual(defaultChannel);
|
expect(updated!.defaultChannel).toEqual(defaultChannel);
|
||||||
expect(new Date(updated.updatedAt).getTime()).toBeGreaterThan(
|
expect(new Date(updated!.updatedAt).getTime()).toBeGreaterThan(
|
||||||
new Date(updated.createdAt).getTime()
|
new Date(updated!.createdAt).getTime()
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -344,21 +344,21 @@ describe.skipIf(skipIfNoDatabase)('Slack Helpers Database Integration Tests', ()
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
createdIntegrationIds.push(integration.id);
|
createdIntegrationIds.push(integration!.id);
|
||||||
|
|
||||||
// Soft delete it
|
// Soft delete it
|
||||||
await slackHelpers.softDeleteIntegration(integration.id);
|
await slackHelpers.softDeleteIntegration(integration!.id);
|
||||||
|
|
||||||
// Verify it was soft deleted
|
// Verify it was soft deleted
|
||||||
const [deleted] = await db
|
const [deleted] = await db
|
||||||
.select()
|
.select()
|
||||||
.from(slackIntegrations)
|
.from(slackIntegrations)
|
||||||
.where(eq(slackIntegrations.id, integration.id));
|
.where(eq(slackIntegrations.id, integration!.id));
|
||||||
|
|
||||||
expect(deleted.status).toBe('revoked');
|
expect(deleted!.status).toBe('revoked');
|
||||||
expect(deleted.deletedAt).toBeTruthy();
|
expect(deleted!.deletedAt).toBeTruthy();
|
||||||
expect(new Date(deleted.updatedAt).getTime()).toBeGreaterThan(
|
expect(new Date(deleted!.updatedAt).getTime()).toBeGreaterThan(
|
||||||
new Date(deleted.createdAt).getTime()
|
new Date(deleted!.createdAt).getTime()
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -382,23 +382,23 @@ describe.skipIf(skipIfNoDatabase)('Slack Helpers Database Integration Tests', ()
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
createdIntegrationIds.push(integration.id);
|
createdIntegrationIds.push(integration!.id);
|
||||||
|
|
||||||
// Wait a bit to ensure timestamp difference
|
// Wait a bit to ensure timestamp difference
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
|
|
||||||
// Update last used
|
// Update last used
|
||||||
await slackHelpers.updateLastUsedAt(integration.id);
|
await slackHelpers.updateLastUsedAt(integration!.id);
|
||||||
|
|
||||||
// Verify the update
|
// Verify the update
|
||||||
const [updated] = await db
|
const [updated] = await db
|
||||||
.select()
|
.select()
|
||||||
.from(slackIntegrations)
|
.from(slackIntegrations)
|
||||||
.where(eq(slackIntegrations.id, integration.id));
|
.where(eq(slackIntegrations.id, integration!.id));
|
||||||
|
|
||||||
expect(updated.lastUsedAt).toBeTruthy();
|
expect(updated!.lastUsedAt).toBeTruthy();
|
||||||
expect(new Date(updated.lastUsedAt!).getTime()).toBeGreaterThan(
|
expect(new Date(updated!.lastUsedAt!).getTime()).toBeGreaterThan(
|
||||||
new Date(updated.createdAt).getTime()
|
new Date(updated!.createdAt).getTime()
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -424,7 +424,7 @@ describe.skipIf(skipIfNoDatabase)('Slack Helpers Database Integration Tests', ()
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
createdIntegrationIds.push(integration.id);
|
createdIntegrationIds.push(integration!.id);
|
||||||
|
|
||||||
const result = await slackHelpers.hasActiveIntegration(testOrganizationId);
|
const result = await slackHelpers.hasActiveIntegration(testOrganizationId);
|
||||||
expect(result).toBe(true);
|
expect(result).toBe(true);
|
||||||
|
@ -446,12 +446,12 @@ describe.skipIf(skipIfNoDatabase)('Slack Helpers Database Integration Tests', ()
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
createdIntegrationIds.push(integration.id);
|
createdIntegrationIds.push(integration!.id);
|
||||||
|
|
||||||
const result = await slackHelpers.getPendingIntegrationByState(testIds.oauthState);
|
const result = await slackHelpers.getPendingIntegrationByState(testIds.oauthState);
|
||||||
|
|
||||||
expect(result).toBeTruthy();
|
expect(result).toBeTruthy();
|
||||||
expect(result?.id).toBe(integration.id);
|
expect(result?.id).toBe(integration!.id);
|
||||||
expect(result?.oauthState).toBe(testIds.oauthState);
|
expect(result?.oauthState).toBe(testIds.oauthState);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -469,7 +469,7 @@ describe.skipIf(skipIfNoDatabase)('Slack Helpers Database Integration Tests', ()
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
createdIntegrationIds.push(integration.id);
|
createdIntegrationIds.push(integration!.id);
|
||||||
|
|
||||||
const result = await slackHelpers.getPendingIntegrationByState(testIds.oauthState);
|
const result = await slackHelpers.getPendingIntegrationByState(testIds.oauthState);
|
||||||
expect(result).toBeNull();
|
expect(result).toBeNull();
|
||||||
|
@ -505,7 +505,7 @@ describe.skipIf(skipIfNoDatabase)('Slack Helpers Database Integration Tests', ()
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
createdIntegrationIds.push(valid.id); // Only track valid one since expired will be deleted
|
createdIntegrationIds.push(valid!.id); // Only track valid one since expired will be deleted
|
||||||
|
|
||||||
// Run cleanup
|
// Run cleanup
|
||||||
const deletedCount = await slackHelpers.cleanupExpiredPendingIntegrations();
|
const deletedCount = await slackHelpers.cleanupExpiredPendingIntegrations();
|
||||||
|
@ -516,7 +516,7 @@ describe.skipIf(skipIfNoDatabase)('Slack Helpers Database Integration Tests', ()
|
||||||
const [expiredCheck] = await db
|
const [expiredCheck] = await db
|
||||||
.select()
|
.select()
|
||||||
.from(slackIntegrations)
|
.from(slackIntegrations)
|
||||||
.where(eq(slackIntegrations.id, expired.id));
|
.where(eq(slackIntegrations.id, expired!.id));
|
||||||
|
|
||||||
expect(expiredCheck).toBeUndefined();
|
expect(expiredCheck).toBeUndefined();
|
||||||
|
|
||||||
|
@ -524,7 +524,7 @@ describe.skipIf(skipIfNoDatabase)('Slack Helpers Database Integration Tests', ()
|
||||||
const [validCheck] = await db
|
const [validCheck] = await db
|
||||||
.select()
|
.select()
|
||||||
.from(slackIntegrations)
|
.from(slackIntegrations)
|
||||||
.where(eq(slackIntegrations.id, valid.id));
|
.where(eq(slackIntegrations.id, valid!.id));
|
||||||
|
|
||||||
expect(validCheck).toBeTruthy();
|
expect(validCheck).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
|
@ -138,12 +138,12 @@ describe.skipIf(skipIfNoEnv)('SlackOAuthService Integration Tests', () => {
|
||||||
.where(eq(slackIntegrations.organizationId, testOrganizationId));
|
.where(eq(slackIntegrations.organizationId, testOrganizationId));
|
||||||
|
|
||||||
expect(pending).toBeTruthy();
|
expect(pending).toBeTruthy();
|
||||||
createdIntegrationIds.push(pending.id);
|
createdIntegrationIds.push(pending!.id);
|
||||||
|
|
||||||
expect(pending.status).toBe('pending');
|
expect(pending!.status).toBe('pending');
|
||||||
expect(pending.userId).toBe(testUserId);
|
expect(pending!.userId).toBe(testUserId);
|
||||||
expect(pending.oauthState).toBeTruthy();
|
expect(pending!.oauthState).toBeTruthy();
|
||||||
expect(pending.oauthMetadata).toMatchObject({
|
expect(pending!.oauthMetadata).toMatchObject({
|
||||||
returnUrl: '/dashboard',
|
returnUrl: '/dashboard',
|
||||||
source: 'settings',
|
source: 'settings',
|
||||||
projectId: '550e8400-e29b-41d4-a716-446655440000',
|
projectId: '550e8400-e29b-41d4-a716-446655440000',
|
||||||
|
@ -168,7 +168,7 @@ describe.skipIf(skipIfNoEnv)('SlackOAuthService Integration Tests', () => {
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
createdIntegrationIds.push(existing.id);
|
createdIntegrationIds.push(existing!.id);
|
||||||
|
|
||||||
// Try to initiate OAuth again
|
// Try to initiate OAuth again
|
||||||
await expect(
|
await expect(
|
||||||
|
@ -202,9 +202,9 @@ describe.skipIf(skipIfNoEnv)('SlackOAuthService Integration Tests', () => {
|
||||||
source: 'onboarding',
|
source: 'onboarding',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.returning();
|
.returning()!;
|
||||||
|
|
||||||
createdIntegrationIds.push(pending.id);
|
createdIntegrationIds.push(pending!.id);
|
||||||
|
|
||||||
const testIds = generateTestIds();
|
const testIds = generateTestIds();
|
||||||
// Mock the auth service to return specific state
|
// Mock the auth service to return specific state
|
||||||
|
@ -227,7 +227,7 @@ describe.skipIf(skipIfNoEnv)('SlackOAuthService Integration Tests', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result.success).toBe(true);
|
expect(result.success).toBe(true);
|
||||||
expect(result.integrationId).toBe(pending.id);
|
expect(result.integrationId).toBe(pending!.id);
|
||||||
expect(result.teamName).toBe('Callback Workspace');
|
expect(result.teamName).toBe('Callback Workspace');
|
||||||
expect(result.metadata).toMatchObject({
|
expect(result.metadata).toMatchObject({
|
||||||
returnUrl: '/settings',
|
returnUrl: '/settings',
|
||||||
|
@ -241,22 +241,22 @@ describe.skipIf(skipIfNoEnv)('SlackOAuthService Integration Tests', () => {
|
||||||
const activatedList = await db
|
const activatedList = await db
|
||||||
.select()
|
.select()
|
||||||
.from(slackIntegrations)
|
.from(slackIntegrations)
|
||||||
.where(eq(slackIntegrations.id, pending.id));
|
.where(eq(slackIntegrations.id, pending!.id));
|
||||||
|
|
||||||
expect(activatedList.length).toBe(1);
|
expect(activatedList.length).toBe(1);
|
||||||
const activated = activatedList[0];
|
const activated = activatedList[0];
|
||||||
expect(activated).toBeDefined();
|
expect(activated).toBeDefined();
|
||||||
expect(activated.status).toBe('active');
|
expect(activated!.status).toBe('active');
|
||||||
expect(activated.teamId).toBe(testIds.teamId);
|
expect(activated!.teamId).toBe(testIds.teamId);
|
||||||
expect(activated.teamName).toBe('Callback Workspace');
|
expect(activated!.teamName).toBe('Callback Workspace');
|
||||||
expect(activated.teamDomain).toBe('callback-workspace');
|
expect(activated!.teamDomain).toBe('callback-workspace');
|
||||||
expect(activated.botUserId).toBe(testIds.botUserId);
|
expect(activated!.botUserId).toBe(testIds.botUserId);
|
||||||
expect(activated.scope).toBe('channels:read,chat:write,channels:join');
|
expect(activated!.scope).toBe('channels:read,chat:write,channels:join');
|
||||||
expect(activated.tokenVaultKey).toBe(`slack-token-${pending.id}`);
|
expect(activated!.tokenVaultKey).toBe(`slack-token-${pending!.id}`);
|
||||||
expect(activated.installedBySlackUserId).toBe('U88888');
|
expect(activated!.installedBySlackUserId).toBe('U88888');
|
||||||
expect(activated.installedAt).toBeTruthy();
|
expect(activated!.installedAt).toBeTruthy();
|
||||||
expect(activated.oauthState).toBeNull();
|
expect(activated!.oauthState).toBeNull();
|
||||||
expect(activated.oauthExpiresAt).toBeNull();
|
expect(activated!.oauthExpiresAt).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle invalid state', async () => {
|
it('should handle invalid state', async () => {
|
||||||
|
@ -299,13 +299,13 @@ describe.skipIf(skipIfNoEnv)('SlackOAuthService Integration Tests', () => {
|
||||||
|
|
||||||
expect(result.success).toBe(false);
|
expect(result.success).toBe(false);
|
||||||
expect(result.error).toBe('Failed to exchange code for token');
|
expect(result.error).toBe('Failed to exchange code for token');
|
||||||
expect(result.integrationId).toBe(pending.id);
|
expect(result.integrationId).toBe(pending!.id);
|
||||||
|
|
||||||
// Verify the pending integration was deleted (to allow retry)
|
// Verify the pending integration was deleted (to allow retry)
|
||||||
const integrations = await db
|
const integrations = await db
|
||||||
.select()
|
.select()
|
||||||
.from(slackIntegrations)
|
.from(slackIntegrations)
|
||||||
.where(eq(slackIntegrations.id, pending.id));
|
.where(eq(slackIntegrations.id, pending!.id));
|
||||||
|
|
||||||
expect(integrations.length).toBe(0);
|
expect(integrations.length).toBe(0);
|
||||||
});
|
});
|
||||||
|
@ -332,13 +332,13 @@ describe.skipIf(skipIfNoEnv)('SlackOAuthService Integration Tests', () => {
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
createdIntegrationIds.push(integration.id);
|
createdIntegrationIds.push(integration!.id);
|
||||||
|
|
||||||
const status = await service.getIntegrationStatus(testOrganizationId);
|
const status = await service.getIntegrationStatus(testOrganizationId);
|
||||||
|
|
||||||
expect(status.connected).toBe(true);
|
expect(status.connected).toBe(true);
|
||||||
expect(status.integration).toBeDefined();
|
expect(status.integration).toBeDefined();
|
||||||
expect(status.integration!.id).toBe(integration.id);
|
expect(status.integration!.id).toBe(integration!.id);
|
||||||
expect(status.integration!.teamName).toBe('Status Test Workspace');
|
expect(status.integration!.teamName).toBe('Status Test Workspace');
|
||||||
expect(status.integration!.teamDomain).toBe('status-test');
|
expect(status.integration!.teamDomain).toBe('status-test');
|
||||||
expect(status.integration!.installedAt).toContain('2024-01-01');
|
expect(status.integration!.installedAt).toContain('2024-01-01');
|
||||||
|
@ -380,7 +380,7 @@ describe.skipIf(skipIfNoEnv)('SlackOAuthService Integration Tests', () => {
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
// Store the ID immediately
|
// Store the ID immediately
|
||||||
const integrationId = integration.id;
|
const integrationId = integration!.id;
|
||||||
createdIntegrationIds.push(integrationId);
|
createdIntegrationIds.push(integrationId);
|
||||||
|
|
||||||
// Verify it was created
|
// Verify it was created
|
||||||
|
@ -390,7 +390,7 @@ describe.skipIf(skipIfNoEnv)('SlackOAuthService Integration Tests', () => {
|
||||||
.where(eq(slackIntegrations.id, integrationId));
|
.where(eq(slackIntegrations.id, integrationId));
|
||||||
|
|
||||||
expect(created).toBeDefined();
|
expect(created).toBeDefined();
|
||||||
expect(created.status).toBe('active');
|
expect(created!.status).toBe('active');
|
||||||
|
|
||||||
// The service will try to delete the token from vault
|
// The service will try to delete the token from vault
|
||||||
// In integration tests, we let it run naturally
|
// In integration tests, we let it run naturally
|
||||||
|
@ -410,8 +410,8 @@ describe.skipIf(skipIfNoEnv)('SlackOAuthService Integration Tests', () => {
|
||||||
expect(removedIntegrations.length).toBe(1);
|
expect(removedIntegrations.length).toBe(1);
|
||||||
const removed = removedIntegrations[0];
|
const removed = removedIntegrations[0];
|
||||||
expect(removed).toBeDefined();
|
expect(removed).toBeDefined();
|
||||||
expect(removed.status).toBe('revoked');
|
expect(removed!.status).toBe('revoked');
|
||||||
expect(removed.deletedAt).toBeTruthy();
|
expect(removed!.deletedAt).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle non-existent integration', async () => {
|
it('should handle non-existent integration', async () => {
|
||||||
|
|
|
@ -136,7 +136,7 @@ describe.skipIf(skipIfNoEnv)('Token Storage Integration Tests', () => {
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
createdIntegrationIds.push(integration.id);
|
createdIntegrationIds.push(integration!.id);
|
||||||
|
|
||||||
// Retrieve state
|
// Retrieve state
|
||||||
const stateData = await storage.getState(testState);
|
const stateData = await storage.getState(testState);
|
||||||
|
@ -167,7 +167,7 @@ describe.skipIf(skipIfNoEnv)('Token Storage Integration Tests', () => {
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
createdIntegrationIds.push(integration.id);
|
createdIntegrationIds.push(integration!.id);
|
||||||
|
|
||||||
// Should return null for expired state
|
// Should return null for expired state
|
||||||
const stateData = await storage.getState(testState);
|
const stateData = await storage.getState(testState);
|
||||||
|
|
|
@ -6,5 +6,14 @@
|
||||||
"tsBuildInfoFile": "dist/.cache/tsbuildinfo.json"
|
"tsBuildInfoFile": "dist/.cache/tsbuildinfo.json"
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["src"],
|
||||||
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.test.tsx", "**/*.spec.ts"]
|
"exclude": ["node_modules", "dist", "**/*.test.tsx", "**/*.test.ts"],
|
||||||
|
"references": [
|
||||||
|
{ "path": "../../packages/access-controls" },
|
||||||
|
{ "path": "../../packages/ai" },
|
||||||
|
{ "path": "../../packages/database" },
|
||||||
|
{ "path": "../../packages/server-shared" },
|
||||||
|
{ "path": "../../packages/slack" },
|
||||||
|
{ "path": "../../packages/test-utils" },
|
||||||
|
{ "path": "../../packages/vitest-config" }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,5 +7,14 @@
|
||||||
"tsBuildInfoFile": "dist/.cache/tsbuildinfo.json"
|
"tsBuildInfoFile": "dist/.cache/tsbuildinfo.json"
|
||||||
},
|
},
|
||||||
"include": ["src/**/*", "tests/**/*", "env.d.ts"],
|
"include": ["src/**/*", "tests/**/*", "env.d.ts"],
|
||||||
"exclude": ["node_modules", "dist"]
|
"exclude": ["node_modules", "dist"],
|
||||||
|
"references": [
|
||||||
|
{ "path": "../../packages/access-controls" },
|
||||||
|
{ "path": "../../packages/ai" },
|
||||||
|
{ "path": "../../packages/database" },
|
||||||
|
{ "path": "../../packages/server-shared" },
|
||||||
|
{ "path": "../../packages/slack" },
|
||||||
|
{ "path": "../../packages/test-utils" },
|
||||||
|
{ "path": "../../packages/vitest-config" }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "bundler"
|
"moduleResolution": "bundler"
|
||||||
},
|
},
|
||||||
"include": ["src/**/*", "env.d.ts"],
|
"include": ["src*", "env.d.ts"],
|
||||||
"exclude": ["node_modules", "dist", "tests", "**/*.test.ts", "**/*.spec.ts"]
|
"exclude": ["node_modules", "dist", "tests", "**/*.test.tsx", "**/*.test.ts", "**/*.spec.ts"],
|
||||||
|
"references": [{ "path": "../database" }, { "path": "../vitest-config" }]
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,15 @@
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"rootDir": "src"
|
"rootDir": "src"
|
||||||
},
|
},
|
||||||
"include": ["src/**/*", "env.d.ts"],
|
"include": ["src*", "env.d.ts"],
|
||||||
"exclude": ["node_modules", "dist"]
|
"exclude": ["node_modules", "dist"],
|
||||||
|
"references": [
|
||||||
|
{ "path": "../access-controls" },
|
||||||
|
{ "path": "../data-source" },
|
||||||
|
{ "path": "../database" },
|
||||||
|
{ "path": "../server-shared" },
|
||||||
|
{ "path": "../stored-values" },
|
||||||
|
{ "path": "../test-utils" },
|
||||||
|
{ "path": "../vitest-config" }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,5 +13,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["src/**/*", "env.d.ts"],
|
"include": ["src/**/*", "env.d.ts"],
|
||||||
"exclude": ["node_modules", "dist", "tests", "**/*.test.ts", "**/*.spec.ts"]
|
"exclude": ["node_modules", "dist", "tests", "**/*.test.ts", "**/*.spec.ts"],
|
||||||
|
"references": [{ "path": "../vitest-config" }]
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,8 +34,6 @@ function validateEnvironment(): string {
|
||||||
}
|
}
|
||||||
|
|
||||||
return connectionString;
|
return connectionString;
|
||||||
|
|
||||||
return connectionString;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the database pool
|
// Initialize the database pool
|
||||||
|
|
|
@ -6,5 +6,6 @@
|
||||||
"rootDir": "src"
|
"rootDir": "src"
|
||||||
},
|
},
|
||||||
"include": ["src/**/*", "env.d.ts"],
|
"include": ["src/**/*", "env.d.ts"],
|
||||||
"exclude": ["node_modules", "dist", "tests", "**/*.test.ts", "**/*.spec.ts"]
|
"exclude": ["node_modules", "dist", "tests", "**/*.test.ts", "**/*.spec.ts"],
|
||||||
|
"references": [{ "path": "../vitest-config" }]
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"rootDir": "src"
|
"rootDir": "src"
|
||||||
},
|
},
|
||||||
"include": ["src/**/*", "env.d.ts"],
|
"include": ["src*", "env.d.ts"],
|
||||||
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
|
"exclude": ["node_modules", "dist", "**/*.test.tsx", "**/*.test.ts", "**/*.spec.ts"],
|
||||||
|
"references": [{ "path": "../vitest-config" }]
|
||||||
}
|
}
|
||||||
|
|
|
@ -207,12 +207,12 @@ describe('ReasoningMessageSchema', () => {
|
||||||
expect(result.data.type).toBe('files');
|
expect(result.data.type).toBe('files');
|
||||||
if (result.data.type === 'files') {
|
if (result.data.type === 'files') {
|
||||||
expect(result.data.file_ids).toHaveLength(2);
|
expect(result.data.file_ids).toHaveLength(2);
|
||||||
expect(result.data.files['file-1'].file_type).toBe('metric');
|
expect(result.data.files['file-1']?.file_type).toBe('metric');
|
||||||
expect(result.data.files['file-1'].file.modified).toEqual([
|
expect(result.data.files['file-1']?.file?.modified).toEqual([
|
||||||
[0, 10],
|
[0, 10],
|
||||||
[20, 30],
|
[20, 30],
|
||||||
]);
|
]);
|
||||||
expect(result.data.files['file-2'].file.modified).toBeUndefined();
|
expect(result.data.files['file-2']?.file?.modified).toBeUndefined();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -258,9 +258,9 @@ describe('ReasoningMessageSchema', () => {
|
||||||
expect(result.data.type).toBe('pills');
|
expect(result.data.type).toBe('pills');
|
||||||
if (result.data.type === 'pills') {
|
if (result.data.type === 'pills') {
|
||||||
expect(result.data.pill_containers).toHaveLength(2);
|
expect(result.data.pill_containers).toHaveLength(2);
|
||||||
expect(result.data.pill_containers[0].pills).toHaveLength(2);
|
expect(result.data.pill_containers?.[0]?.pills).toHaveLength(2);
|
||||||
expect(result.data.pill_containers[0].pills[0].type).toBe('metric');
|
expect(result.data.pill_containers?.[0]?.pills?.[0]?.type).toBe('metric');
|
||||||
expect(result.data.pill_containers[1].pills[0].text).toBe('Sales Overview');
|
expect(result.data.pill_containers?.[1]?.pills?.[0]?.text).toBe('Sales Overview');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -193,8 +193,8 @@ describe('chartConfigProps', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check that defaults are applied to column settings
|
// Check that defaults are applied to column settings
|
||||||
expect(result.columnSettings.revenue.lineWidth).toBe(2);
|
expect(result.columnSettings.revenue?.lineWidth).toBe(2);
|
||||||
expect(result.columnSettings.profit.columnVisualization).toBe('bar');
|
expect(result.columnSettings.profit?.columnVisualization).toBe('bar');
|
||||||
|
|
||||||
expect(result.columnLabelFormats.revenue).toMatchObject({
|
expect(result.columnLabelFormats.revenue).toMatchObject({
|
||||||
prefix: '$',
|
prefix: '$',
|
||||||
|
|
|
@ -312,7 +312,10 @@ describe('Helper function edge cases', () => {
|
||||||
it('should handle null and undefined defaults correctly', () => {
|
it('should handle null and undefined defaults correctly', () => {
|
||||||
const NullDefaultsSchema = z.object({
|
const NullDefaultsSchema = z.object({
|
||||||
nullable: z.string().nullable().default(null),
|
nullable: z.string().nullable().default(null),
|
||||||
optional: z.string().optional().default(undefined),
|
optional: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.default(undefined as any),
|
||||||
emptyString: z.string().default(''),
|
emptyString: z.string().default(''),
|
||||||
zero: z.number().default(0),
|
zero: z.number().default(0),
|
||||||
false: z.boolean().default(false),
|
false: z.boolean().default(false),
|
||||||
|
|
|
@ -191,8 +191,8 @@ describe('ShareConfigSchema', () => {
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
expect(result.data.individual_permissions).toHaveLength(2);
|
expect(result.data.individual_permissions).toHaveLength(2);
|
||||||
expect(result.data.individual_permissions?.[0].email).toBe('user1@example.com');
|
expect(result.data.individual_permissions?.[0]?.email).toBe('user1@example.com');
|
||||||
expect(result.data.individual_permissions?.[0].role).toBe('canEdit');
|
expect(result.data.individual_permissions?.[0]?.role).toBe('canEdit');
|
||||||
expect(result.data.publicly_accessible).toBe(true);
|
expect(result.data.publicly_accessible).toBe(true);
|
||||||
expect(result.data.permission).toBe('owner');
|
expect(result.data.permission).toBe('owner');
|
||||||
}
|
}
|
||||||
|
@ -369,11 +369,11 @@ describe('ShareConfigSchema', () => {
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
expect(result.data.individual_permissions).toHaveLength(4);
|
expect(result.data.individual_permissions).toHaveLength(4);
|
||||||
expect(result.data.individual_permissions?.[0].role).toBe('owner');
|
expect(result.data.individual_permissions?.[0]?.role).toBe('owner');
|
||||||
expect(result.data.individual_permissions?.[1].role).toBe('canEdit');
|
expect(result.data.individual_permissions?.[1]?.role).toBe('canEdit');
|
||||||
expect(result.data.individual_permissions?.[2].role).toBe('canView');
|
expect(result.data.individual_permissions?.[2]?.role).toBe('canView');
|
||||||
expect(result.data.individual_permissions?.[3].role).toBe('canFilter');
|
expect(result.data.individual_permissions?.[3]?.role).toBe('canFilter');
|
||||||
expect(result.data.individual_permissions?.[2].name).toBeUndefined();
|
expect(result.data.individual_permissions?.[2]?.name).toBeUndefined();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,6 @@
|
||||||
"tsBuildInfoFile": "dist/.cache/tsbuildinfo.json"
|
"tsBuildInfoFile": "dist/.cache/tsbuildinfo.json"
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["src"],
|
||||||
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"],
|
"exclude": ["node_modules", "dist", "**/*.test.tsx", "**/*.test.ts", "**/*.spec.ts"],
|
||||||
"references": [
|
"references": [{ "path": "../database" }, { "path": "../vitest-config" }]
|
||||||
{ "path": "../database" } // Reference other projects
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,5 +6,6 @@
|
||||||
"rootDir": "src"
|
"rootDir": "src"
|
||||||
},
|
},
|
||||||
"include": ["src/**/*", "env.d.ts"],
|
"include": ["src/**/*", "env.d.ts"],
|
||||||
"exclude": ["node_modules", "dist", "tests", "**/*.test.ts", "**/*.spec.ts"]
|
"exclude": ["node_modules", "dist", "tests", "**/*.test.ts", "**/*.spec.ts"],
|
||||||
|
"references": [{ "path": "../vitest-config" }]
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
"rootDir": "src"
|
"rootDir": "src"
|
||||||
},
|
},
|
||||||
"include": ["src/**/*", "env.d.ts"],
|
"include": ["src*", "env.d.ts"],
|
||||||
"exclude": ["node_modules", "dist", "tests", "**/*.test.ts", "**/*.spec.ts"]
|
"exclude": ["node_modules", "dist", "tests", "**/*.test.tsx", "**/*.test.ts", "**/*.spec.ts"],
|
||||||
|
"references": [{ "path": "../database" }, { "path": "../vitest-config" }]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{
|
{
|
||||||
"extends": "../typescript-config/base.json",
|
"extends": "../typescript-config/base.json",
|
||||||
"include": ["volumes/**/*"]
|
"include": ["volumes*"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
"tsBuildInfoFile": "dist/.cache/tsbuildinfo.json",
|
"tsBuildInfoFile": "dist/.cache/tsbuildinfo.json",
|
||||||
"outDir": "dist"
|
"outDir": "dist"
|
||||||
},
|
},
|
||||||
"include": ["src/**/*", "env.d.ts"],
|
"include": ["src*", "env.d.ts"],
|
||||||
"exclude": ["node_modules", "dist"]
|
"exclude": ["node_modules", "dist"],
|
||||||
|
"references": [{ "path": "../database" }, { "path": "../vitest-config" }]
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,9 @@
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"declarationMap": true,
|
"declarationMap": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"incremental": false,
|
"incremental": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
|
"composite": true,
|
||||||
"lib": ["es2022", "DOM", "DOM.Iterable"],
|
"lib": ["es2022", "DOM", "DOM.Iterable"],
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
|
|
|
@ -10,5 +10,6 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["src/**/*"],
|
"include": ["src/**/*"],
|
||||||
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
|
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"],
|
||||||
|
"references": [{ "path": "../vitest-config" }]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue