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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 158 additions & 0 deletions .cursor/rules/bun-test-mocks.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
---
description: bun testing mocks
globs: *.spec.ts
alwaysApply: false
---
# Bun Mocks

This document outlines the standards for writing tests using Bun's testing framework in this repository.

## Mocking Best Practices

### Use Proper Mocking Functions

Always use the appropriate mocking functions from Bun's testing framework:

- Use `spyOn()` to track calls to existing functions without replacing their implementation
- Use `mock()` to create standalone mock functions
- Use `mock.module()` for module-level mocking

```typescript
// Good: Using spyOn for existing methods
const spy = spyOn(fs, "readFileSync");

// Good: Creating a standalone mock function
const mockFn = mock(() => "mocked result");
```

### Restore Mocks After Tests

Always restore mocks after tests to prevent test pollution:

- Use `mock.restore()` in `afterEach` or `afterAll` hooks
- Alternatively, use `mockFn.mockRestore()` for individual mocks

```typescript
// Good: Restoring all mocks after each test
afterEach(() => {
mock.restore();
});
```

### Use Test Lifecycle Hooks

Organize test setup and teardown using lifecycle hooks:

- Use `beforeEach` for setting up mocks and test data
- Use `afterEach` for cleaning up mocks and test data
- Use `afterAll` for final cleanup

```typescript
describe("My Test Suite", () => {
let mySpy;

beforeEach(() => {
// Setup mocks
mySpy = spyOn(myObject, "myMethod");
});

afterEach(() => {
// Clean up
mock.restore();
});

// Tests go here
});
```

### Avoid Global Mocks

Avoid modifying global objects or prototypes directly. Instead:

- Use `spyOn` to mock methods on objects
- Use `mock.module()` to mock entire modules
- Keep mocks scoped to the tests that need them

```typescript
// Bad: Directly modifying a global object
fs.readFileSync = jest.fn();

// Good: Using spyOn
const readFileSpy = spyOn(fs, "readFileSync");
```

### Use Module Mocking Appropriately

When mocking modules:

- Use `mock.module()` before the module is imported when possible
- For modules already imported, be aware that side effects have already occurred
- Consider using `--preload` for mocks that need to be in place before any imports

```typescript
// Good: Module mocking
mock.module("./myModule", () => {
return {
myFunction: () => "mocked result",
};
});
```

### Verify Mock Interactions

Always verify that mocks were called as expected:

- Use `.toHaveBeenCalled()` to verify a function was called
- Use `.toHaveBeenCalledWith()` to verify arguments
- Use `.toHaveBeenCalledTimes()` to verify call count

```typescript
test("my function calls the dependency", () => {
const spy = spyOn(dependency, "method");
myFunction();
expect(spy).toHaveBeenCalled();
expect(spy).toHaveBeenCalledWith("expected arg");
expect(spy).toHaveBeenCalledTimes(1);
});
```

## Test Organization

### Group Related Tests

Use `describe` blocks to group related tests:

```typescript
describe("MyClass", () => {
describe("myMethod", () => {
it("should handle valid input", () => {
// Test with valid input
});

it("should handle invalid input", () => {
// Test with invalid input
});
});
});
```

### Write Clear Test Descriptions

Test descriptions should clearly state what is being tested and the expected outcome:

```typescript
// Good: Clear test description
it("should return user data when given a valid user ID", () => {
// Test implementation
});

// Bad: Vague test description
it("works correctly", () => {
// Test implementation
});
```

## Additional Resources

- [Bun Testing Documentation](mdc:https:/bun.sh/docs/cli/test)
- [Jest API Reference](mdc:https:/jestjs.io/docs/api) (for compatible APIs)
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ jobs:
run: bun run build

- name: Creating .npmrc
if: ${{ steps.release.outputs.release_created }}
run: |
cat << EOF > "$HOME/.npmrc"
//registry.npmjs.org/:_authToken=$NPM_TOKEN
Expand All @@ -49,4 +50,5 @@ jobs:
NPM_TOKEN: ${{ secrets.NPM_CONFIG_TOKEN }}

