Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
f9964c6
feat(driver): get projectv2 by url and contentid
rulasg Oct 31, 2025
ce1ebae
feat(get-project): Add query parameter
rulasg Oct 31, 2025
ba143f3
feat(Save-ProjectV2toDatabase): update each of the new items and not …
rulasg Nov 3, 2025
e92d555
wip
rulasg Nov 5, 2025
47170c0
feat(database): log database store path during retrieval
rulasg Nov 5, 2025
60cf5e6
refactor(Show-ProjectItem) improve tracing and jumplines
rulasg Nov 10, 2025
44e3b78
feat(mockdatabase): add Update-Mock_DatabaseFileWithReplace to allow …
rulasg Nov 10, 2025
62f27ba
refactor(test): use Update-Mock_DatabaseFileWithReplace in test
rulasg Nov 10, 2025
819e808
faet(test): getproject with query test
rulasg Nov 10, 2025
1a53e94
refactor(graphql): add more tags and refactor querys for clarity
rulasg Nov 10, 2025
5bd7c27
feat(graphql): add updateProjectV2Collaborators mutation and related …
rulasg Nov 12, 2025
a09205e
feat(user): add Invoke-GetUser and Get-User functions for user retrieval
rulasg Nov 12, 2025
27b2fc1
refactor(callAPI): remove Export-ModuleMember statements for clarity
rulasg Nov 12, 2025
32d131d
refactor(invokeRestMethod): comment out function implementation for c…
rulasg Nov 12, 2025
11323cd
refactor(user): update cache check logic in Get-User function
rulasg Nov 12, 2025
8248bfc
refactor(callAPI): allow call with outfile parameter
rulasg Nov 12, 2025
dff98c0
feat(driver): Invoke-UpdateProjectV2Collaborators
rulasg Nov 12, 2025
1244cdd
feat(project): Add-ProjectUser
rulasg Nov 12, 2025
9da3983
feat(test): tests for add-projectUser
rulasg Nov 12, 2025
5f2cd0e
style(header): update author and date formatting in writeHeader2
rulasg Nov 24, 2025
f05e59e
style(use_order): add alias for OpenInBrowser parameter
rulasg Nov 24, 2025
ceb1e0c
Merge branch 'get-item-by' into projectv2ContributionUpdate
rulasg Nov 25, 2025
e0424ee
fix(test): correct formatting in assertions for author and updatedAt
rulasg Nov 25, 2025
18a7e18
refactor(project): enable ValueFromPipelineByPropertyName for paramet…
rulasg Dec 11, 2025
d8f3da0
refactor(issue): add OpenOnCreation parameter to New-ProjectIssueDire…
rulasg Dec 12, 2025
29cf83d
fix(draft-issue): open URL on creation if OpenOnCreation is set
rulasg Dec 19, 2025
1a9f7ba
refactor(issue): correct typo in error message for issue creation
rulasg Dec 20, 2025
bd2ade9
fix(Get-ProjectItemByUrl): return null for missing owner or project n…
rulasg Dec 20, 2025
1a53998
wip
rulasg Dec 24, 2025
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
153 changes: 153 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# ProjectHelper Copilot Instructions

## Project Overview
**ProjectHelper** is a PowerShell module for GitHub Projects interaction via GraphQL API. It provides CLI-like functions to manage GitHub projects, items, users, and collaborators from PowerShell.

## Architecture & Core Patterns

### Module Structure
- **Loading Order** (`ProjectHelper.psm1`): `config` → `helper` → `include` → `private` → `public`
- **Public Functions**: Exported via `Export-ModuleMember -Function <name>` in each file
- **Private Functions**: In `/private` folder, not exported; used internally by public functions
- **Include Files** (`/include`): Shared utilities loaded early; example: `callAPI.ps1` (GraphQL/REST), `MyWrite.ps1` (logging)
- **Driver Functions** (`/public/driver`): Low-level integration with GitHub APIs; marked with comment "integration function not intended for direct user use"

### Key Files & Responsibilities
- `include/callAPI.ps1`: Handles `Invoke-GraphQL` and `Invoke-RestAPI` calls to GitHub
- `helper/invokeCommand.helper.ps1`: Provides `Invoke-MyCommand` for alias-based command dispatch (enables test mocking)
- `include/config.ps1`: Configuration storage in `~/.helpers/ProjectHelper/config/`
- `public/graphql/*.{query,mutant,tag}`: GraphQL template files (fragments and queries)
- `Test/Test.psd1`: Parallel test module with identical structure to main module

