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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
424 changes: 424 additions & 0 deletions .github/copilot-instructions.md

Large diffs are not rendered by default.

16 changes: 11 additions & 5 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ name: Node PR Lint, Build and Test
# This workflow handles three scenarios:
#
# 1. Push to `next`, `dev/*`, or `feature/*` branches:
# - Runs `code-quality-and-tests` and `integration-tests`
# - Skips `build-and-package` to save resources and focus on code quality
# - Runs `code-quality-and-tests`
# - Skips `integration-tests` and `build-and-package` to save resources and focus on code quality
#
# 2. Pull Requests to `main` or `next`:
# - Runs all jobs: code checks, tests, and packaging
# - Runs all jobs: code checks, tests, integration tests, and packaging
# - Ensures complete validation including artifact generation before merge
#
# 3. Push to `main`:
Expand Down Expand Up @@ -77,6 +77,13 @@ jobs:
integration-tests:
name: Integration Tests
runs-on: ubuntu-latest
needs: [code-quality-and-tests]
if: |
github.ref == 'refs/heads/main' ||
(startsWith(github.ref, 'refs/pull/') && (
github.base_ref == 'main' ||
github.base_ref == 'next'
))
defaults:
run:
working-directory: '.'
Expand Down Expand Up @@ -109,8 +116,7 @@ jobs:
build-and-package:
name: Build & Package Artifacts
runs-on: ubuntu-latest
needs: [code-quality-and-tests, integration-tests]