- name: Publish package
if: ${{ steps.release.outputs.release_created }}
run: npm publish --access public
3 changes: 2 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ jobs:
- name: Install dependencies
run: bun install

# TODO change this to bun test
- name: Run tests
run: bun test
run: bun test -u
112 changes: 59 additions & 53 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,20 +77,65 @@ if (employeeTool) {
}
```

## Integrations
## File Uploads

### OpenAI
The SDK supports file uploads for tools that accept file parameters. File uploads have been simplified to use a single `file_path` parameter:

```typescript
import { StackOneToolSet } from "@stackone/ai";
import * as path from "path";

// Initialize with API key and account ID
const toolset = new StackOneToolSet(
process.env.STACKONE_API_KEY,
process.env.STACKONE_ACCOUNT_ID
);
const tools = toolset.getTools("documents_*");
const uploadTool = tools.getTool("documents_upload_file");

// Upload a file using the file_path parameter
const result = await uploadTool.execute({
file_path: path.join(__dirname, "document.pdf"), // Path to the file
});
```

### Using with OpenAI or AI SDK

When using file upload tools with OpenAI or AI SDK, the parameters are automatically simplified to a single `file_path` parameter:

```typescript
import { StackOneToolSet } from "@stackone/ai";
import OpenAI from "openai";

const toolset = new StackOneToolSet();
const tools = toolset.getTools("hris_*", "your-account-id");
// Initialize with API key and account ID
const toolset = new StackOneToolSet(
process.env.STACKONE_API_KEY,
process.env.STACKONE_ACCOUNT_ID
);
const tools = toolset.getTools("documents_*");

// Convert to OpenAI functions
const openAITools = tools.toOpenAI();

// The file upload tool will have a simplified schema with just a file_path parameter
// {
// "type": "function",
// "function": {
// "name": "documents_upload_file",
// "description": "Upload a document file",
// "parameters": {
// "type": "object",
// "properties": {
// "file_path": {
// "type": "string",
// "description": "Path to the file to upload. The filename will be extracted from the path."
// }
// },
// "required": ["file_path"]
// }
// }
// }

// Use with OpenAI
const openai = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
Expand All @@ -100,62 +145,23 @@ const response = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "List all employees" },
{ role: "user", content: "Upload this document: /path/to/document.pdf" },
],
tools: openAITools,
});
```

### AI SDK

```typescript
import { StackOneToolSet } from "@stackone/ai";
import { generateText } from "ai";
import { openai } from "@ai-sdk/openai";

const toolset = new StackOneToolSet();
const tools = toolset.getTools("hris_*", "your-account-id");

// Convert to AI SDK tools
const aiSdkTools = tools.toAISDKTools();
// Use max steps to automatically call the tool if it's needed
const { text } = await generateText({
model: openai("gpt-4o-mini"),
tools: aiSdkTools,
prompt:
"Get all details about employee with id: c28xIQaWQ6MzM5MzczMDA2NzMzMzkwNzIwNA",
maxSteps: 3,
});
// When OpenAI calls the tool with the file_path parameter
// The SDK automatically handles:
// 1. Extracting the filename from the path
// 2. Determining the file format from the extension
// 3. Reading and encoding the file
// 4. Sending it to the API with the correct parameters
```

## Error Handling

The SDK provides specific error classes for different types of errors:

- `StackOneError`: Base error class for all SDK errors
- `StackOneAPIError`: Raised when the StackOne API returns an error
- `ToolsetConfigError`: Raised when there is an error in the toolset configuration
- `ToolsetLoadError`: Raised when there is an error loading tools
## Integrations

```typescript
import { StackOneToolSet, StackOneAPIError } from "@stackone/ai";
### OpenAI

const toolset = new StackOneToolSet();
const tools = toolset.getTools("hris_*", "your-account-id");
const tool = tools.getTool("hris_get_employee");

try {
const result = await tool.execute({ id: "employee-id" });
console.log(result);
} catch (error) {
if (error instanceof StackOneAPIError) {
console.error(`API Error (${error.statusCode}):`, error.responseBody);
} else {
console.error("Error:", error);
}
}
```

## License

Apache 2.0
```
Loading