chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:46:20 +08:00
commit eb21b65815
830 changed files with 113645 additions and 0 deletions
+147
View File
@@ -0,0 +1,147 @@
const express = require('express')
const app = express()
const port = 43434
// Middleware for JSON parsing
app.use(express.json())
// Middleware to check Bearer token
app.use((req, res, next) => {
const authHeader = req.headers.authorization
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Missing or invalid authorization header' })
}
next()
})
// Helper function to extract user input from message
function extractUserInput (message) {
const match = message.match(/user input: "([^"]*)"/)
return match ? match[1] : ''
}
// Generate 5 fake commands that start with user input
function generateTestCommands (userInput) {
return [
`${userInput}-test-command-1`,
`${userInput}-test-command-2`,
`${userInput}-test-command-3`,
`${userInput}-test-command-4`,
`${userInput}-test-command-5`
]
}
// Generate a test bookmark based on description
function generateTestBookmark (description) {
return {
title: 'Test Server',
host: 'test.example.com',
port: 22,
username: 'testuser',
type: 'ssh',
description
}
}
// Chat completions endpoint
app.post('/chat/completions', (req, res) => {
const { messages, stream } = req.body
const lastMessage = messages[messages.length - 1].content
if (lastMessage.includes('give me max 5 command suggestions for user input')) {
const userInput = extractUserInput(lastMessage)
const mockSuggestions = generateTestCommands(userInput)
// Command suggestions are always non-streaming
res.json({
choices: [{
message: {
content: JSON.stringify(mockSuggestions)
}
}]
})
} else if (lastMessage.includes('Generate the bookmark JSON')) {
const bookmarkData = generateTestBookmark(lastMessage)
res.json({
choices: [{
message: {
content: JSON.stringify(bookmarkData, null, 2)
}
}]
})
} else if (stream) {
// Handle streaming response
res.writeHead(200, {
'Content-Type': 'text/plain',
'Transfer-Encoding': 'chunked'
})
const mockResponse = '# Response to your query\n\n' +
'Here is a sample response with different markdown elements:\n\n' +
'## Code Example\n' +
'```javascript\n' +
'console.log("Hello World!");\n' +
'```\n\n' +
'## List Example\n' +
'- Item 1\n' +
'- Item 2\n' +
'- Item 3\n\n' +
'## Text Formatting\n' +
'**Bold text** and *italic text*\n\n' +
'> This is a blockquote\n\n' +
'[This is a link](https://example.com)'
// Split response into chunks and send as streaming data
const chunks = mockResponse.split(' ')
let index = 0
const sendChunk = () => {
if (index < chunks.length) {
const content = chunks[index] + (index < chunks.length - 1 ? ' ' : '')
const streamData = {
choices: [{
delta: {
content
}
}]
}
res.write(`data: ${JSON.stringify(streamData)}\n\n`)
index++
setTimeout(sendChunk, 50) // Simulate streaming delay
} else {
res.write('data: [DONE]\n\n')
res.end()
}
}
sendChunk()
} else {
// Handle non-streaming response
const mockResponse = '# Response to your query\n\n' +
'Here is a sample response with different markdown elements:\n\n' +
'## Code Example\n' +
'```javascript\n' +
'console.log("Hello World!");\n' +
'```\n\n' +
'## List Example\n' +
'- Item 1\n' +
'- Item 2\n' +
'- Item 3\n\n' +
'## Text Formatting\n' +
'**Bold text** and *italic text*\n\n' +
'> This is a blockquote\n\n' +
'[This is a link](https://example.com)'
res.json({
choices: [{
message: {
content: mockResponse
}
}]
})
}
})
app.listen(port, '127.0.0.1', () => {
console.log(`Mock AI API server running at http://127.0.0.1:${port}`)
})
+14
View File
@@ -0,0 +1,14 @@
const { resolve } = require('path')
const cwd = process.cwd()
module.exports = {
env: {
...process.env,
NODE_TEST: 'yes'
},
args: [
resolve(cwd, 'work/app'),
'--disable-gpu',
'--disable-dev-shm-usage'
]
}
+42
View File
@@ -0,0 +1,42 @@
const delay = require('./wait')
const { expect } = require('./expect')
exports.basicTerminalTest = async (client, cmd) => {
async function focus () {
client.click('.session-current .term-wrap')
}
async function selectAll () {
await client.keyboard.press('Meta+A')
await delay(401)
}
async function copy () {
await selectAll()
await client.keyboard.press('Meta+C')
await delay(401)
}
await copy()
const text1 = await client.readClipboard()
await delay(301)
await focus()
await delay(1010)
await client.keyboard.type(cmd)
await client.keyboard.press('Enter')
await delay(1011)
await copy()
await delay(101)
const text2 = await client.readClipboard()
expect(text1.trim().length).lessThan(text2.trim().length)
}
exports.getTerminalContent = async function (client) {
await client.click('.session-current .term-wrap')
await delay(300)
await client.keyboard.press('Meta+A')
await delay(300)
await client.keyboard.press('Meta+C')
await delay(300)
const clipboardText = await client.readClipboard()
await client.keyboard.press('Escape')
await delay(300)
return clipboardText
}
+77
View File
@@ -0,0 +1,77 @@
/**
* Standard batch-op example workflow for e2e tests.
* Covers: connect, command (create file + log), sftp_download,
* sftp_upload, command (verify + cleanup).
*/
const {
TEST_HOST,
TEST_PORT,
TEST_USER,
TEST_PASS
} = require('./env')
module.exports = [
{
name: 'Connect SSH',
action: 'connect',
params: {
host: TEST_HOST,
port: parseInt(TEST_PORT, 10),
username: TEST_USER,
authType: 'password',
password: TEST_PASS,
enableSftp: true
}
},
{
name: 'Create 5M Test File',
action: 'command',
prevDelay: 500,
afterDelay: 500,
command: "fallocate -l 5M /tmp/test_5m_file.bin && rm -f /tmp/test_log.log && echo '[LOG] Created 5M test file at $(date)' >> /tmp/test_log.log"
},
{
name: 'Log creation',
action: 'command',
afterDelay: 200,
command: "ls -la /tmp/test_5m_file.bin >> /tmp/test_log.log 2>&1 && echo '[LOG] File size logged at $(date)' >> /tmp/test_log.log"
},
{
name: 'Download 5M File',
action: 'sftp_download',
afterDelay: 200,
remotePath: '/tmp/test_5m_file.bin',
localPath: '/tmp/test_5m_file.bin'
},
{
name: 'Log after download',
action: 'command',
afterDelay: 200,
command: "echo '[LOG] Download complete at $(date)' >> /tmp/test_log.log"
},
{
name: 'Delete Remote 5M File',
action: 'command',
afterDelay: 200,
command: "rm /tmp/test_5m_file.bin && echo '[LOG] Deleted remote 5M file at $(date)' >> /tmp/test_log.log"
},
{
name: 'Upload Downloaded File to Remote',
action: 'sftp_upload',
afterDelay: 200,
localPath: '/tmp/test_5m_file.bin',
remotePath: '/tmp/test_5m_file_uploaded.bin'
},
{
name: 'Log after upload',
action: 'command',
afterDelay: 200,
command: "echo '[LOG] Upload complete at $(date)' >> /tmp/test_log.log"
},
{
name: 'Verify and clean up',
action: 'command',
command: "ls -la /tmp/test_5m_file_uploaded.bin >> /tmp/test_log.log 2>&1 && rm -f /tmp/test_5m_file*.bin && echo '[LOG] Cleaned up at $(date)' >> /tmp/test_log.log"
}
]
+101
View File
@@ -0,0 +1,101 @@
/**
* extend client functions
*/
const {
expect
} = require('@playwright/test')
const delay = require('./wait')
module.exports = (client, app) => {
client.element = (sel) => {
return client.locator(sel).nth(0)
}
client.elements = (selector) => {
return client.locator(selector)
}
client.click = async (sel, n = 0, parent) => {
const sl = sel + ` >> nth=${n}`
let s = client.locator(sl)
if (parent) {
s = s.locator('..')
}
await s.waitFor({ state: 'visible', timeout: 10000 })
await s.click()
}
client.elemExist = async (sel) => {
try {
await client.locator(sel).waitFor({ timeout: 100 })
return true
} catch (error) {
return false
}
}
client.hasElem = async (sel, target = true) => {
const s = client.locator(sel)
const c = await s.count()
expect(!!c).toEqual(target)
}
client.countElem = async (sel) => {
const s = client.locator(sel)
const c = await s.count()
return c
}
client.hasFocus = async (sel) => {
const s = await client.locator(sel).nth(0)
expect(s).toBeFocused()
}
client.getValue = async (sel) => {
const s = await client.locator(sel)
return s.inputValue()
}
client.getText = async (sel) => {
const s = await client.locator(sel).nth(0)
return s.innerText()
}
client.setValue = async (sel, v) => {
const s = client.locator(sel).first()
return s.fill(v)
}
client.getAttribute = async (sel, name) => {
const element = await client.locator(sel).nth(0)
return element.getAttribute(name)
}
client.rightClick = async function (sel, x, y) {
const s = client.locator(sel).first()
await s.waitFor({ state: 'visible', timeout: 10000 })
await s.click({
button: 'right',
position: {
x,
y
}
})
}
client.doubleClick = async function (sel, n = 0) {
const sl = sel + `>> nth=${n}`
const s = client.locator(sl).first()
await s.waitFor({ state: 'visible', timeout: 10000 })
await s.dblclick()
}
client.readClipboard = async () => {
return app.evaluate(async ({ clipboard }) => clipboard.readText())
}
client.writeClipboard = async (clipboardContentToWrite) => {
await app.evaluate(async ({ clipboard }, text) => {
await clipboard.writeText(text)
}, clipboardContentToWrite)
}
client.getAttribute = async (sel, name) => {
return client.$eval(sel, (e, name) => {
return e.getAttribute(name)
}, name)
}
client.hover = async (sel) => {
const element = await client.locator(sel).nth(0)
await element.hover()
await delay(400)
}
return client
}
+465
View File
@@ -0,0 +1,465 @@
const delay = require('./wait')
const {
TEST_HOST,
TEST_PASS,
TEST_USER,
TEST_PORT
} = require('./env')
const {
expect
} = require('./expect')
const log = require('./log')
/**
* Common file and folder operations for electerm SFTP tests
*/
/**
* Creates a new file in the specified type of file list (local/remote)
* Always uses the parent-file-item which is guaranteed to be present
*
* @param {Object} client - The Playwright client
* @param {string} type - The type of file list ('local' or 'remote')
* @param {string} fileName - The name of the file to create
*/
async function createFile (client, type, fileName) {
// Always use the parent-file-item for right-click context menu
await client.rightClick(`.session-current .file-list.${type} .parent-file-item`, 10, 10)
await delay(500)
await client.click('.ant-dropdown:not(.ant-dropdown-hidden) .ant-dropdown-menu-item:has-text("New File")')
await delay(400)
await client.setValue('.session-current .sftp-item input', fileName)
await client.click('.session-current .sftp-panel-title')
await delay(3500) // Ensure file creation completes
}
/**
* Creates a new folder in the specified type of file list (local/remote)
* Always uses the parent-file-item which is guaranteed to be present
*
* @param {Object} client - The Playwright client
* @param {string} type - The type of file list ('local' or 'remote')
* @param {string} folderName - The name of the folder to create
*/
async function createFolder (client, type, folderName) {
await delay(500)
// Always use the parent-file-item for right-click context menu
await client.rightClick(`.session-current .file-list.${type} .parent-file-item`, 10, 10)
await delay(500)
await client.click('.ant-dropdown:not(.ant-dropdown-hidden) .ant-dropdown-menu-item:has-text("New Folder")')
await delay(400)
await client.setValue('.session-current .sftp-item input', folderName)
await client.click('.session-current .sftp-panel-title')
await delay(3500) // Ensure folder creation completes
}
/**
* Deletes an item (file or folder) from the specified type of file list
*
* @param {Object} client - The Playwright client
* @param {string} type - The type of file list ('local' or 'remote')
* @param {string} itemName - The name of the item to delete
*/
async function deleteItem (client, type, itemName) {
await client.click(`.session-current .file-list.${type} .sftp-item[title="${itemName}"]`)
await delay(400)
await client.keyboard.press('Delete')
await delay(400)
await client.keyboard.press('Enter')
await delay(2000)
}
/**
* Copies an item using the context menu
*
* @param {Object} client - The Playwright client
* @param {string} type - The type of file list ('local' or 'remote')
* @param {string} itemName - The name of the item to copy
*/
async function copyItem (client, type, itemName) {
await client.rightClick(`.session-current .file-list.${type} .sftp-item[title="${itemName}"]`, 10, 10)
await delay(1000) // Increased delay for context menu
await client.click('.ant-dropdown:not(.ant-dropdown-hidden) .ant-dropdown-menu-item:has-text("Copy")')
await delay(1500) // Ensure copy operation registers
}
/**
* Copies an item using keyboard shortcuts
*
* @param {Object} client - The Playwright client
* @param {string} type - The type of file list ('local' or 'remote')
* @param {string} itemName - The name of the item to copy
*/
async function copyItemWithKeyboard (client, type, itemName) {
await client.click(`.session-current .file-list.${type} .sftp-item[title="${itemName}"]`)
await delay(400)
// Use Meta+C on Mac, Ctrl+C otherwise
const isMac = process.platform === 'darwin'
const modKey = isMac ? 'Meta' : 'Control'
await client.keyboard.press(`${modKey}+c`)
await delay(1500) // Ensure copy operation registers
}
/**
* Cuts an item using the context menu
*
* @param {Object} client - The Playwright client
* @param {string} type - The type of file list ('local' or 'remote')
* @param {string} itemName - The name of the item to cut
*/
async function cutItem (client, type, itemName) {
await client.rightClick(`.session-current .file-list.${type} .sftp-item[title="${itemName}"]`, 10, 10)
await delay(800)
await client.click('.ant-dropdown:not(.ant-dropdown-hidden) .ant-dropdown-menu-item:has-text("Cut")')
await delay(1000)
}
/**
* Pastes an item using the context menu
*
* @param {Object} client - The Playwright client
* @param {string} type - The type of file list ('local' or 'remote')
*/
async function pasteItem (client, type) {
const parentFolderSelector = `.session-current .file-list.${type} .parent-file-item`
const realFileSelector = `.session-current .file-list.${type} .real-file-item`
// Click elsewhere to ensure the previous context menu is closed
await client.click('.session-current .sftp-panel-title')
await delay(1000) // Increased delay
// Try to right click on the parent file item first (for empty folders)
if (await client.locator(parentFolderSelector).count() > 0) {
await client.rightClick(parentFolderSelector, 10, 10)
} else {
// Fall back to real file item if parent item doesn't exist
await client.rightClick(realFileSelector, 10, 10)
}
await delay(1000)
// Wait for paste menu to be visible and enabled
const pasteMenuItem = await client.locator('.ant-dropdown:not(.ant-dropdown-hidden) .ant-dropdown-menu-item:has-text("Paste"):not(.ant-dropdown-menu-item-disabled)')
await pasteMenuItem.waitFor({ state: 'visible', timeout: 5000 })
await pasteMenuItem.click()
await delay(4000) // Increased delay for paste operation
}
/**
* Pastes an item using keyboard shortcuts
*
* @param {Object} client - The Playwright client
* @param {string} type - The type of file list ('local' or 'remote')
*/
async function pasteItemWithKeyboard (client, type) {
// Click on empty space in the file list to ensure focus
await client.click(`.session-current .file-list.${type}`)
await delay(1000)
// Use Meta+V on Mac, Ctrl+V otherwise
const isMac = process.platform === 'darwin'
const modKey = isMac ? 'Meta' : 'Control'
await client.keyboard.press(`${modKey}+v`)
await delay(4000) // Increased delay for paste operation
}
/**
* Renames an item using the context menu
*
* @param {Object} client - The Playwright client
* @param {string} type - The type of file list ('local' or 'remote')
* @param {string} oldName - The current name of the item
* @param {string} newName - The new name for the item
*/
async function renameItem (client, type, oldName, newName) {
await client.rightClick(`.session-current .file-list.${type} .sftp-item[title="${oldName}"]`, 10, 10)
await delay(500)
await client.click('.ant-dropdown:not(.ant-dropdown-hidden) .ant-dropdown-menu-item:has-text("Rename")')
await delay(400)
await client.setValue('.session-current .sftp-item input', newName)
await client.click('.session-current .sftp-panel-title')
await delay(2500)
}
/**
* Enters a folder in the file list
*
* @param {Object} client - The Playwright client
* @param {string} type - The type of file list ('local' or 'remote')
* @param {string} folderName - The name of the folder to enter
*/
async function enterFolder (client, type, folderName) {
await client.rightClick(`.session-current .file-list.${type} .sftp-item[title="${folderName}"]`, 10, 10)
await delay(800)
await client.click('.ant-dropdown:not(.ant-dropdown-hidden) .ant-dropdown-menu-item:has-text("Enter")')
await delay(3500) // Increased delay for folder navigation
}
/**
* Navigates to the parent folder
*
* @param {Object} client - The Playwright client
* @param {string} type - The type of file list ('local' or 'remote')
*/
async function navigateToParentFolder (client, type) {
await client.doubleClick(`.session-current .file-list.${type} .parent-file-item`)
await delay(3000)
}
/**
* Selects all items in a file list using context menu
*
* @param {Object} client - The Playwright client
* @param {string} type - The type of file list ('local' or 'remote')
*/
async function selectAllContextMenu (client, type) {
await client.rightClick(`.session-current .file-list.${type} .real-file-item`, 10, 10)
await delay(500)
await client.click('.ant-dropdown:not(.ant-dropdown-hidden) .ant-dropdown-menu-item:has-text("Select All")')
await delay(1000)
}
/**
* Accesses folder from the terminal through context menu
*
* @param {Object} client - The Playwright client
* @param {string} type - The type of file list ('local' or 'remote')
* @param {string} folderName - The name of the folder to access
*/
async function accessFolderFromTerminal (client, type, folderName) {
await client.rightClick(`.file-list.${type} .sftp-item[title="${folderName}"]`, 10, 10)
await delay(500)
await client.click('.ant-dropdown:not(.ant-dropdown-hidden) .ant-dropdown-menu-item:has-text("Access this folder from the terminal")')
await delay(1000)
}
async function openNewConnectionForm (client) {
await client.click('.btns .anticon-plus-circle')
await delay(500)
}
async function confirmSshHostKeyVerificationIfNeeded (client, timeout = 4000) {
const trustButton = client.locator('.custom-modal-wrap button:has-text("Trust and Save")').first()
try {
await trustButton.waitFor({ state: 'visible', timeout })
} catch {
return false
}
await trustButton.click()
await delay(500)
return true
}
/**
* Sets up SSH connection for testing (fills form and submits, no SFTP tab)
*
* @param {Object} client - The Playwright client
*/
async function setupSshConnection (client, options = {}) {
const {
host = TEST_HOST,
username = TEST_USER,
password = TEST_PASS,
port = TEST_PORT,
openForm = true,
waitAfterConnect = 2000,
hostKeyModalTimeout = 4000
} = options
if (openForm) {
await openNewConnectionForm(client)
}
await client.setValue('#ssh-form_host', host)
await client.setValue('#ssh-form_username', username)
await client.setValue('#ssh-form_password', password)
await client.setValue('#ssh-form_port', port)
await client.click('.setting-wrap .ant-btn-primary')
await confirmSshHostKeyVerificationIfNeeded(client, hostKeyModalTimeout)
await delay(waitAfterConnect)
}
/**
* Sets up SFTP connection for testing (SSH form + SFTP tab)
*
* @param {Object} client - The Playwright client
*/
async function setupSftpConnection (client) {
await setupSshConnection(client)
// Click sftp tab
await client.click('.session-current .term-sftp-tabs .type-tab', 1)
await delay(2500)
}
/**
* Verify that a file exists in the file list
*
* @param {Object} client - The Playwright client
* @param {string} type - The type of file list ('local' or 'remote')
* @param {string} itemName - The name of the item to verify
* @returns {Promise<boolean>} - Whether the file exists
*/
async function verifyFileExists (client, type, itemName) {
const fileItems = await client.locator(`.session-current .file-list.${type} .sftp-item[title="${itemName}"]`)
const count = await fileItems.count()
return count > 0
}
/**
* Verify that a file does not exist in the file list
*
* @param {Object} client - The Playwright client
* @param {string} type - The type of file list ('local' or 'remote')
* @param {string} itemName - The name of the item to verify
* @returns {Promise<boolean>} - Whether the file does not exist
*/
async function verifyFileNotExists (client, type, itemName) {
return !(await verifyFileExists(client, type, itemName))
}
// Selection operations
async function selectItemsWithShift (client, type, startIndex, endIndex) {
const items = await client.locator(`.session-current .file-list.${type} .real-file-item`)
// Click first item
await items.nth(startIndex).click()
await delay(500)
// Shift+click second item
await items.nth(endIndex).click({
modifiers: ['Shift']
})
await delay(500)
}
async function selectItemsWithCtrlOrCmd (client, type, indices) {
const items = await client.locator(`.session-current .file-list.${type} .real-file-item`)
// Click first item
await items.nth(indices[0]).click()
await delay(500)
// Add remaining items with Cmd/Ctrl
for (let i = 1; i < indices.length; i++) {
await items.nth(indices[i]).click({
modifiers: process.platform === 'darwin' ? ['Meta'] : ['Control']
})
await delay(500)
}
}
/**
* Verifies the current path in the file list input
*
* @param {Object} client - The Playwright client
* @param {string} type - The type of file list ('local' or 'remote')
* @param {string} expectedPath - The expected path or part of it
* @returns {Promise<boolean>} - Whether the path matches
*/
async function verifyCurrentPath (client, type, expectedPath) {
const currentPath = await client.getValue(`.session-current .sftp-${type}-section .sftp-title input`)
return currentPath.endsWith(expectedPath)
}
/**
* Clicks on the SFTP tab
*
* @param {Object} client - The Playwright client
*/
async function clickSftpTab (client) {
await client.click('.session-current .term-sftp-tabs .type-tab', 1)
await delay(3500)
}
/**
* Counts items in the file list
*
* @param {Object} client - The Playwright client
* @param {string} type - The type of file list ('local' or 'remote')
* @param {string} selector - The CSS selector for the items (e.g., '.sftp-item' or '.parent-file-item')
*/
async function countFileListItems (client, type, selector) {
const items = await client.locator(`.session-current .file-list.${type} ${selector}`)
return await items.count()
}
/**
* Verifies the number of selected items
*
* @param {Object} client - The Playwright client
* @param {string} type - The type of file list ('local' or 'remote')
* @param {number} expectedCount - The expected number of selected items
*/
async function verifySelectionCount (client, type, expectedCount) {
const selectedItems = await client.locator(`.session-current .file-list.${type} .sftp-item.selected`)
const count = await selectedItems.count()
expect(count).toBe(expectedCount, `Expected ${expectedCount} items to be selected, found ${count}`)
}
/**
* Verifies that the fileTransfers array in window.store is empty
* Polls with retries to handle timing variations in transfer completion
*
* @param {Object} client - The Playwright client
* @param {number} [timeout=30000] - Max wait time in ms
* @param {number} [interval=1000] - Poll interval in ms
*/
async function verifyFileTransfersComplete (client, timeout = 30000, interval = 1000) {
const start = Date.now()
let isEmpty = false
while (Date.now() - start < timeout) {
isEmpty = await client.evaluate(() => {
return window.store.fileTransfers.length === 0
})
if (isEmpty) {
break
}
await delay(interval)
}
expect(isEmpty).toBe(true, `Expected fileTransfers array to be empty after operations complete (waited ${timeout}ms)`)
}
async function closeApp (electronApp, fileName) {
try {
await Promise.race([
electronApp.close(),
new Promise((resolve, reject) => setTimeout(() => reject(new Error('close timeout')), 5000))
])
} catch (e) {
if (e.message === 'close timeout') {
log(`${fileName}: close timed out, killing process`)
electronApp.process().kill()
} else {
console.log(e)
}
}
}
module.exports = {
createFile,
createFolder,
deleteItem,
copyItem,
copyItemWithKeyboard,
cutItem,
pasteItem,
pasteItemWithKeyboard,
renameItem,
enterFolder,
navigateToParentFolder,
selectAllContextMenu,
accessFolderFromTerminal,
setupSshConnection,
setupSftpConnection,
verifyFileExists,
verifyFileNotExists,
selectItemsWithShift,
selectItemsWithCtrlOrCmd,
verifyCurrentPath,
clickSftpTab,
countFileListItems,
verifySelectionCount,
verifyFileTransfersComplete,
closeApp
}
+30
View File
@@ -0,0 +1,30 @@
/**
* export test username/password/host/port
*/
require('dotenv').config({
override: true
})
const os = require('os').platform()
const {
env
} = process
const TEST_HOST = env[`TEST_HOST_${os}`] || env.TEST_HOST
const TEST_PASS = env[`TEST_PASS_${os}`] || env.TEST_PASS
const TEST_USER = env[`TEST_USER_${os}`] || env.TEST_USER
const TEST_PORT = env[`TEST_PORT_${os}`] || env.TEST_PORT || '22'
if (!TEST_HOST || !TEST_PASS || !TEST_USER) {
throw new Error(`
basic sftp test need TEST_HOST TEST_PASS TEST_USER env set,
TEST_PORT is optional (default 22)
you can run "cp .sample.env .env" to create env file, then edit .env, fill all required field
`)
}
module.exports = {
TEST_HOST,
TEST_PASS,
TEST_USER,
TEST_PORT: '' + TEST_PORT
}
+33
View File
@@ -0,0 +1,33 @@
// expect.js
const { expect: playwrightExpect } = require('@playwright/test')
function extend (value) {
const originalExpect = playwrightExpect(value)
// Create a proxy to handle all method calls
return new Proxy(originalExpect, {
get (target, prop) {
// Handle the five custom functions
if (prop === 'equal' || prop === 'equals') {
return (expected) => target.toEqual(expected)
}
if (prop === 'lessThan') {
return (expected) => target.toBeLessThan(expected)
}
if (prop === 'greaterThan') {
return (expected) => target.toBeGreaterThan(expected)
}
if (prop === 'includes') {
return (expected) => target.toContain(expected)
}
// For all other properties, return the original Playwright expect method
return target[prop]
}
})
}
// Export a function that looks like Chai's expect
module.exports = {
expect: (actual) => extend(actual)
}
+67
View File
@@ -0,0 +1,67 @@
const { FtpSrv } = require('@electerm/ftp-srv')
const path = require('path')
const fs = require('fs')
// Create FTP root directory if it doesn't exist
const ftpRoot = path.join(__dirname, 'ftp-root')
if (!fs.existsSync(ftpRoot)) {
fs.mkdirSync(ftpRoot)
}
const ftpServer = new FtpSrv({
url: 'ftp://0.0.0.0:21',
anonymous: false,
root: ftpRoot
})
// Handle user login
ftpServer.on('login', ({ username, password }, resolve, reject) => {
// Simple authentication
console.log('login', username, password)
if (username === 'test' && password === 'test123') {
return resolve({ root: ftpRoot })
}
return reject(new Error('Invalid username or password'))
})
// Log events
ftpServer.on('client-error', ({ connection, context, error }) => {
console.log('Client error', error)
})
ftpServer.on('STOR', (error, fileName) => {
if (error) {
console.log('Error uploading file:', error)
} else {
console.log('File uploaded:', fileName)
}
})
ftpServer.on('RETR', (error, fileName) => {
if (error) {
console.log('Error downloading file:', error)
} else {
console.log('File downloaded:', fileName)
}
})
// Start server
ftpServer.listen()
.then(() => {
console.log('FTP server is running at port 21')
console.log('Username: test')
console.log('Password: test123')
console.log('FTP root directory:', ftpRoot)
})
.catch(err => {
console.log('Error starting FTP server:', err)
})
// Handle shutdown
process.on('SIGTERM', () => {
ftpServer.close()
.then(() => {
console.log('FTP server closed')
process.exit(0)
})
})
+10
View File
@@ -0,0 +1,10 @@
/**
* some test may need only run in some os
*/
const os = require('os')
const platform = os.platform()
module.exports = (osName) => {
return platform === osName
}
+14
View File
@@ -0,0 +1,14 @@
/**
* language fix
*/
const _ = require('lodash')
function capitalizeFirstLetter (string) {
return string.charAt(0).toUpperCase() + string.slice(1)
}
module.exports = (id) => {
const lang = require('@electerm/electerm-locales').en_us.lang
return capitalizeFirstLetter(_.get(lang, `${id}`) || id)
}
+4
View File
@@ -0,0 +1,4 @@
module.exports = (...arg) => {
const timestamp = new Date().toISOString()
console.log(`[${timestamp}] >`, ...arg)
}
+10
View File
@@ -0,0 +1,10 @@
/**
* output uid with os name prefix
*/
const { nanoid } = require('nanoid')
const os = require('os').platform()
module.exports = () => {
return os + '_' + nanoid()
}
+5
View File
@@ -0,0 +1,5 @@
/**
* wait async
* @param {*} time
*/
module.exports = time => new Promise(resolve => setTimeout(resolve, time))