needs: [code-quality-and-tests]
if: |
github.ref == 'refs/heads/main' ||
(startsWith(github.ref, 'refs/pull/') && (
Expand Down
53 changes: 41 additions & 12 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,54 @@

Thank you for your interest in contributing to the **DocumentDB for VS Code** extension. This guide helps you set up your development environment and configure Visual Studio Code to effectively contribute to the extension.

The document consists of two sections:
The document consists of three sections:

1. [Machine Setup](#1-machine-setup)
2. [VS Code Configuration](#2-vs-code-configuration)
1. [Branching Strategy](#1-branching-strategy)
2. [Machine Setup](#2-machine-setup)
3. [VS Code Configuration](#3-vs-code-configuration)

## 1. Machine Setup
## 1. Branching Strategy

The repository follows a structured branching strategy to ensure smooth development and release processes:

- **`main`** — Production-ready code. All releases are tagged here.
- **`next`** — Staging for the upcoming release. Completed features are merged here.
- **`dev/<user>/<feature>`** — Individual feature branches for personal development.
- **`feature/<big-feature>`** — Shared branches for large features requiring collaboration.

### Pull Requests and GitHub Actions

GitHub Actions are configured to perform automated checks on the repository. The intensity of these checks depends on the target branch:

1. **Push to `next`, `dev/*`, or `feature/*` branches**:

- Runs basic code quality checks and tests.
- Skips resource-intensive jobs like integration tests and packaging to focus on code validation.

2. **Pull Requests to `main` or `next`**:

- Executes all jobs, including code checks, tests, and packaging.
- Ensures complete validation before merging, including artifact generation.

3. **Push to `main`**:
- Runs the full workflow for release validation and artifact generation.

This setup ensures that contributions are thoroughly validated while optimizing resource usage during development.

## 2. Machine Setup

Follow these instructions to configure your machine for JavaScript/TypeScript development using Windows Subsystem for Linux (WSL2) and Visual Studio Code.

> This setup assumes you're using WSL2 on Windows. However, you can use a Linux or Windows setup exclusively if preferred.

### 1.1. Install Ubuntu 22.\* on Windows
### 2.1. Install Ubuntu 22.\* on Windows

- Install **Ubuntu 22.\*** from the Microsoft Store and launch it to configure your Linux user account.

- Your development environment and tools will reside within `WSL2`.
- VS Code integrates seamlessly with `WSL2` instances, enabling smooth development from your Windows machine.

### 1.2. Update Ubuntu Packages
### 2.2. Update Ubuntu Packages

Open your Ubuntu terminal and run:

Expand All @@ -29,7 +58,7 @@ sudo apt update
sudo apt upgrade
```

### 1.3. Install Node.js with FNM (Fast Node Manager)
### 2.3. Install Node.js with FNM (Fast Node Manager)

- `FNM` helps with installing and switching Node.js versions easily. This is useful for testing compatibility across different Node.js versions.

Expand All @@ -44,21 +73,21 @@ fnm default 22
node --version
```

### 1.4. Install TypeScript Globally (optional)
### 2.4. Install TypeScript Globally (optional)

You can install TypeScript globally:

```bash
npm install -g typescript
```

## 2. VS Code Configuration
## 3. VS Code Configuration

This section explains how to clone the **DocumentDB for VS Code** repository and set up Visual Studio Code for development and debugging.

### 2.1. Steps to Clone and Set Up Repository
### 3.1. Steps to Clone and Set Up Repository

1. Ensure you have completed the [Machine Setup](#1-machine-setup) steps.
1. Ensure you have completed the [Machine Setup](#2-machine-setup) steps.

2. Fork or directly clone the official repository:

Expand All @@ -79,7 +108,7 @@ npm install
npm run build
```

### 2.2. Launching and Debugging in VS Code
### 3.2. Launching and Debugging in VS Code

To effectively isolate development environments, it is beneficial to create and use a separate VS Code profile.

Expand Down
206 changes: 206 additions & 0 deletions src/services/tasks/DummyTask.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { TaskState } from '../taskService';
import { DummyTask } from './DummyTask';

describe('DummyTask', () => {
let task: DummyTask;

beforeEach(() => {
task = new DummyTask('Test Dummy Task');
});

afterEach(async () => {
await task.delete();
});

describe('constructor', () => {
it('should initialize with correct properties', () => {
const status = task.getStatus();

expect(task.id).toMatch(/^dummy-task-\d+$/);
expect(task.type).toBe('dummy-task');
expect(task.name).toBe('Test Dummy Task');
expect(status.state).toBe(TaskState.Pending);
expect(status.progress).toBe(0);
expect(status.message).toBe('Task created and ready to start');
});

it('should generate unique IDs for multiple instances', () => {
const task1 = new DummyTask();
const task2 = new DummyTask();

expect(task1.id).not.toBe(task2.id);

// Cleanup
void task1.delete();
void task2.delete();
});

it('should use default name when none provided', () => {
const defaultTask = new DummyTask();
expect(defaultTask.name).toMatch(/^Dummy Task \d+$/);

void defaultTask.delete();
});
});

describe('start', () => {
it('should transition through initialization states', async () => {
const startPromise = task.start();

// Should be initializing briefly
await new Promise(resolve => setTimeout(resolve, 50));
let status = task.getStatus();
expect(status.state).toBe(TaskState.Initializing);
expect(status.message).toBe('Initializing task...');

await startPromise;

// Should now be running
status = task.getStatus();
expect(status.state).toBe(TaskState.Running);
expect(status.message).toBe('Task execution started');
});

it('should not allow starting twice', async () => {
await task.start();

await expect(task.start()).rejects.toThrow('Cannot start task in state: running');
});

it('should update progress over time', async () => {
await task.start();

// Wait for at least one progress update
await new Promise(resolve => setTimeout(resolve, 1100));

const status = task.getStatus();
expect(status.state).toBe(TaskState.Running);
expect(status.progress).toBeGreaterThan(0);
expect(status.progress).toBeLessThanOrEqual(100);
expect(status.message).toContain('Processing...');
expect(status.message).toContain('% complete');
});

it('should complete after approximately 10 seconds', async () => {
await task.start();

// Wait for completion (with some buffer for timing)
await new Promise(resolve => setTimeout(resolve, 11000));

const status = task.getStatus();
expect(status.state).toBe(TaskState.Completed);
expect(status.progress).toBe(100);
expect(status.message).toBe('Task completed successfully');
}, 15000); // Increase Jest timeout for this test
});

describe('stop', () => {
it('should stop a running task', async () => {
await task.start();

// Let it run for a bit
await new Promise(resolve => setTimeout(resolve, 1100));

await task.stop();

const status = task.getStatus();
expect(status.state).toBe(TaskState.Stopped);
expect(status.message).toBe('Task was stopped');
});

it('should handle stopping before start', async () => {
await task.stop();

const status = task.getStatus();
expect(status.state).toBe(TaskState.Stopped);
});

it('should handle multiple stop calls', async () => {
await task.start();
await task.stop();

// Second stop should not throw
await expect(task.stop()).resolves.toBeUndefined();

const status = task.getStatus();
expect(status.state).toBe(TaskState.Stopped);
});

it('should preserve progress when stopped', async () => {
await task.start();

// Wait for some progress
await new Promise(resolve => setTimeout(resolve, 2100));

const progressBeforeStop = task.getStatus().progress;
await task.stop();

const status = task.getStatus();
expect(status.progress).toBe(progressBeforeStop);
});
});

describe('delete', () => {
it('should stop and cleanup the task', async () => {
await task.start();

// Let it run briefly
await new Promise(resolve => setTimeout(resolve, 500));

await task.delete();

const status = task.getStatus();
expect(status.state).toBe(TaskState.Stopped);
});

it('should handle delete on pending task', async () => {
await expect(task.delete()).resolves.toBeUndefined();

const status = task.getStatus();
expect(status.state).toBe(TaskState.Stopped);
});
});

describe('abort signal handling', () => {
it('should handle abort during initialization', async () => {
const startPromise = task.start();

// Give it a tiny bit of time to enter initialization, then stop
await new Promise(resolve => setTimeout(resolve, 10));
await task.stop();
await startPromise;

const status = task.getStatus();
// The task might be in running state if start() completed before stop()
// or stopped if stop() was processed first
expect([TaskState.Stopped, TaskState.Running].includes(status.state)).toBe(true);
});

it('should handle abort during execution', async () => {
await task.start();

// Let it start running
await new Promise(resolve => setTimeout(resolve, 1100));

await task.stop();

const status = task.getStatus();
expect(status.state).toBe(TaskState.Stopped);
});
});

describe('getStatus', () => {
it('should return a copy of status to prevent mutation', () => {
const status1 = task.getStatus();
const status2 = task.getStatus();

expect(status1).toEqual(status2);
expect(status1).not.toBe(status2); // Different object references
});
});
});
Loading
Loading