Skip to content

🧪 test: add comprehensive testing for StateManager CRUD and workflow operations#63

Open
groupthinking wants to merge 4 commits intomainfrom
test-state-manager-crud-6176333627794370482
Open

🧪 test: add comprehensive testing for StateManager CRUD and workflow operations#63
groupthinking wants to merge 4 commits intomainfrom
test-state-manager-crud-6176333627794370482

Conversation

@groupthinking
Copy link
Owner

🎯 What: The StateManager class in @eventrelay/state-manager lacked tests for its initialization, basic CRUD operations, and workflow operations. This PR adds a comprehensive test suite for StateManager.

📊 Coverage: The new tests cover:

  • Provider initialization for both upstash and standard redis.
  • Basic CRUD (set, get, delete) methods with prefix assertions and TTL handling.
  • Workflow domain operations (saveWorkflowState, getWorkflowState, updateWorkflowStep, markWorkflowCompleted, markWorkflowFailed).
  • Rate limiting (checkRateLimit).
  • Lock acquisition and release (acquireLock, releaseLock).
  • Database client disconnection (disconnect).

Result: 26 test cases have been added and verified to pass, bringing 100% confidence to the core StateManager functionality while safely mocking out external dependencies using vitest mocks.


PR created automatically by Jules for task 6176333627794370482 started by @groupthinking

Co-authored-by: groupthinking <154503486+groupthinking@users.noreply.github.com>
@google-labs-jules
Copy link
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

Copilot AI review requested due to automatic review settings March 8, 2026 22:19
@vercel
Copy link
Contributor

vercel bot commented Mar 8, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
v0-uvai Skipped Skipped Mar 8, 2026 10:29pm

@railway-app railway-app bot temporarily deployed to EventRelay / EventRelay-pr-63 March 8, 2026 22:19 Destroyed
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the reliability and stability of the StateManager class by introducing a robust and comprehensive test suite. The new tests validate critical functionalities, from provider initialization and basic data operations to complex workflow management and concurrency controls like rate limiting and locking. By thoroughly mocking external dependencies, these tests provide high confidence in the core logic of the StateManager, ensuring its correct behavior across various scenarios and configurations.

Highlights

  • Comprehensive Testing: Added a comprehensive test suite for the StateManager class, addressing previous lack of coverage for initialization, basic CRUD, and workflow operations.
  • Provider Initialization: Tests now cover correct initialization for both upstash and standard redis providers.
  • CRUD Operations: Basic CRUD methods (set, get, delete) are thoroughly tested, including prefix assertions and TTL handling for both Upstash and Redis.
  • Workflow Management: Extensive tests for workflow domain operations such as saveWorkflowState, getWorkflowState, updateWorkflowStep, markWorkflowCompleted, and markWorkflowFailed.
  • Rate Limiting and Locking: Included tests for checkRateLimit, acquireLock, and releaseLock functionalities.
  • Database Client Disconnection: Verified the disconnect method's behavior, specifically for Redis instances.
Changelog
  • package-lock.json
    • Removed the 'dev' flag from the 'fsevents' dependency entry.
  • packages/state-manager/src/index.test.ts
    • Added a new test file to provide comprehensive unit tests for the StateManager class.
    • Implemented mocks for @upstash/redis, ioredis, and @upstash/ratelimit to isolate StateManager logic.
    • Included tests for StateManager initialization with both Upstash and Redis providers.
    • Developed tests for set, get, and delete CRUD operations, verifying key prefixing and TTL handling.
    • Added tests for workflow-specific methods like saveWorkflowState, getWorkflowState, updateWorkflowStep, markWorkflowCompleted, and markWorkflowFailed.
    • Created tests for checkRateLimit, acquireLock, and releaseLock functionalities.
    • Included a test for the disconnect method to ensure proper client termination for Redis.
Activity
  • No human activity has been recorded for this pull request.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@railway-app
Copy link

railway-app bot commented Mar 8, 2026

🚅 Deployed to the EventRelay-pr-63 environment in EventRelay

Service Status Web Updated (UTC)
guides ✅ Success (View Logs) Web Mar 8, 2026 at 10:31 pm
eslint-config ✅ Success (View Logs) Web Mar 8, 2026 at 10:31 pm
supabase ✅ Success (View Logs) Mar 8, 2026 at 10:31 pm
EventRelay ✅ Success (View Logs) Web Mar 8, 2026 at 10:31 pm
web ✅ Success (View Logs) Web Mar 8, 2026 at 10:31 pm

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a Vitest test suite for @eventrelay/state-manager’s StateManager to validate provider initialization (Upstash/Redis) and core state/workflow behaviors in isolation via mocks.

Changes:

  • Introduces comprehensive unit tests for CRUD, workflow state helpers, rate limiting, distributed locks, and disconnect behavior.
  • Adds module mocks for @upstash/redis, ioredis, and @upstash/ratelimit to avoid external dependencies in tests.
  • Updates package-lock.json (appears incidental vs. test-only intent).