### Invocation Pattern (Critical for Testing)
```powershell
# Production: Direct API calls
Invoke-GraphQL -Query $query -Variables $variables

# High-level: Uses Invoke-MyCommand alias dispatch (mockable)
Invoke-MyCommand -Command "findProject" -Parameters @{owner=$Owner; pattern=$Pattern}

# Set custom implementation for testing/mocking:
Set-MyInvokeCommandAlias -Alias $alias -Command $Command
```

## Development Workflows

### Running Tests
```powershell
./test.ps1 # Run all tests
./test.ps1 -ShowTestErrors # Show error details
./test.ps1 -TestName "Test_*" # Run specific test
```

Uses **TestingHelper** module from PSGallery (installed automatically).

### Building & Deploying
```powershell
./build.ps1 # Build module
./deploy.ps1 -VersionTag "v1.0.0" # Deploy to PSGallery
./sync.ps1 # Sync with TestingHelper templates
```

### Debugging
Enable module debug output:
```powershell
Enable-ProjectHelperDebug
Disable-ProjectHelperDebug
```

## Code Patterns & Conventions

### GraphQL Integration
1. Store GraphQL in template files: `/public/graphql/queryName.query` or `.mutant`
2. Retrieve via: `Get-GraphQLString "queryName.query"`
3. Execute: `Invoke-GraphQL -Query $query -Variables $variables`

Example:
```powershell
$query = Get-GraphQLString "findProject.query"
$variables = @{ login = $Owner; pattern = $Pattern }
$response = Invoke-GraphQL -Query $query -Variables $variables
```

### Public vs. Private Functions
- **Public** (`/public`): User-facing, high-level; transform data, handle caching
- **Private** (`/private`): Lower-level helpers; return raw GitHub data
- **Driver** (`/public/driver`): Thin wrappers around API calls; minimal logic

### Command Aliases with Parameter Templates
Use `Set-MyInvokeCommandAlias` for dynamic command dispatch:
```powershell
Set-MyInvokeCommandAlias -Alias "findProject" -Command "Invoke-FindProject -Owner {owner} -Pattern {pattern}"
Invoke-MyCommand -Command "findProject" -Parameters @{owner="foo"; pattern="bar"}
```
This enables mocking in tests without changing implementation.

### Pipeline & Object Transformation
Functions support pipeline input for bulk operations:
```powershell
"user1", "user2" | Add-ProjectUser -Owner $owner -ProjectNumber 123 -Role "WRITER"
```

### Error Handling
- GraphQL errors: Check `$response.errors` before processing
- Include meaningful context in error messages
- Use `Write-MyError`, `Write-MyVerbose`, `Write-MyDebug` for consistent logging

## Testing Patterns

### Test File Location
Test files must mirror the module structure:
- **Source**: `public/code.ps1` → **Test**: `Test/public/code.test.ps1`
- **Source**: `public/driver/invoke-getnode.ps1` → **Test**: `Test/public/driver/invoke-getnode.test.ps1`
- **Source**: `private/dates.ps1` → **Test**: `Test/private/dates.test.ps1`

The folder structure in `Test/` must exactly match the structure in the main module.

### Test Function Naming
- **Format**: `Test_<FunctionName>_<Scenario>`
- **Examples**:
- `Test_FindProject_SUCCESS` (success case)
- `Test_AddProjectUser_SUCCESS_SingleUser` (specific variant)
- `Test_GetProjectIssue_NotFound` (error case)
- **Conventions**:
- Use PascalCase matching the actual function name (e.g., `Get-SomeInfo` → `Test_GetSomeInfo_<tip>`)
- `<tip>` should be a descriptive word indicating the test goal (SUCCESS, NotFound, InvalidInput, etc.)
- Use assertions: `Assert-IsTrue`, `Assert-Contains`, `Assert-AreEqual`, `Assert-Count`, `Assert-IsNull`

### Mock System
Located in `Test/include/`:
- `invokeCommand.mock.ps1`: Mocks `Invoke-MyCommand` calls via JSON files in `Test/private/mocks/`
- `callPrivateContext.ps1`: Execute private functions in module context

### Mock Data Structure
```powershell
# Test/private/mocks/mockCommands.json defines:
{
"Command": "Invoke-GetUser -Handle rulasg",
"FileName": "invoke-GetUser-rulasg.json"
}
```

### Common Test Setup
```powershell
Reset-InvokeCommandMock
Mock_DatabaseRoot
MockCall_GetProject $project -SkipItems
MockCallJson -Command "command" -File "response.json"
```

## Key Dependencies
- **GitHub API**: GraphQL (primary), REST (legacy)
- **TestingHelper**: Test framework from PSGallery
- **InvokeHelper**: Command dispatch/mocking library (external)

