Skip to content

Conversation

@shige
Copy link
Member

@shige shige commented Dec 3, 2025

Summary

  • Replace hardcoded isEmailFromRoute06 with configurable isInternalUserEmail using INTERNAL_USER_EMAIL_DOMAIN env var

Changes

New Environment Variable

  • INTERNAL_USER_EMAIL_DOMAIN: Set to the email domain for internal users
  • When not set, no users are treated as internal

Internal User Check

Security Improvements

  • Added server-side validation in createTeam action to prevent bypassing UI restrictions

Test plan

  • Set `INTERNAL_USER_EMAIL_DOMAIN in production
  • Verify internal users cannot create free teams
  • Verify external users can create one free team

Summary by CodeRabbit

  • Refactor

    • Updated internal user identification to use configurable environment variable instead of hardcoded domain, providing greater flexibility.
    • Introduced improved retry utility with enhanced error handling and configurable retry strategies.
  • Tests

    • Added comprehensive test coverage for email domain validation across multiple scenarios.
    • Enhanced test suite for free team creation logic with proper environment variable management.

✏️ Tip: You can customize this high-level summary in your review settings.

shige added 6 commits December 3, 2025 17:29
Replace hardcoded domain check with configurable INTERNAL_USER_EMAIL_DOMAIN.
Returns false when env var is not set.
Update tests to mock INTERNAL_USER_EMAIL_DOMAIN env var.
Remove duplicated logic and use the centralized function.
Use strict equality instead of endsWith to prevent subdomain matching.
For example, [email protected] will not match when domain is example.com.
Copilot AI review requested due to automatic review settings December 3, 2025 08:37
@changeset-bot
Copy link

changeset-bot bot commented Dec 3, 2025

⚠️ No Changeset found

Latest commit: ad92086

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

💥 An error occurred when fetching the changed packages and changesets in this PR
Some errors occurred when validating the changesets config:
The package or glob expression "giselles-ai" is specified in the `ignore` option but it is not found in the project. You may have misspelled the package name or provided an invalid glob expression. Note that glob expressions must be defined according to https://www.npmjs.com/package/micromatch.

@giselles-ai
Copy link

giselles-ai bot commented Dec 3, 2025

Finished running flow.

Step 1
🟢
On Pull Request OpenedStatus: Success Updated: Dec 3, 2025 8:37am
Step 2
🟢
Manual QAStatus: Success Updated: Dec 3, 2025 8:40am
🟢
Prompt for AI AgentsStatus: Success Updated: Dec 3, 2025 8:40am
Step 3
🟢
Create a Comment for PRStatus: Success Updated: Dec 3, 2025 8:45am
Step 4
🟢
Create Pull Request CommentStatus: Success Updated: Dec 3, 2025 8:45am

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

This PR refactors the internal user identification system to use a configurable environment variable instead of a hardcoded domain check. The main changes replace isEmailFromRoute06 with isInternalUserEmail, which checks against the INTERNAL_USER_EMAIL_DOMAIN environment variable.

Key Changes:

  • Introduced configurable INTERNAL_USER_EMAIL_DOMAIN environment variable to determine internal users
  • Consolidated free team creation logic into server-side validation
  • Added comprehensive test coverage for the new email domain matching logic

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.

Show a summary per file
File Description
apps/studio.giselles.ai/lib/utils.ts Replaced hardcoded domain check with configurable environment variable
apps/studio.giselles.ai/lib/utils.test.ts Added comprehensive test coverage for the new domain matching function
apps/studio.giselles.ai/services/teams/plan-features/free-team-creation.ts Updated to use new configurable internal user check
apps/studio.giselles.ai/services/teams/plan-features/free-team-creation.test.ts Updated tests to work with configurable domain
apps/studio.giselles.ai/services/teams/components/team-creation.tsx Consolidated free team validation using centralized function
apps/studio.giselles.ai/services/teams/actions/create-team.ts Updated to use new internal user check function
apps/studio.giselles.ai/services/accounts/actions/initialize-account.ts Updated to use new internal user check function

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@vercel
Copy link

vercel bot commented Dec 3, 2025

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

Project Deployment Preview Comments Updated (UTC)
giselle Ready Ready Preview Comment Dec 3, 2025 8:41am
ui Ready Ready Preview Comment Dec 3, 2025 8:41am

@qodo-merge-for-open-source
Copy link

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🔴
Missing server-side validation

Description: The server-side validation for free team creation is missing - the function checks
isInternalUser but does not verify if the user already has a free team before allowing
creation, allowing potential bypass of the one-free-team-per-user restriction through
direct API calls.
create-team.ts [23-37]

Referred Code
export async function createTeam(formData: FormData) {
	const teamName = formData.get("teamName") as string;
	const selectedPlan = formData.get("selectedPlan") as string;

	const supabaseUser = await getUser();
	if (!supabaseUser) {
		throw new Error("User not found");
	}

	const isInternalUser =
		supabaseUser.email != null && isInternalUserEmail(supabaseUser.email);
	if (isInternalUser) {
		const teamId = await createInternalTeam(supabaseUser, teamName);
		await setCurrentTeam(teamId);
		redirect("/settings/team");
Email parsing vulnerability

Description: The email.split("@")[1] operation does not validate the email format before splitting,
which could cause undefined behavior or crashes if the email contains no '@' symbol or
multiple '@' symbols, potentially leading to incorrect internal user classification.
utils.ts [20-20]

Referred Code
const domain = email.split("@")[1];
return domain === internalDomain;
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

🔴
Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Missing email validation: The isInternalUserEmail function splits email by '@' without validating the
email format, which could cause runtime errors or incorrect behavior with malformed
inputs.

Referred Code
const domain = email.split("@")[1];
return domain === internalDomain;

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@shige shige self-assigned this Dec 3, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 3, 2025

Walkthrough

This PR refactors internal user identification by renaming isEmailFromRoute06 to isInternalUserEmail with environment-based domain configuration, introduces a new withRetry utility for retryable async operations, and updates all usages throughout the account and team services.

Changes

Cohort / File(s) Summary
Core utility refactoring
apps/studio.giselles.ai/lib/utils.ts, apps/studio.giselles.ai/lib/utils.test.ts
Renamed isEmailFromRoute06 to isInternalUserEmail with environment-driven domain matching; added new withRetry utility function with MaxRetriesExceededError handling and configurable retry options; added comprehensive test suite covering all scenarios
Account service integration
apps/studio.giselles.ai/services/accounts/actions/initialize-account.ts
Updated import and usage from isEmailFromRoute06 to isInternalUserEmail for internal user plan assignment logic
Team service utility updates
apps/studio.giselles.ai/services/teams/actions/create-team.ts, apps/studio.giselles.ai/services/teams/plan-features/free-team-creation.ts
Updated imports and function calls from isEmailFromRoute06 to isInternalUserEmail in team creation and plan feature logic
Team creation component refactoring
apps/studio.giselles.ai/services/teams/components/team-creation.tsx
Removed legacy isEmailFromRoute06 detection and replaced with dynamic canCreateFreeTeam function for permission determination based on user email and team plans
Team feature test updates
apps/studio.giselles.ai/services/teams/plan-features/free-team-creation.test.ts
Added environment variable setup and teardown lifecycle; updated test cases to reflect isInternalUserEmail behavior with configurable domain configuration

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • New withRetry utility: Verify the retry logic is sound, error handling is complete, and the MaxRetriesExceededError class properly encapsulates retry history
  • Refactoring consistency: Ensure all occurrences of isEmailFromRoute06 have been replaced with isInternalUserEmail across the codebase
  • Environment variable handling: Confirm the guard clause in isInternalUserEmail correctly handles missing INTERNAL_USER_EMAIL_DOMAIN and test coverage reflects all edge cases
  • Component changes: Review the canCreateFreeTeam refactoring in team-creation.tsx to verify the new dynamic logic maintains existing behavior

Poem

🐰 Old route06 hops away, a new dawn breaks today,
With envs that guide the way, retry logic saves the day!
Freed from hardcoded chains, the config now reigns,
Each service sings in perfect refrain. 🌟

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: refactoring internal user detection to use a configurable domain instead of hardcoded route06.co.jp, which is the primary objective of the PR.
Description check ✅ Passed The PR description covers all required sections of the template with clear information about changes, environment variables, and test plan.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/internal-user-domain-env

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@qodo-merge-for-open-source
Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
General
Use Vitest API for environment stubs

Replace the manual process.env manipulation in tests with Vitest's vi.stubEnv
and vi.unstubAllEnvs for more robust environment variable management.

apps/studio.giselles.ai/lib/utils.test.ts [5-13]

-const originalEnv = process.env.INTERNAL_USER_EMAIL_DOMAIN;
-
 afterEach(() => {
-	if (originalEnv === undefined) {
-		delete process.env.INTERNAL_USER_EMAIL_DOMAIN;
-	} else {
-		process.env.INTERNAL_USER_EMAIL_DOMAIN = originalEnv;
-	}
+	vi.unstubAllEnvs();
 });
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out that using Vitest's vi.stubEnv is a more robust and idiomatic way to manage environment variables in tests, improving test isolation and maintainability.

Medium
Possible issue
Improve email domain extraction logic

Replace email.split('@')[1] with a method using lastIndexOf('@') to correctly
extract the domain from potentially malformed email addresses containing
multiple '@' symbols.

apps/studio.giselles.ai/lib/utils.ts [20]

-const domain = email.split("@")[1];
+const atIndex = email.lastIndexOf("@");
+if (atIndex === -1) {
+	return false;
+}
+const domain = email.substring(atIndex + 1);
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies a potential issue with split('@') for malformed email strings and proposes a more robust solution using lastIndexOf, improving the function's reliability.

Low
  • More

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
apps/studio.giselles.ai/lib/utils.ts (1)

15-22: Potential undefined comparison when email lacks @ symbol.

When email doesn't contain an @ symbol (e.g., "invalid-email"), email.split("@")[1] returns undefined. While comparing undefined === internalDomain will return false (which is the desired behavior), this relies on implicit behavior that could be confusing. The tests cover this case, so functionality is correct, but consider making it explicit for clarity.

 export const isInternalUserEmail = (email: string): boolean => {
 	const internalDomain = process.env.INTERNAL_USER_EMAIL_DOMAIN;
 	if (!internalDomain) {
 		return false;
 	}
 	const domain = email.split("@")[1];
+	if (!domain) {
+		return false;
+	}
 	return domain === internalDomain;
 };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e423b3 and ad92086.

📒 Files selected for processing (7)
  • apps/studio.giselles.ai/lib/utils.test.ts (1 hunks)
  • apps/studio.giselles.ai/lib/utils.ts (1 hunks)
  • apps/studio.giselles.ai/services/accounts/actions/initialize-account.ts (2 hunks)
  • apps/studio.giselles.ai/services/teams/actions/create-team.ts (2 hunks)
  • apps/studio.giselles.ai/services/teams/components/team-creation.tsx (2 hunks)
  • apps/studio.giselles.ai/services/teams/plan-features/free-team-creation.test.ts (2 hunks)
  • apps/studio.giselles.ai/services/teams/plan-features/free-team-creation.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (11)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx,js,jsx}: Favor clear, descriptive names and type annotations over clever tricks
If you need a multi-paragraph comment, refactor until intent is obvious

**/*.{ts,tsx,js,jsx}: Use async/await and proper error handling
Variables and functions should use camelCase naming
Booleans and functions should use is, has, can, should prefixes
Function names should clearly indicate purpose

Files:

  • apps/studio.giselles.ai/services/accounts/actions/initialize-account.ts
  • apps/studio.giselles.ai/services/teams/actions/create-team.ts
  • apps/studio.giselles.ai/services/teams/plan-features/free-team-creation.ts
  • apps/studio.giselles.ai/lib/utils.ts
  • apps/studio.giselles.ai/services/teams/components/team-creation.tsx
  • apps/studio.giselles.ai/lib/utils.test.ts
  • apps/studio.giselles.ai/services/teams/plan-features/free-team-creation.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/development-guide.mdc)

**/*.{ts,tsx}: MUST run pnpm biome check --write [filename] after EVERY code modification
All code changes must be formatted using Biome before being committed
Use Biome for formatting with tab indentation and double quotes
Follow organized imports pattern (enabled in biome.json)
Use TypeScript for type safety; avoid any types
Use Next.js patterns for web applications
Use async/await for asynchronous code rather than promises
Error handling: use try/catch blocks and propagate errors appropriately
Use kebab-case for all filenames
Use PascalCase for React components and classes
Use camelCase for variables, functions, and methods
Use prefixes like is, has, can, should for boolean variables (e.g., isEnabled, hasPermission)
Use prefixes like is, has, can, should for boolean functions (e.g., isTriggerRequiringCallsign(), hasActiveSubscription()) instead of imperative verbs
Use verbs or verb phrases for function naming that clearly indicate purpose (e.g., calculateTotalPrice(), not process())

Use PascalCase for React component and class names

Use TypeScript and avoid any

Files:

  • apps/studio.giselles.ai/services/accounts/actions/initialize-account.ts
  • apps/studio.giselles.ai/services/teams/actions/create-team.ts
  • apps/studio.giselles.ai/services/teams/plan-features/free-team-creation.ts
  • apps/studio.giselles.ai/lib/utils.ts
  • apps/studio.giselles.ai/services/teams/components/team-creation.tsx
  • apps/studio.giselles.ai/lib/utils.test.ts
  • apps/studio.giselles.ai/services/teams/plan-features/free-team-creation.test.ts
**/*.{js,ts,tsx,jsx,py,java,cs,cpp,c,go,rb,php,swift,kt,scala,rs,dart}

📄 CodeRabbit inference engine (.cursor/rules/language-support.mdc)

Write all code comments in English

Files:

  • apps/studio.giselles.ai/services/accounts/actions/initialize-account.ts
  • apps/studio.giselles.ai/services/teams/actions/create-team.ts
  • apps/studio.giselles.ai/services/teams/plan-features/free-team-creation.ts
  • apps/studio.giselles.ai/lib/utils.ts
  • apps/studio.giselles.ai/services/teams/components/team-creation.tsx
  • apps/studio.giselles.ai/lib/utils.test.ts
  • apps/studio.giselles.ai/services/teams/plan-features/free-team-creation.test.ts
**/*

📄 CodeRabbit inference engine (.cursor/rules/naming-guide.mdc)

Use kebab-case for file names (e.g., user-profile.ts, api-client.tsx)

Files:

  • apps/studio.giselles.ai/services/accounts/actions/initialize-account.ts
  • apps/studio.giselles.ai/services/teams/actions/create-team.ts
  • apps/studio.giselles.ai/services/teams/plan-features/free-team-creation.ts
  • apps/studio.giselles.ai/lib/utils.ts
  • apps/studio.giselles.ai/services/teams/components/team-creation.tsx
  • apps/studio.giselles.ai/lib/utils.test.ts
  • apps/studio.giselles.ai/services/teams/plan-features/free-team-creation.test.ts
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/naming-guide.mdc)

**/*.{js,ts,jsx,tsx}: Use camelCase for variable names, functions, and methods
Use verbs or verb phrases for function names to clearly indicate what the function does (e.g., calculateTotalPrice(), validateUserInput())
Use nouns or noun phrases for variable names to describe what the variable represents
Use boolean prefixes (is, has, can, should) for boolean variables and functions returning boolean values (e.g., isEnabled, hasPermission, isTriggerRequiringCallsign())

**/*.{js,ts,jsx,tsx}: Run pnpm biome check --write [filename] after every code change
All code must be formatted with Biome before commit

Files:

  • apps/studio.giselles.ai/services/accounts/actions/initialize-account.ts
  • apps/studio.giselles.ai/services/teams/actions/create-team.ts
  • apps/studio.giselles.ai/services/teams/plan-features/free-team-creation.ts
  • apps/studio.giselles.ai/lib/utils.ts
  • apps/studio.giselles.ai/services/teams/components/team-creation.tsx
  • apps/studio.giselles.ai/lib/utils.test.ts
  • apps/studio.giselles.ai/services/teams/plan-features/free-team-creation.test.ts
**/*.{ts,tsx,js,jsx,css}

📄 CodeRabbit inference engine (AGENTS.md)

Files should use kebab-case naming

Files:

  • apps/studio.giselles.ai/services/accounts/actions/initialize-account.ts
  • apps/studio.giselles.ai/services/teams/actions/create-team.ts
  • apps/studio.giselles.ai/services/teams/plan-features/free-team-creation.ts
  • apps/studio.giselles.ai/lib/utils.ts
  • apps/studio.giselles.ai/services/teams/components/team-creation.tsx
  • apps/studio.giselles.ai/lib/utils.test.ts
  • apps/studio.giselles.ai/services/teams/plan-features/free-team-creation.test.ts
**/*.{tsx,ts}

📄 CodeRabbit inference engine (AGENTS.md)

Components should use PascalCase naming

Files:

  • apps/studio.giselles.ai/services/accounts/actions/initialize-account.ts
  • apps/studio.giselles.ai/services/teams/actions/create-team.ts
  • apps/studio.giselles.ai/services/teams/plan-features/free-team-creation.ts
  • apps/studio.giselles.ai/lib/utils.ts
  • apps/studio.giselles.ai/services/teams/components/team-creation.tsx
  • apps/studio.giselles.ai/lib/utils.test.ts
  • apps/studio.giselles.ai/services/teams/plan-features/free-team-creation.test.ts
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/development-guide.mdc)

Use functional components with React hooks

Files:

  • apps/studio.giselles.ai/services/teams/components/team-creation.tsx
**/{components,pages}/**/*.{tsx,ts}

📄 CodeRabbit inference engine (AGENTS.md)

Components should use React hooks and Next.js patterns

Files:

  • apps/studio.giselles.ai/services/teams/components/team-creation.tsx
**/*.test.ts

📄 CodeRabbit inference engine (.cursor/rules/development-guide.mdc)

Tests should follow *.test.ts naming pattern and use Vitest

Files:

  • apps/studio.giselles.ai/lib/utils.test.ts
  • apps/studio.giselles.ai/services/teams/plan-features/free-team-creation.test.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Tests use Vitest with *.test.ts naming convention

Files:

  • apps/studio.giselles.ai/lib/utils.test.ts
  • apps/studio.giselles.ai/services/teams/plan-features/free-team-creation.test.ts
🧠 Learnings (4)
📚 Learning: 2025-11-28T00:51:04.041Z
Learnt from: shige
Repo: giselles-ai/giselle PR: 2290
File: apps/studio.giselles.ai/db/schema.ts:109-169
Timestamp: 2025-11-28T00:51:04.041Z
Learning: The team is consciously using Stripe API version 2025-11-17.preview for Stripe v2 billing cadence (stripeBillingCadenceHistories) and pricing plan subscription (stripePricingPlanSubscriptionHistories) features in apps/studio.giselles.ai/db/schema.ts with awareness that preview APIs may not be production-safe.

Applied to files:

  • apps/studio.giselles.ai/services/teams/actions/create-team.ts
📚 Learning: 2025-11-25T03:06:27.023Z
Learnt from: CR
Repo: giselles-ai/giselle PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T03:06:27.023Z
Learning: Applies to **/*.test.{ts,tsx} : Tests use Vitest with `*.test.ts` naming convention

Applied to files:

  • apps/studio.giselles.ai/lib/utils.test.ts
  • apps/studio.giselles.ai/services/teams/plan-features/free-team-creation.test.ts
📚 Learning: 2025-11-25T03:05:21.219Z
Learnt from: CR
Repo: giselles-ai/giselle PR: 0
File: .cursor/rules/development-guide.mdc:0-0
Timestamp: 2025-11-25T03:05:21.219Z
Learning: Applies to **/*.test.ts : Tests should follow `*.test.ts` naming pattern and use Vitest

Applied to files:

  • apps/studio.giselles.ai/lib/utils.test.ts
  • apps/studio.giselles.ai/services/teams/plan-features/free-team-creation.test.ts
📚 Learning: 2025-05-23T01:12:17.025Z
Learnt from: shige
Repo: giselles-ai/giselle PR: 920
File: packages/web-search/src/websearch.test.ts:15-25
Timestamp: 2025-05-23T01:12:17.025Z
Learning: When testing the web-search package, real API calls are preferred over mocks. Tests should use environment variables like VITEST_WITH_EXTERNAL_API and FIRECRAWL_API_KEY to conditionally run tests that require external services.

Applied to files:

  • apps/studio.giselles.ai/lib/utils.test.ts
  • apps/studio.giselles.ai/services/teams/plan-features/free-team-creation.test.ts
🧬 Code graph analysis (5)
apps/studio.giselles.ai/services/accounts/actions/initialize-account.ts (1)
apps/studio.giselles.ai/lib/utils.ts (1)
  • isInternalUserEmail (15-22)
apps/studio.giselles.ai/services/teams/actions/create-team.ts (1)
apps/studio.giselles.ai/lib/utils.ts (1)
  • isInternalUserEmail (15-22)
apps/studio.giselles.ai/services/teams/plan-features/free-team-creation.ts (2)
apps/studio.giselles.ai/db/schema.ts (1)
  • TeamPlan (194-194)
apps/studio.giselles.ai/lib/utils.ts (1)
  • isInternalUserEmail (15-22)
apps/studio.giselles.ai/lib/utils.test.ts (1)
apps/studio.giselles.ai/lib/utils.ts (1)
  • isInternalUserEmail (15-22)
apps/studio.giselles.ai/services/teams/plan-features/free-team-creation.test.ts (1)
apps/studio.giselles.ai/services/teams/plan-features/free-team-creation.ts (1)
  • canCreateFreeTeam (9-20)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: check
  • GitHub Check: Cursor Bugbot
🔇 Additional comments (14)
apps/studio.giselles.ai/lib/utils.ts (1)

53-106: LGTM!

The withRetry utility is well-designed with sensible defaults, proper exponential backoff with jitter, configurable abort conditions, and comprehensive error tracking via MaxRetriesExceededError.

apps/studio.giselles.ai/services/accounts/actions/initialize-account.ts (2)

15-15: LGTM!

Import updated correctly to use the new isInternalUserEmail utility.


39-39: LGTM!

The nullish coalescing to empty string (supabaseUserEmail ?? "") is appropriate since isInternalUserEmail("") correctly returns false, maintaining safe behavior for users without email.

apps/studio.giselles.ai/services/teams/plan-features/free-team-creation.ts (1)

1-20: LGTM!

Clean refactor that:

  1. Updates the import to use isInternalUserEmail
  2. Generalizes the comment from company-specific to generic "Internal users"
  3. Properly guards against null/undefined email before calling the utility

The logic correctly prevents internal users and users with existing free teams from creating additional free teams.

apps/studio.giselles.ai/services/teams/actions/create-team.ts (3)

11-11: LGTM!

Import updated correctly to use the new isInternalUserEmail utility.


32-33: LGTM!

Consistent null check pattern (supabaseUser.email != null && isInternalUserEmail(supabaseUser.email)) matching the approach used in free-team-creation.ts.


40-52: Good addition of server-side validation.

This server-side check using canCreateFreeTeam is an important security improvement that prevents clients from bypassing UI restrictions. The validation correctly:

  1. Fetches the user's existing teams
  2. Checks eligibility before allowing free team creation
  3. Throws an explicit error if not eligible

This addresses the PR objective of adding server-side validation to prevent UI bypass.

apps/studio.giselles.ai/lib/utils.test.ts (3)

4-13: LGTM!

Good test setup pattern that properly captures and restores the original environment variable state to avoid test pollution.


15-43: Comprehensive edge case coverage.

The tests thoroughly validate:

  • Exact domain matching (line 21)
  • Subdomain rejection (line 25) - confirms PR objective of exact-only matching
  • Different domain rejection (line 29)
  • Domain-as-prefix attack prevention (line 33)
  • Invalid email format handling (lines 37, 41)

45-64: Good coverage for env var edge cases.

Tests correctly verify that when INTERNAL_USER_EMAIL_DOMAIN is unset or empty, all emails are treated as external users (returning false). This aligns with the PR's security goal that "when unset, no users are treated as internal."

apps/studio.giselles.ai/services/teams/components/team-creation.tsx (1)

5-28: LGTM! Excellent refactoring to centralize business logic.

The change successfully moves the free team eligibility logic from the component to a dedicated canCreateFreeTeam function in the plan-features module. This improves maintainability by:

  • Centralizing the business rule in a reusable module
  • Enabling server-side validation (as used in the create-team action)
  • Making the component more focused on presentation

The function call correctly passes user.email and the mapped team plans, matching the expected signature.

apps/studio.giselles.ai/services/teams/plan-features/free-team-creation.test.ts (3)

6-18: LGTM! Proper test isolation with environment variable lifecycle management.

The beforeEach/afterEach pattern correctly manages the INTERNAL_USER_EMAIL_DOMAIN environment variable across test runs:

  • Captures the original value before tests
  • Sets a consistent test domain ("internal.example.com") before each test
  • Properly restores or deletes the variable after each test based on its original state

This ensures test isolation and prevents environment pollution between tests.


24-26: LGTM! Updated test correctly reflects configurable internal domain.

The test now uses "[email protected]" which aligns with the beforeEach setup and properly verifies that internal users cannot create free teams. This replaces the previous hardcoded route06.co.jp domain with the configurable approach.


50-53: LGTM! Critical test case for fallback behavior.

This test verifies the important security behavior documented in the PR objectives: when INTERNAL_USER_EMAIL_DOMAIN is not set, no users are treated as internal. The test correctly:

  • Deletes the environment variable to simulate missing configuration
  • Verifies that even emails matching the internal pattern can create free teams
  • Relies on afterEach to restore the environment state

This ensures the system fails open (allows free team creation) rather than failing closed when the environment variable is misconfigured or missing.

@giselles-ai
Copy link

giselles-ai bot commented Dec 3, 2025

🔍 QA Testing Assistant by Giselle

📋 Manual QA Checklist

Based on the changes in this PR, here are the key areas to test manually:

  • UI/UX: Verify that the "Create team" button is present and clickable on all relevant pages.
  • Functionality: Log in as an internal user (email matching INTERNAL_USER_EMAIL_DOMAIN). Navigate to the team creation page and confirm that the "Free" team option is disabled or hidden.
  • Functionality: Log in as an internal user. Verify that the "Pro" (paid) team option is still available and functional.
  • Functionality: Log in as an external user with no existing teams. Navigate to the team creation page and confirm that the "Free" team option is available and functional.
  • Functionality: Log in as an external user who already has one free team. Navigate to the team creation page and confirm that the "Free" team option is disabled or hidden.
  • Functionality: Log in as an external user. Verify that creating a second free team is prevented.
  • Functionality: Test with a user whose email is a subdomain of INTERNAL_USER_EMAIL_DOMAIN (e.g., [email protected] if INTERNAL_USER_EMAIL_DOMAIN is example.com). Verify they are treated as an external user.
  • Functionality: With INTERNAL_USER_EMAIL_DOMAIN unset, log in as a user who would have previously been considered internal. Verify they can create a free team (if they don't already have one).
  • Functionality: After creating a team, verify that the "Create team" button correctly opens the creation dialog/form without errors.
  • Security: Confirm that attempting to bypass the UI restrictions for creating free teams (e.g., via developer tools) results in a server-side rejection.

✨ Prompt for AI Agents

Use the following prompts with Cursor or Claude Code to automate E2E testing:

📝 E2E Test Generation Prompt

/**
 * @type {import('@playwright/test').PlaywrightTestConfig}
 */
const config = {
  globalSetup: './global-setup', // Assuming a global setup for common configurations
  use: {
    // All options that are shared across all the projects.
    baseURL: process.env.PLAYWRIGHT_TEST_BASE_URL || 'http://localhost:3000',
    headless: process.env.CI ? true : false,
    viewport: { width: 1280, height: 720 },
    ignoreHTTPSErrors: true,
    video: process.env.CI ? 'retain-on-failure' : 'off',
    trace: 'on-first-retry',
    launchOptions: {
      // Use environment variables for internal user checks
      env: {
        ...process.env,
        INTERNAL_USER_EMAIL_DOMAIN: process.env.INTERNAL_USER_EMAIL_DOMAIN || 'default.com', // Default for local runs if not set
      },
    },
  },
  projects: [
    {
      name: 'chromium',
      use: {
        browserName: 'chromium',
        ...({
          permissions: process.platform === 'darwin' ? ['calendar', 'reminders'] : undefined,
        }),
      },
      testMatch: /.*.spec.ts/,
    },
    // Add more projects if needed for different configurations
  ],
  reporter: process.env.CI ? 'github' : 'list',
  outputDir: 'test-results/',
  webServer: {
    command: 'npm run start', // Assumes your app starts with `npm run start`
    url: 'http://localhost:3000',
    reuseExistingServer: !process.env.CI,
  },
};

// Set the environment variable for internal user tests if available
if (process.env.CI) {
  // Example: Setting environment variable for CI runs
  // In a real CI environment, this would be set via CI/CD variables
  if (process.env.INTERNAL_USER_EMAIL_DOMAIN) {
    config.projects.find(p => p.name === 'chromium').use.launchOptions.env.INTERNAL_USER_EMAIL_DOMAIN = process.env.INTERNAL_USER_EMAIL_DOMAIN;
  } else {
    // Default behavior if not explicitly set in CI
    config.projects.find(p => p.name === 'chromium').use.launchOptions.env.INTERNAL_USER_EMAIL_DOMAIN = 'default.com';
  }
}


export default config;

To: AI Coding Assistant (Cursor/Claude Code)

From: Expert QA Engineer

Subject: Generate Playwright E2E Tests for Configurable Internal User Team Creation Logic

You are an expert QA engineer. Your task is to write a comprehensive suite of E2E tests using Playwright and TypeScript for a Next.js application. The tests should validate the changes introduced in a recent Pull Request, which refactors the "internal user" check to be configurable via an environment variable and moves team creation validation to the server-side.

Follow the instructions below to create robust, maintainable, and CI-ready test code.

1. Context Summary

The PR introduces the following key changes:

  • Configurable Internal Users: The logic to identify an "internal user" is no longer hardcoded to a specific email domain (route06.co.jp). It now uses a new environment variable, INTERNAL_USER_EMAIL_DOMAIN. If this variable is set, any user whose email domain is an exact match will be treated as an internal user. If it's not set, no user is considered internal.
  • Server-Side Validation: The logic determining if a user can create a "free" team (canCreateFreeTeam) has been moved from the frontend component to a server-side module. This is a security enhancement to prevent users from bypassing UI restrictions.
  • User Rules:
    • Internal users can never create free teams.
    • External (non-internal) users can create only one free team.
  • Bug Fix: A regression where the "Create team" button was unresponsive has been fixed. The button must now correctly open the team creation dialog.

Critical User Flow to Test: The entire team creation process, from clicking the "Create team" button to successfully creating a team or seeing the correct restrictions based on user type (internal vs. external) and their existing teams.

2. Test Scenarios

Create E2E tests covering the following scenarios. Organize them using test.describe() blocks for clarity (e.g., "Internal User Scenarios", "External User Scenarios").

Happy Paths

  • Scenario 1: External User - First Free Team Creation

    • Given: An external user (e.g., [email protected]) is logged in and has no existing teams.
    • When: The user navigates to the team creation page.
    • Then: The "Create a free team" option should be visible and enabled.
    • And: The user should be able to fill in a team name and successfully create a new free team.
    • Finally: The user should be redirected to the new team's dashboard.
  • Scenario 2: Regression Fix - Create Team Button

    • Given: Any logged-in user.
    • When: The user clicks the main "Create team" button from the team switcher or dashboard.
    • Then: The "Create a new team" dialog/modal must appear.

Edge Cases & Negative Paths

  • Scenario 3: Internal User - No Free Team Creation

    • Given: An internal user (e.g., [email protected]) is logged in. The test environment must have INTERNAL_USER_EMAIL_DOMAIN set to my-company.com.
    • When: The user navigates to the team creation page.
    • Then: The "Create a free team" option should be either hidden or disabled.
    • And: The user should only be able to create paid ("Pro") teams.
  • Scenario 4: External User - With Existing Free Team

    • Given: An external user is logged in who already owns one free team.
    • When: The user navigates to the team creation page.
    • Then: The "Create a free team" option should be either hidden or disabled.
    • And: The user should only see options to create paid teams.
  • Scenario 5: Unset INTERNAL_USER_EMAIL_DOMAIN

    • Given: The INTERNAL_USER_EMAIL_DOMAIN environment variable is not set.
    • And: A user who would otherwise be internal (e.g., [email protected]) is logged in.
    • When: The user navigates to the team creation page.
    • Then: The user should be treated as an external user, and the "Create a free team" option should be available (assuming they don't have one).
  • Scenario 6: Server-Side Validation Bypass Attempt

    • Given: An internal user is logged in, for whom the "Create free team" button is disabled on the UI.
    • When: The test uses page.evaluate() to forcefully enable the disabled "Create free team" button and then clicks it.
    • Then: The form submission should fail.
    • And: An error message should be displayed, or the user should remain on the creation page without a new team being created. The server must reject the request.

3. Playwright Implementation Instructions

  • File Structure: Place all tests in a new file named tests/e2e/team-creation.spec.ts.
  • Authentication: Assume a pre-existing authentication helper function loginAs(page, { email, teams: [] }) is available to mock the user session. You will need to implement or mock this function to set up the test state. The email will determine if the user is internal or external based on the test's environment setup.
  • Data Setup/Teardown: Use test.beforeEach to log in a fresh user for each test to ensure test isolation. For scenarios requiring a pre-existing team, modify the loginAs helper or create a specific setup function.
  • Selectors: Use robust, user-facing selectors. Avoid relying on brittle CSS selectors.
// Example Selectors
const createTeamButton = page.getByRole('button', { name: 'Create team' });
const createTeamDialog = page.getByRole('dialog', { name: 'Create a new team' });
const teamNameInput = page.getByPlaceholder('Team name');

// For selecting plans, prefer data-testid if available, otherwise use text.
const freePlanCard = page.getByTestId('plan-card-free'); // Or a similar stable selector
const selectFreePlanButton = freePlanCard.getByRole('button', { name: 'Create free team' });

const proPlanCard = page.getByTestId('plan-card-pro');
const selectProPlanButton = proPlanCard.getByRole('button', { name: 'Create pro team' });
  • Interactions: Use standard Playwright actions: await page.goto('/teams/create'), await createTeamButton.click(), await teamNameInput.fill('My New Team').

  • Assertions: Verify UI state and application behavior.

// Example Assertions
// Verify visibility and state
await expect(selectFreePlanButton).toBeVisible();
await expect(selectFreePlanButton).toBeEnabled();
await expect(selectFreePlanButton).toBeDisabled();
await expect(createTeamDialog).toBeVisible();

// Verify navigation/result
await expect(page).toHaveURL(/.*/dashboard/my-new-team/);
await expect(page.getByText('Team created successfully')).toBeVisible();

// Verify error state
await expect(page.getByText('Internal users cannot create free teams.')).toBeVisible();

4. MCP Integration Guidelines

Playwright MCP (or Playwright's project configuration) will be used to run these tests with different environment variable settings.

  • Playwright Config: Structure your playwright.config.ts to include different projects for each environment setup.
// playwright.config.ts example
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  // ... other configs
  projects: [
    {
      name: 'default-env',
      use: {
        ...devices['Desktop Chrome'],
        // INTERNAL_USER_EMAIL_DOMAIN is unset by default
      },
      testMatch: /.*.spec.ts/,
    },
    {
      name: 'internal-user-env',
      use: {
        ...devices['Desktop Chrome'],
        // Set the environment variable for internal user tests
        launchOptions: {
          env: {
            ...process.env,
            INTERNAL_USER_EMAIL_DOMAIN: 'my-company.com',
          },
        },
      },
      testMatch: /.*.spec.ts/,
    },
  ],
});
  • Running Tests: Instruct the user on how to run specific test projects.

    • To run tests for internal user scenarios: npx playwright test --project=internal-user-env
    • To run tests for scenarios where the env var is unset: npx playwright test --project=default-env
  • Tagging (Alternative): You can also use test tags (@internal, @external) and filter them, but using Playwright projects is preferred for environment-dependent tests.

5. CI-Ready Code Requirements

  • Test Independence: Ensure each test() is self-contained and can be run in any order or in parallel. All necessary state (user login, team data) must be created within test.beforeEach.
  • Clear Naming: Use descriptive names for test.describe() and test() blocks that clearly state the scenario being tested.
    • Good: test('should disable free team creation for internal users')
    • Bad: test('team test 3')
  • Organization: Group tests logically.
// Example Structure in team-creation.spec.ts
import { test, expect } from '@playwright/test';

test.describe('Team Creation Flow', () => {

  test.describe('As an External User', () => {
    // loginAs external user in beforeEach
    test('should allow creating a first free team', async ({ page }) => {
      // ... test implementation
    });

    test('should prevent creating a second free team', async ({ page }) => {
      // ... test implementation with pre-existing free team
    });
  });

  test.describe('As an Internal User', () => {
    // This describe block should only run in the 'internal-user-env' project
    // You can add a condition: test.skip(process.env.INTERNAL_USER_EMAIL_DOMAIN !== 'my-company.com', 'Internal user tests');

    // loginAs internal user in beforeEach
    test('should not allow creating any free team', async ({ page }) => {
      // ... test implementation
    });

    test('should fail server-side validation when attempting to bypass UI', async ({ page }) => {
      // ... test implementation for Scenario 6
    });
  });

});

Please generate the complete Playwright test file based on these detailed instructions.

-->


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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants