Skip to content
Merged
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
18 changes: 18 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,19 @@ jobs:
- name: Clone
uses: actions/checkout@v6

- name: Start MinIO
run: |
docker run -d --name minio \
-p 9090:9000 \
-e MINIO_ROOT_USER=minioadmin \
-e MINIO_ROOT_PASSWORD=minioadmin \
minio/minio server /data
timeout 30 bash -c 'until curl -sf http://127.0.0.1:9090/minio/health/live; do sleep 1; done'
curl -sL https://dl.min.io/client/mc/release/linux-amd64/mc -o /usr/local/bin/mc
chmod +x /usr/local/bin/mc
mc alias set ci http://127.0.0.1:9090 minioadmin minioadmin
mc mb ci/vms-media

- name: Find tests
run: |
echo "Finding tests"
Expand Down Expand Up @@ -96,10 +109,15 @@ jobs:
env:
CI: 'Yes'

- name: Configure VMS Settings for MinIO
working-directory: /home/runner/frappe-bench
run: bench --site test_site execute vms.seed.seed_test_settings

- name: Run Tests
working-directory: /home/runner/frappe-bench
run: |
bench --site test_site set-config allow_tests true
bench --site test_site run-tests --app vms
env:
TYPE: server
VMS_S3_ENDPOINT_URL: http://127.0.0.1:9090
21 changes: 21 additions & 0 deletions .github/workflows/ui-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,21 @@ jobs:
- name: Clone
uses: actions/checkout@v4

- name: Start MinIO
run: |
docker run -d --name minio \
-p 9090:9000 \
-e MINIO_ROOT_USER=minioadmin \
-e MINIO_ROOT_PASSWORD=minioadmin \
minio/minio server /data
# Wait for MinIO to be ready
timeout 30 bash -c 'until curl -sf http://127.0.0.1:9090/minio/health/live; do sleep 1; done'
# Create test bucket using mc
curl -sL https://dl.min.io/client/mc/release/linux-amd64/mc -o /usr/local/bin/mc
chmod +x /usr/local/bin/mc
mc alias set ci http://127.0.0.1:9090 minioadmin minioadmin
mc mb ci/vms-media

- name: Setup Python
uses: actions/setup-python@v5
with:
Expand Down Expand Up @@ -102,6 +117,10 @@ jobs:
bench --site vms.test set-config allow_tests true
bench --site vms.test set-config host_name "http://vms.test:8000"

- name: Configure VMS Settings for MinIO
working-directory: /home/runner/frappe-bench
run: bench --site vms.test execute vms.seed.seed_test_settings

- name: Start Frappe Server
working-directory: /home/runner/frappe-bench
run: |
Expand All @@ -111,6 +130,8 @@ jobs:
echo "Waiting for Frappe server to start..."
timeout 60 bash -c 'until curl -s http://vms.test:8000 > /dev/null; do sleep 2; done'
echo "Frappe server is ready!"
env:
VMS_S3_ENDPOINT_URL: http://127.0.0.1:9090

- name: Install Playwright
run: |
Expand Down
161 changes: 160 additions & 1 deletion e2e/helpers/vms.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { APIRequestContext } from "@playwright/test";
import { createDoc, deleteDoc, getDoc, getList } from "./frappe";
import { callMethod, createDoc, deleteDoc, getDoc, getList } from "./frappe";

/**
* VMS Project document interface.
Expand Down Expand Up @@ -151,3 +151,162 @@ export async function cleanupTestProjects(
}
}
}

// ---------------------------------------------------------------------------
// Upload helpers
// ---------------------------------------------------------------------------

interface UploadUrlResponse {
upload_url: string;
r2_key: string;
asset_name: string;
}

interface ConfirmUploadResponse {
status: string;
asset_name: string;
}

interface ViewUrlResponse {
url: string;
}

interface DownloadUrlResponse {
url: string;
}

/**
* Request a presigned upload URL from the VMS API.
*/
export async function getUploadUrl(
request: APIRequestContext,
options: {
file_name: string;
content_type: string;
project?: string;
category?: string;
},
): Promise<UploadUrlResponse> {
return callMethod<UploadUrlResponse>(request, "vms.api.get_upload_url", {
file_name: options.file_name,
content_type: options.content_type,
project: options.project,
category: options.category,
});
}

/**
* Upload a buffer to the presigned URL (PUT request directly to object storage).
*/
export async function uploadToPresignedUrl(
request: APIRequestContext,
uploadUrl: string,
content: Buffer,
contentType: string,
): Promise<void> {
const response = await request.put(uploadUrl, {
data: content,
headers: {
"Content-Type": contentType,
},
});

if (!response.ok()) {
throw new Error(
`Upload to presigned URL failed: ${response.status()} ${response.statusText()}`,
);
}
}

/**
* Confirm an upload after the file has been PUT to object storage.
*/
export async function confirmUpload(
request: APIRequestContext,
assetName: string,
fileSize: number,
): Promise<ConfirmUploadResponse> {
return callMethod<ConfirmUploadResponse>(
request,
"vms.api.confirm_upload",
{
asset_name: assetName,
file_size: fileSize,
},
);
}

/**
* Get a presigned view URL for an asset.
*/
export async function getViewUrl(
request: APIRequestContext,
assetName: string,
): Promise<ViewUrlResponse> {
return callMethod<ViewUrlResponse>(request, "vms.api.get_view_url", {
asset_name: assetName,
});
}

/**
* Get a presigned download URL for an asset.
*/
export async function getDownloadUrl(
request: APIRequestContext,
assetName: string,
): Promise<DownloadUrlResponse> {
return callMethod<DownloadUrlResponse>(
request,
"vms.api.get_download_url",
{
asset_name: assetName,
},
);
}

/**
* Full upload flow: get presigned URL → PUT file → confirm upload.
* Returns the asset name and r2 key.
*/
export async function uploadTestFile(
request: APIRequestContext,
options: {
file_name?: string;
content?: Buffer;
content_type?: string;
project?: string;
category?: string;
} = {},
): Promise<{ asset_name: string; r2_key: string }> {
const fileName = options.file_name || `test-file-${Date.now()}.mp4`;
const contentType = options.content_type || "video/mp4";
const content = options.content || Buffer.from("E2E test file content");

// Step 1: Get presigned upload URL
const { upload_url, r2_key, asset_name } = await getUploadUrl(request, {
file_name: fileName,
content_type: contentType,
project: options.project,
category: options.category,
});

// Step 2: PUT file to object storage
await uploadToPresignedUrl(request, upload_url, content, contentType);

// Step 3: Confirm the upload
await confirmUpload(request, asset_name, content.length);

return { asset_name, r2_key };
}

/**
* Delete an asset via the VMS API (cleans up both DB record and object storage).
*/
export async function deleteAsset(
request: APIRequestContext,
assetName: string,
): Promise<void> {
await callMethod(request, "vms.api.delete_asset", {
asset_name: assetName,
});
}
Loading
Loading