feat: import servers from ~/.ssh/config (#873)

This commit is contained in:
lollipopkit🏳️‍⚧️
2025-08-31 19:33:29 +08:00
committed by GitHub
parent a97b3cf43e
commit 12a243d139
42 changed files with 2850 additions and 334 deletions

View File

@@ -0,0 +1,317 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:server_box/data/model/server/server_private_info.dart';
void main() {
group('Server Edit Page Logic Tests', () {
test('SSH import should only be available on desktop platforms', () {
final desktopPlatforms = ['linux', 'macos', 'windows'];
final mobilePlatforms = ['android', 'ios', 'fuchsia'];
for (final platform in desktopPlatforms) {
final isDesktop = desktopPlatforms.contains(platform);
expect(isDesktop, isTrue, reason: '$platform should support SSH import');
}
for (final platform in mobilePlatforms) {
final isDesktop = desktopPlatforms.contains(platform);
expect(isDesktop, isFalse, reason: '$platform should not support SSH import');
}
});
test('permission prompt conditions are correct', () {
// Test the conditions for showing permission prompt
// Should prompt when: firstTimeReadSSHCfg=true, sshConfigExists=true, isNewServer=true
bool shouldPrompt(bool firstTime, bool configExists, bool isNew) {
return firstTime && configExists && isNew;
}
expect(shouldPrompt(true, true, true), isTrue); // All conditions met
expect(shouldPrompt(false, true, true), isFalse); // Setting disabled
expect(shouldPrompt(true, false, true), isFalse); // No config file
expect(shouldPrompt(true, true, false), isFalse); // Editing existing server
expect(shouldPrompt(false, false, false), isFalse); // No conditions met
});
test('server validation logic works correctly', () {
// Test server validation without actual form widgets
// Valid server
const validServer = Spi(
name: 'test-server',
ip: '192.168.1.100',
port: 22,
user: 'root',
);
expect(validServer.name.isNotEmpty, isTrue);
expect(validServer.ip.isNotEmpty, isTrue);
expect(validServer.port > 0 && validServer.port <= 65535, isTrue);
expect(validServer.user.isNotEmpty, isTrue);
// Invalid cases
expect(''.isNotEmpty, isFalse); // Empty name
expect(0 > 0, isFalse); // Invalid port
expect(65536 <= 65535, isFalse); // Port too high
});
test('server form data processing is correct', () {
// Test data processing logic
final Map<String, dynamic> formData = {
'name': 'my-server',
'ip': '192.168.1.100',
'port': '2222',
'user': 'admin',
};
// Process form data into server object
final server = Spi(
name: formData['name'] as String,
ip: formData['ip'] as String,
port: int.parse(formData['port'] as String),
user: formData['user'] as String,
);
expect(server.name, 'my-server');
expect(server.ip, '192.168.1.100');
expect(server.port, 2222);
expect(server.user, 'admin');
});
test('SSH key handling is correct', () {
// Test SSH key field handling
const serverWithKey = Spi(
name: 'key-server',
ip: '192.168.1.100',
port: 22,
user: 'root',
keyId: '~/.ssh/id_rsa',
);
expect(serverWithKey.keyId, '~/.ssh/id_rsa');
expect(serverWithKey.keyId?.isNotEmpty, isTrue);
const serverWithoutKey = Spi(
name: 'pwd-server',
ip: '192.168.1.100',
port: 22,
user: 'root',
pwd: 'password123',
);
expect(serverWithoutKey.keyId, isNull);
expect(serverWithoutKey.pwd, 'password123');
});
test('server editing vs creation logic', () {
// Test logic for distinguishing between editing and creating servers
const existingServer = Spi(
name: 'existing',
ip: '192.168.1.100',
port: 22,
user: 'root',
id: 'server123',
);
// Existing server has non-empty ID
final isEditing = existingServer.id.isNotEmpty;
final isCreating = !isEditing;
expect(isEditing, isTrue);
expect(isCreating, isFalse);
const newServer = Spi(
name: 'new-server',
ip: '192.168.1.100',
port: 22,
user: 'root',
id: '',
);
final isCreatingNew = newServer.id.isEmpty;
final isEditingExisting = !isCreatingNew;
expect(isCreatingNew, isTrue);
expect(isEditingExisting, isFalse);
});
test('form field population from imported server', () {
// Test that imported server data correctly populates form fields
const importedServer = Spi(
name: 'imported-prod-web',
ip: '10.0.1.100',
port: 2222,
user: 'deploy',
keyId: '~/.ssh/production.pem',
);
// Simulate form field population
final formFields = {
'name': importedServer.name,
'ip': importedServer.ip,
'port': importedServer.port.toString(),
'user': importedServer.user,
'keyId': importedServer.keyId,
};
expect(formFields['name'], 'imported-prod-web');
expect(formFields['ip'], '10.0.1.100');
expect(formFields['port'], '2222');
expect(formFields['user'], 'deploy');
expect(formFields['keyId'], '~/.ssh/production.pem');
});
test('import summary display logic', () {
// Test import summary formatting
const totalFound = 5;
const duplicatesRemoved = 2;
const serversToImport = 3;
final summary = {
'total': totalFound,
'duplicates': duplicatesRemoved,
'toImport': serversToImport,
};
expect(summary['total'], 5);
expect(summary['duplicates'], 2);
expect(summary['toImport'], 3);
// Summary validation
expect(summary['duplicates']! + summary['toImport']!, summary['total']);
// Format summary message (simplified)
final message = 'Found ${summary['total']} servers, '
'${summary['duplicates']} duplicates removed, '
'${summary['toImport']} will be imported.';
expect(message, 'Found 5 servers, 2 duplicates removed, 3 will be imported.');
});
test('error handling logic', () {
// Test error handling scenarios
final errors = <String>[];
// Validation errors
void validateServer(Spi server) {
if (server.name.isEmpty) {
errors.add('Server name is required');
}
if (server.ip.isEmpty) {
errors.add('Server IP is required');
}
if (server.port <= 0 || server.port > 65535) {
errors.add('Port must be between 1 and 65535');
}
if (server.user.isEmpty) {
errors.add('Username is required');
}
}
// Test with invalid server
const invalidServer = Spi(
name: '',
ip: '',
port: 0,
user: '',
);
validateServer(invalidServer);
expect(errors.length, 4);
expect(errors.contains('Server name is required'), isTrue);
expect(errors.contains('Server IP is required'), isTrue);
expect(errors.contains('Port must be between 1 and 65535'), isTrue);
expect(errors.contains('Username is required'), isTrue);
// Test with valid server
errors.clear();
const validServer = Spi(
name: 'valid',
ip: '192.168.1.1',
port: 22,
user: 'root',
);
validateServer(validServer);
expect(errors.isEmpty, isTrue);
});
test('name conflict resolution logic', () {
// Test name conflict resolution during import
final existingNames = ['server1', 'server2', 'server3'];
String resolveNameConflict(String proposedName, List<String> existing) {
if (!existing.contains(proposedName)) {
return proposedName;
}
int suffix = 2;
String newName;
do {
newName = '$proposedName ($suffix)';
suffix++;
} while (existing.contains(newName));
return newName;
}
// Test with no conflict
expect(resolveNameConflict('unique-name', existingNames), 'unique-name');
// Test with conflict
expect(resolveNameConflict('server1', existingNames), 'server1 (2)');
// Test with multiple conflicts
final extendedNames = [...existingNames, 'server1 (2)'];
expect(resolveNameConflict('server1', extendedNames), 'server1 (3)');
});
test('SSH config import button visibility logic', () {
// Test when SSH import button should be visible
bool shouldShowSSHImport({
required bool isDesktop,
required bool firstTimeReadSSHCfg,
required bool isNewServer,
}) {
return isDesktop && (firstTimeReadSSHCfg || !isNewServer);
}
// Desktop, first time, new server - should show
expect(shouldShowSSHImport(
isDesktop: true,
firstTimeReadSSHCfg: true,
isNewServer: true,
), isTrue);
// Desktop, not first time, new server - should not show auto import but manual import available
expect(shouldShowSSHImport(
isDesktop: true,
firstTimeReadSSHCfg: false,
isNewServer: true,
), isFalse);
// Desktop, editing existing server - should show manual import
expect(shouldShowSSHImport(
isDesktop: true,
firstTimeReadSSHCfg: false,
isNewServer: false,
), isTrue);
// Mobile - should never show
expect(shouldShowSSHImport(
isDesktop: false,
firstTimeReadSSHCfg: true,
isNewServer: true,
), isFalse);
});
});
}