## Important Gotchas
1. **Module Loading**: Functions depend on proper load order; new files in `/private` or `/public` auto-loaded
2. **Aliases**: Use `Set-MyInvokeCommandAlias` before calling `Invoke-MyCommand` for consistency
3. **GraphQL Templates**: Fragment files (`.tag`) must match schema; test with actual GitHub API responses
4. **Configuration**: Stored per-module; reset with `Reset-ProjectHelperEnvironment`
5. **PSScriptAnalyzer**: PR checks fail on warnings; review `.github/workflows/powershell.yml` rules

## PR Branch & Active Work
Currently on `projectv2ContributionUpdate` - Implementing project access management with new user collaboration features. Recent changes focus on `Invoke-UpdateProjectV2Collaborators` and `Add-ProjectUser` functions with proper string splitting via `-split` with `[System.StringSplitOptions]::RemoveEmptyEntries`.
222 changes: 222 additions & 0 deletions .github/instructions/test.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
---
applyTo: '**/*.test.ps1'
---

# Test File Creation Guidelines

## Test File Location and Naming

### File Placement
Test files must mirror the exact folder structure of the source code:

- **Source**: `public/code.ps1` → **Test**: `Test/public/code.test.ps1`
- **Source**: `public/driver/invoke-getnode.ps1` → **Test**: `Test/public/driver/invoke-getnode.test.ps1`
- **Source**: `public/issues/Get-ProjectIssue.ps1` → **Test**: `Test/public/issues/Get-ProjectIssue.test.ps1`
- **Source**: `private/dates.ps1` → **Test**: `Test/private/dates.test.ps1`

The folder structure in `Test/` must exactly match the structure in the main module.

### Test Function Naming
- **Format**: `Test_<FunctionName>_<Scenario>`
- **PascalCase**: Match the actual function name (e.g., `Add-ProjectUser` → `Test_AddProjectUser_*`)
- **Scenario Suffix**: Use descriptive words indicating the test goal:
- `SUCCESS` - Happy path / successful execution
- `SUCCESS_SingleUser` - Variant of success with specific condition
- `SUCCESS_MultipleUser` - Another variant with different parameters
- `NotFound` - Resource not found error case
- `InvalidInput` - Invalid parameter error case
- `ERROR` - General error condition

**Examples**:
```powershell
function Test_FindProject_SUCCESS { }
function Test_AddProjectUser_SUCCESS_SingleUser { }
function Test_AddProjectUser_SUCCESS_MultipleUser { }
function Test_GetProjectIssue_NotFound { }
```

## Test Structure - AAA Pattern

All test functions must follow the **Arrange-Act-Assert** pattern with explicit comments:

### 1. ARRANGE Phase
```powershell
# Arrange
Reset-InvokeCommandMock # CRITICAL: Always reset mocks at the start
Mock_DatabaseRoot # Setup test database if needed

$owner = "github"
$projectNumber = 123
```

**Critical Setup Steps**:
- **ALWAYS** call `Reset-InvokeCommandMock` at the very beginning of the Arrange phase
- Setup any mock data using `Mock_*` helpers (e.g., `Mock_DatabaseRoot`, `Get-Mock_Project_700`)
- Define test input parameters and expected values
- Setup mock calls using `MockCallJson` or `MockCall_*` helpers

### 2. ACT Phase
```powershell
# Act
$result = Add-ProjectUser -Owner $owner -ProjectNumber $projectNumber -Handle "testuser" -Role "WRITER"
```

**Important**:
- Execute the function being tested
- Use the parameters and mocks prepared in Arrange phase
- Capture the result for assertions

### 3. ASSERT Phase
```powershell
# Assert
Assert-IsTrue $result
Assert-AreEqual -Expected "expected-value" -Presented $result.id
Assert-Count -Expected 3 -Presented $result
```

**Available Assertions**:
- `Assert-IsTrue` - Verify condition is true
- `Assert-IsNull` - Verify value is null
- `Assert-AreEqual` - Compare expected vs actual value
- `Assert-Contains` - Check if value is in collection
- `Assert-Count` - Verify collection count
- `Assert-NotImplemented` - For stub/empty test implementations

## Mocking Invoke-MyCommand Dependencies

When your test function calls a function that uses `Invoke-MyCommand`, you must mock those dependencies.

### Why Mocking is Critical
The module uses `Invoke-MyCommand` for dynamic command dispatch to enable testing without actual API calls. Any function calling `Invoke-MyCommand` indirectly must have those calls mocked.

### Mock Setup Pattern
```powershell
# Arrange
Reset-InvokeCommandMock

# Mock the Invoke-* driver function calls
MockCallJson -Command "Invoke-GetUser -Handle testuser" -File "invoke-GetUser-testuser.json"
MockCallJson -Command "Invoke-UpdateProjectV2Collaborators -ProjectId PVT_123 -collaborators ""ID123"" -Role ""WRITER""" -File "invoke-UpdateProjectV2Collaborators-ID123.json"

# Act
$result = Add-ProjectUser -Owner "github" -ProjectNumber 123 -Handle "testuser" -Role "WRITER"
```