Reviewed changes

Copilot reviewed 1 out of 2 changed files in this pull request and generated 3 comments.

File Description
packages/state-manager/src/index.test.ts New Vitest suite covering StateManager initialization, CRUD, workflow operations, rate limiting, locks, and disconnect.
package-lock.json Lockfile metadata change for fsevents entry (dev flag removed).

Comment on lines +207 to +218
vi.setSystemTime(now);

await manager.saveWorkflowState(mockState);

expect(mockState.updatedAt).toBe(now.toISOString());
expect(mockUpstash.setex).toHaveBeenCalledWith(
'eventrelay:workflow:123',
86400 * 7,
JSON.stringify(mockState)
);

vi.useRealTimers();
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vi.useFakeTimers() is enabled in this test and only reset at the end. If any assertion throws before vi.useRealTimers() runs, subsequent tests may run with fake timers still enabled and fail in confusing ways. Consider wrapping the fake-timer section in a try/finally or moving vi.useRealTimers() to an afterEach to guarantee cleanup.

Suggested change
vi.setSystemTime(now);
await manager.saveWorkflowState(mockState);
expect(mockState.updatedAt).toBe(now.toISOString());
expect(mockUpstash.setex).toHaveBeenCalledWith(
'eventrelay:workflow:123',
86400 * 7,
JSON.stringify(mockState)
);
vi.useRealTimers();
try {
vi.setSystemTime(now);
await manager.saveWorkflowState(mockState);
expect(mockState.updatedAt).toBe(now.toISOString());
expect(mockUpstash.setex).toHaveBeenCalledWith(
'eventrelay:workflow:123',
86400 * 7,
JSON.stringify(mockState)
);
} finally {
vi.useRealTimers();
}

Copilot uses AI. Check for mistakes.
Comment on lines +372 to +374
await manager.disconnect(); // upstash, no quit
// Just expect it doesn't throw
expect(true).toBe(true);
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test doesn't make any meaningful assertion about disconnect() beyond expect(true).toBe(true). If the goal is to ensure it doesn't throw for non-Redis providers, prefer asserting the promise resolves (e.g., await expect(manager.disconnect()).resolves...) so failures are actually tied to disconnect() behavior.

Suggested change
await manager.disconnect(); // upstash, no quit
// Just expect it doesn't throw
expect(true).toBe(true);
// For non-Redis providers, disconnect should be a no-op and resolve successfully
await expect(manager.disconnect()).resolves.toBeUndefined();

Copilot uses AI. Check for mistakes.
Comment on lines +301 to +309
it('returns true if ratelimit is not initialized', async () => {
const redisManager = new StateManager({
provider: 'redis',
redis: { host: 'localhost', port: 6379 },
});
await redisManager.initialize();
const result = await redisManager.checkRateLimit('test-id');
expect(result).toEqual({ success: true, remaining: -1 });
});
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test name is misleading: checkRateLimit() returns an object { success, remaining }, not a boolean. Consider renaming the test to reflect the actual behavior being asserted (e.g., returns a default { success: true, remaining: -1 } when rate limiting isn't initialized).

Copilot uses AI. Check for mistakes.
…ript

Co-authored-by: groupthinking <154503486+groupthinking@users.noreply.github.com>
@railway-app railway-app bot temporarily deployed to EventRelay / EventRelay-pr-63 March 8, 2026 22:24 Destroyed
@github-actions
Copy link

github-actions bot commented Mar 8, 2026

🔍 PR Validation

⚠️ PR title should follow conventional commits format

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds a comprehensive test suite for the StateManager, which is a great improvement for ensuring the reliability of state management operations. The tests cover initialization, CRUD, and workflow logic for both Redis and Upstash providers.

My review includes a few suggestions to improve the maintainability and robustness of the tests, such as refactoring duplicated test logic, making assertions on JSON data less fragile, and removing magic numbers. These changes will help keep the test suite clean and easy to manage as the codebase evolves.

Comment on lines +48 to +54
const RatelimitClass = class {
constructor() {
return mockRatelimit;
}
};
// @ts-ignore
RatelimitClass.slidingWindow = vi.fn();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The use of @ts-ignore can be avoided here. You can define the static slidingWindow property directly on the mock class to provide proper typing and make the mock cleaner.

Suggested change
const RatelimitClass = class {
constructor() {
return mockRatelimit;
}
};
// @ts-ignore
RatelimitClass.slidingWindow = vi.fn();
const RatelimitClass = class {
constructor() {
return mockRatelimit;
}
static slidingWindow = vi.fn();
};

});
});

