Skip to main content
Thank you for your interest in contributing to Intune Commander! This guide provides everything you need to know about submitting pull requests and maintaining code quality.

Pull Request Guidelines

PR Title Convention

Use conventional commit format with priority labels:
<type>(<scope>): <description> [Priority]
Examples:
feat(auth): add certificate authentication [P1]
fix(ui): resolve dark mode contrast issue [P1]
docs: update ARCHITECTURE.md with cache patterns [P2]
refactor(services): extract common pagination helper [P3]
test(core): add unit tests for ProfileService [P2]
Types:
  • feat - New feature
  • fix - Bug fix
  • docs - Documentation only
  • refactor - Code restructuring without behavior change
  • test - Adding or updating tests
  • style - Code style changes (formatting, etc.)
  • perf - Performance improvements
  • chore - Maintenance tasks, dependencies
Priorities:
  • [P1] - Critical: Security issues, broken features, blocking bugs
  • [P2] - Important: Significant improvements, major bugs, essential docs
  • [P3] - Enhancement: Nice-to-have features, optimizations, minor fixes

PR Description Template

Every PR should include these sections:
## Summary
Brief description of what this PR does and why.

## Changes
- Bullet list of specific modifications
- Each change on its own line
- Link to related issues if applicable

## Test Plan
- [ ] `dotnet build` - no errors/warnings
- [ ] `dotnet test` - all tests pass
- [ ] Manual testing: describe steps taken
- [ ] Specific scenarios verified

## Breaking Changes
If any breaking changes exist:
- Describe what breaks
- Provide migration path
- Update CHANGELOG.md

## Documentation
- [ ] Updated relevant documentation
- [ ] Added code comments where needed
- [ ] Updated CHANGELOG.md if user-facing

Before Submitting

  1. Build and Test Locally
    dotnet build
    dotnet test
    
  2. Follow Code Conventions
    • Review .github/copilot-instructions.md
    • Review CLAUDE.md for architectural patterns
    • Match existing code style
  3. Update Documentation
    • Update CHANGELOG.md for user-facing changes
    • Update docs/ARCHITECTURE.md if changing patterns
    • Add XML doc comments for public APIs
  4. Commit Messages
    • Use meaningful commit messages
    • Reference issue numbers where applicable
    • Keep commits focused and atomic

Code Standards

C# Conventions

Namespaces:
  • Core library: Intune.Commander.Core.*
  • Desktop app: Intune.Commander.Desktop.*
  • File-scoped namespaces: namespace Intune.Commander.Core.Services;
Nullable Reference Types:
  • Enabled everywhere
  • Use ? for nullable types
  • Use ! null-forgiving operator sparingly
Naming:
  • Private fields: _camelCase
  • Public members: PascalCase
  • Interfaces: IInterfaceName
  • Async methods: Always end with Async
ViewModels:
  • Must be partial class for CommunityToolkit.Mvvm source generators
  • Inherit from ObservableObject
  • Use [ObservableProperty] and [RelayCommand] attributes
Example:
namespace Intune.Commander.Desktop.ViewModels;

public partial class MyViewModel : ObservableObject
{
    [ObservableProperty]
    private string _displayName = string.Empty;
    
    [RelayCommand]
    private async Task LoadDataAsync()
    {
        // Implementation
    }
}

Graph API Patterns

Manual Pagination (Required): Never use PageIterator - it silently truncates results on some tenants.
public async Task<List<SomeType>> ListItemsAsync(
    CancellationToken cancellationToken = default)
{
    var result = new List<SomeType>();

    var response = await _graphClient.SomeEndpoint
        .GetAsync(req => req.QueryParameters.Top = 200, cancellationToken);

    while (response != null)
    {
        if (response.Value != null)
            result.AddRange(response.Value);

        if (!string.IsNullOrEmpty(response.OdataNextLink))
        {
            response = await _graphClient.SomeEndpoint
                .WithUrl(response.OdataNextLink)
                .GetAsync(cancellationToken: cancellationToken);
        }
        else
        {
            break;
        }
    }

    return result;
}
All async methods must:
  • End with Async suffix
  • Return Task or Task<T>
  • Accept CancellationToken cancellationToken = default as last parameter
  • Be async all the way up (no .Result, .Wait(), or .GetAwaiter().GetResult())

UI Thread Rules

CRITICAL: Never block the UI thread.
// ❌ BAD - blocks UI thread
var result = SomeAsyncMethod().Result;

// ✅ GOOD - async all the way
var result = await SomeAsyncMethod();

// ✅ GOOD - fire and forget for non-blocking loads
_ = LoadDataAsync();

Service Implementation Pattern

Every Intune object type service follows this pattern:
public interface ISomeService
{
    Task<List<SomeType>> ListAsync(CancellationToken cancellationToken = default);
    Task<SomeType?> GetByIdAsync(string id, CancellationToken cancellationToken = default);
    Task<SomeType> CreateAsync(SomeType item, CancellationToken cancellationToken = default);
    Task<SomeType> UpdateAsync(SomeType item, CancellationToken cancellationToken = default);
    Task DeleteAsync(string id, CancellationToken cancellationToken = default);
    Task<List<Assignment>> GetAssignmentsAsync(string id, CancellationToken cancellationToken = default);
}

public class SomeService : ISomeService
{
    private readonly GraphServiceClient _graphClient;

    public SomeService(GraphServiceClient graphClient)
    {
        _graphClient = graphClient;
    }

    // Implementation with manual pagination on List methods
}
See Services documentation for complete service patterns.

Testing Requirements

Unit Tests Are Required

Every new or changed code must include tests. PRs without adequate test coverage will not be merged. Coverage Requirement: 40% line coverage (enforced in CI)

Unit Test Conventions

Framework: xUnit with [Fact] or [Theory] Test Structure:
[Fact]
public async Task MethodName_Scenario_ExpectedBehavior()
{
    // Arrange
    var service = new SomeService(mockClient);

    // Act
    var result = await service.SomeMethodAsync();

    // Assert
    Assert.NotNull(result);
}
Naming:
  • Test class: {ClassUnderTest}Tests
  • Test method: {MethodName}_{Scenario}_{ExpectedBehavior}
  • Test file mirrors source structure
Coverage:
  • Happy path
  • Edge cases
  • Null handling
  • Exception scenarios

Mocking with NSubstitute

Important: GraphServiceClient is NOT mockable (sealed SDK). Use reflection-based contract tests for Graph services. NSubstitute patterns:
// Return values
svc.MethodAsync(Arg.Any<T>(), Arg.Any<CancellationToken>())
   .Returns(Task.FromResult(result));

// Argument capture
T? captured = null;
svc.MethodAsync(Arg.Do<T>(x => captured = x), Arg.Any<CancellationToken>())
   .Returns(Task.FromResult(result));
Assert.NotNull(captured);

// Call verification
await svc.Received(1).MethodAsync(expectedArg, Arg.Any<CancellationToken>());

// No-call assertion
svc.DidNotReceive().MethodAsync(Arg.Any<string>(), Arg.Any<CancellationToken>());

Integration Tests

For tests that require actual Graph API calls:
[Trait("Category", "Integration")]
public class SomeServiceIntegrationTests : GraphIntegrationTestBase
{
    [Fact]
    public async Task SomeMethod_ReturnsData()
    {
        if (ShouldSkip()) return; // Graceful skip if no credentials
        
        var service = new SomeService(GraphClient);
        var result = await service.SomeMethodAsync();
        
        Assert.NotNull(result);
    }
}
Always tag integration tests:
[Trait("Category", "Integration")]
CRUD tests:
  • Use IntTest_AutoCleanup_ prefix for created objects
  • Clean up in finally blocks
  • Read-only tests are safe for any tenant
See Testing documentation for complete testing guide.

Documentation Standards

Code Comments

XML Doc Comments for Public APIs:
/// <summary>
/// Gets a device configuration by ID.
/// </summary>
/// <param name="id">The configuration ID.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>The configuration, or null if not found.</returns>
public async Task<DeviceConfiguration?> GetDeviceConfigurationAsync(
    string id, 
    CancellationToken cancellationToken = default)
{
    // Implementation
}
Inline Comments:
  • Use // comments sparingly
  • Only for complex logic that isn’t self-documenting
  • Code should be self-documenting where possible

Documentation Files

CHANGELOG.md:
  • User-facing changes
  • Release notes
  • Breaking changes
docs/ARCHITECTURE.md:
  • Architectural decisions
  • Design patterns
  • Technical constraints
README.md:
  • Getting started
  • Build instructions
  • Quick reference
PR_STATUS.md:
  • Current PR organization
  • Merge order
  • Dependencies

Review Process

What Reviewers Look For

  1. Correctness: Does it work as intended?
  2. Tests: Are there sufficient tests?
  3. Code Quality: Is it maintainable and readable?
  4. Documentation: Are changes documented?
  5. Breaking Changes: Are they necessary and documented?
  6. Performance: Any obvious performance issues?
  7. Security: No credentials, sensitive data, or vulnerabilities?

Addressing Review Feedback

  • Respond to all comments
  • Make requested changes or explain why not
  • Push new commits (don’t force-push during review)
  • Re-request review after addressing feedback

Git Workflow

Branching

Never commit directly to main. All changes go through feature branches and pull requests. Branch naming:
  • Feature: feature/description (e.g., feature/wave7-scripts)
  • Fix: fix/description (e.g., fix/lazy-load-guard)
  • Docs: docs/description (e.g., docs/update-architecture)
Creating a branch:
git checkout main
git pull
git checkout -b feature/my-feature

Commit Messages

Format:
<type>(<scope>): <description>

Optional longer explanation.

Fixes #123
Examples:
feat(auth): add support for client certificate authentication
fix(ui): resolve navigation bug when switching profiles
test(services): add coverage for CacheService edge cases

Creating Pull Requests

# Push your branch
git push -u origin feature/my-feature

# Create PR with gh CLI
gh pr create --title "feat(auth): add certificate auth [P2]" --body "$(cat <<'EOF'
## Summary
Adds support for client certificate authentication.

## Changes
- Implemented ClientCertificateCredential support
- Added certificate selection UI
- Updated ProfileService schema

## Test Plan
- [x] dotnet build - no errors
- [x] dotnet test - all tests pass
- [x] Manual testing with certificate

## Documentation
- [x] Updated ARCHITECTURE.md
- [x] Added XML doc comments
EOF
)"

Wave Implementation

The project uses a Wave system for implementing new Intune object types. See docs/issues/ for detailed tracking:
  • Wave 1: Endpoint Security, Admin Templates, Enrollment
  • Wave 2: App Protection, Managed App Configs
  • Wave 3: Tenant Administration
  • Wave 4: Autopilot, Device Management
  • Wave 5: Conditional Access, Identity
If contributing a new service, refer to the appropriate Wave document for requirements and status.

Getting Help

  • Questions about implementation: Check CLAUDE.md and docs/ARCHITECTURE.md
  • Questions about PR process: See PR_STATUS.md
  • Questions about code patterns: See .github/copilot-instructions.md
  • Stuck on something?: Open a draft PR and ask for guidance

PowerShell Scripts

If contributing PowerShell scripts:
  • Use ASCII-only characters - No Unicode decorations (e.g., ━─→✓✗○—)
  • Save .ps1 files with ASCII encoding
  • Target PowerShell 5.1+ compatibility
  • Unicode breaks PowerShell 5.1 parsing

License

By contributing to Intune Commander, you agree that your contributions will be licensed under the MIT License.

Build docs developers (and LLMs) love