### Mock Files
Mock response files should be stored in `Test/private/mocks/` and contain realistic GitHub API responses as JSON.

### Common Mock Helpers
- `Reset-InvokeCommandMock` - Clear all mocks (MUST be called first)
- `MockCallJson -Command "..." -File "..."` - Mock a driver function call with JSON response
- `MockCall_GetProject $project -SkipItems` - Pre-configured mock for Get-Project
- `Mock_DatabaseRoot` - Setup database root for project operations
- `Get-Mock_Project_700` - Get pre-configured test project
- `Get-Mock_Users` - Get pre-configured test users

## Empty/Stub Test Implementation

When creating a test function as a placeholder to be implemented later:

```powershell
function Test_SomeFunction_NotYetImplemented {
Assert-NotImplemented
}
```

This allows the test infrastructure to recognize the test while explicitly marking it as not ready.

## Real Test Examples

### Example 1: Simple Success Case
```powershell
function Test_FindProject_SUCCESS {
# Arrange
Reset-InvokeCommandMock
Enable-InvokeCommandAliasModule

$owner = "github"
$pattern = "kk"
$command = 'Invoke-FindProject -Owner {owner} -Pattern "{pattern}" -firstProject 100 -afterProject ""'
$command = $command -replace "{owner}", $owner
$command = $command -replace "{pattern}", $pattern
MockCallJson -Command $command -filename "findprojectwithlist.json"

# Act
$result = Find-Project -Owner $owner -Pattern $pattern

# Assert
Assert-Count -Expected 3 -Presented $result
Assert-AreEqual -Expected "PVT_kwDNJr_OANANzQ" -Presented $result[1].id
}
```

### Example 2: Single vs Multiple User Variants
```powershell
function Test_AddProjectUser_SUCCESS_SingleUser {
# Arrange
Reset-InvokeCommandMock
Mock_DatabaseRoot

$p = Get-Mock_Project_700
$owner = $p.Owner
$projectNumber = $p.Number
$projectId = $p.id
MockCall_GetProject $p -SkipItems

$u = Get-Mock_Users
$userId = $u.u1.id
$userName = $u.u1.name
$role = "WRITER"

MockCallJson -Command "Invoke-GetUser -Handle $userName" -File $u.u1.file
MockCallJson -Command "Invoke-UpdateProjectV2Collaborators -ProjectId $projectId -collaborators ""$userId"" -Role ""$role""" -File "invoke-UpdateProjectV2Collaborators-$userId.json"

# Act
$result = Add-ProjectUser -Owner $owner -ProjectNumber $projectNumber -Handle $userName -Role $role

# Assert
Assert-IsTrue $result
}

function Test_AddProjectUser_SUCCESS_MultipleUser {
# Arrange
Reset-InvokeCommandMock
Mock_DatabaseRoot

$p = Get-Mock_Project_700
$owner = $p.Owner
$projectNumber = $p.Number
$projectId = $p.id
MockCall_GetProject $p -SkipItems

$u = Get-Mock_Users
$userId1 = $u.u1.id
$userName1 = $u.u1.name
$userId2 = $u.u2.id
$userName2 = $u.u2.name
$userNames = "$userName1", "$userName2"
$usersIds = "$userId1 $userId2"
$role = "WRITER"

MockCallJson -Command "Invoke-GetUser -Handle $userName1" -File $u.u1.file
MockCallJson -Command "Invoke-GetUser -Handle $userName2" -File $u.u2.file
MockCallJson -Command "Invoke-UpdateProjectV2Collaborators -ProjectId $projectId -collaborators ""$usersIds"" -Role ""$role""" -File "invoke-UpdateProjectV2Collaborators-$userId1-$userId2.json"

# Act
$result = $userNames | Add-ProjectUser -Owner $owner -ProjectNumber $projectNumber -Role $role

# Assert
Assert-IsTrue $result
}
```

## Checklist Before Submitting Test

- [ ] Test file is in correct location: `Test/<source-path>/<filename>.test.ps1`
- [ ] Test function named: `Test_<FunctionName>_<Scenario>`
- [ ] Three phases clearly marked with comments: `# Arrange`, `# Act`, `# Assert`
- [ ] `Reset-InvokeCommandMock` called at start of Arrange phase
- [ ] All `Invoke-MyCommand` calls mocked with `MockCallJson`
- [ ] Assertions verify expected behavior
- [ ] Empty test uses `Assert-NotImplemented`
- [ ] No actual API calls made (all mocked)
Loading
Loading