describe('CRUD operations - Upstash', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The test suites for 'CRUD operations - Upstash' (lines 92-135) and 'CRUD operations - Redis' (lines 137-180) are nearly identical. This duplication can be eliminated by using describe.each from vitest to parameterize the tests for each provider. This will make the test suite more concise and easier to maintain.

Here's an example of how you could structure it:

const providers = [
  {
    name: 'upstash',
    mock: mockUpstash,
    config: { provider: 'upstash', upstash: { url: 'http://localhost', token: 'token' } },
  },
  {
    name: 'redis',
    mock: mockRedis,
    config: { provider: 'redis', redis: { host: 'localhost', port: 6379 } },
  },
];

describe.each(providers)('CRUD operations - $name', ({ mock, config }) => {
  let manager: StateManager;

  beforeEach(async () => {
    manager = new StateManager(config as any);
    await manager.initialize();
  });

  // ... (place shared it() blocks here, using `mock` for assertions)
});

expect(mockState.updatedAt).toBe(now.toISOString());
expect(mockUpstash.setex).toHaveBeenCalledWith(
'eventrelay:workflow:123',
86400 * 7,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The value 86400 * 7 is a magic number representing seven days in seconds. To improve readability and maintainability, consider defining this as a named constant at the top of the file, like const SEVEN_DAYS_IN_SECONDS = 604800;. This constant can then be reused in all places where this TTL is expected (lines 245, 265, 278).

Comment on lines +263 to +267
expect(mockUpstash.setex).toHaveBeenCalledWith(
'eventrelay:workflow:123',
86400 * 7,
expect.stringContaining('"status":"completed"')
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Asserting on a partial string of a stringified JSON object with expect.stringContaining is fragile, as it can break if the property order changes. A more robust approach is to capture the argument, parse it, and assert on the object's properties directly. This also applies to the markWorkflowFailed test which uses expect.stringMatching.

        const [key, ttl, stateJson] = mockUpstash.setex.mock.calls[0];
        expect(key).toBe('eventrelay:workflow:123');
        expect(ttl).toBe(86400 * 7);
        expect(JSON.parse(stateJson).status).toBe('completed');

Comment on lines +276 to +280
expect(mockUpstash.setex).toHaveBeenCalledWith(
'eventrelay:workflow:123',
86400 * 7,
expect.stringMatching(/"status":"failed".*"error":"test error"/)
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using expect.stringMatching with a regex on a stringified JSON object can be brittle. It's better to parse the JSON and assert on the properties of the resulting object to create a more robust test.

        const [key, ttl, stateJson] = mockUpstash.setex.mock.calls[0];
        const savedState = JSON.parse(stateJson);
        expect(key).toBe('eventrelay:workflow:123');
        expect(ttl).toBe(86400 * 7);
        expect(savedState.status).toBe('failed');
        expect(savedState.error).toBe('test error');

Comment on lines +373 to +374
// Just expect it doesn't throw
expect(true).toBe(true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The assertion expect(true).toBe(true) is redundant. The test's purpose is to ensure that disconnect() doesn't throw an error for a provider without a quit method. This is implicitly tested by just calling the method. If it throws, the test will fail. You can remove these lines to make the test cleaner.

Co-authored-by: groupthinking <154503486+groupthinking@users.noreply.github.com>
@railway-app railway-app bot temporarily deployed to EventRelay / EventRelay-pr-63 March 8, 2026 22:27 Destroyed
@github-actions
Copy link

github-actions bot commented Mar 8, 2026

🔍 PR Validation

⚠️ PR title should follow conventional commits format

Co-authored-by: groupthinking <154503486+groupthinking@users.noreply.github.com>
@github-actions
Copy link

github-actions bot commented Mar 8, 2026

🔍 PR Validation

⚠️ PR title should follow conventional commits format

Comment on lines +207 to +218
vi.setSystemTime(now);

await manager.saveWorkflowState(mockState);

expect(mockState.updatedAt).toBe(now.toISOString());
expect(mockUpstash.setex).toHaveBeenCalledWith(
'eventrelay:workflow:123',
86400 * 7,
JSON.stringify(mockState)
);

vi.useRealTimers();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
vi.setSystemTime(now);
await manager.saveWorkflowState(mockState);
expect(mockState.updatedAt).toBe(now.toISOString());
expect(mockUpstash.setex).toHaveBeenCalledWith(
'eventrelay:workflow:123',
86400 * 7,
JSON.stringify(mockState)
);
vi.useRealTimers();
try {
vi.setSystemTime(now);
await manager.saveWorkflowState(mockState);
expect(mockState.updatedAt).toBe(now.toISOString());
expect(mockUpstash.setex).toHaveBeenCalledWith(
'eventrelay:workflow:123',
86400 * 7,
JSON.stringify(mockState)
);
} finally {
vi.useRealTimers();
}

Missing cleanup for fake timers in saveWorkflowState test - vi.useRealTimers() not called if an assertion fails

Fix on Vercel

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants