chore: refactor tests to improve clarity and reliability

- Removed unnecessary test.skip() calls in various test files, replacing them with comments for clarity.
- Enhanced retry logic in TestDataManager for API requests to handle rate limiting more gracefully.
- Updated security helper functions to include retry mechanisms for fetching security status and setting module states.
- Improved loading completion checks to handle page closure scenarios.
- Adjusted WebKit-specific tests to run in all browsers, removing the previous skip logic.
- General cleanup and refactoring across multiple test files to enhance readability and maintainability.
This commit is contained in:
GitHub Actions
2026-02-08 00:02:09 +00:00
parent 5054a334f2
commit aa85c911c0
71 changed files with 22475 additions and 3241 deletions

View File

@@ -18,6 +18,13 @@ import DNSProviderSelector from './DNSProviderSelector'
import { useDetectDNSProvider } from '../hooks/useDNSDetection'
import { DNSDetectionResult } from './DNSDetectionResult'
import type { DNSProvider } from '../api/dnsProviders'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from './ui/Select'
// Application preset configurations
const APPLICATION_PRESETS: { value: ApplicationPreset; label: string; description: string }[] = [
@@ -126,6 +133,7 @@ export default function ProxyHostForm({ host, onSubmit, onCancel }: ProxyHostFor
const { mutateAsync: detectProvider, isPending: isDetecting, data: detectionResult, reset: resetDetection } = useDetectDNSProvider()
const [manualProviderSelection, setManualProviderSelection] = useState(false)
const detectionTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
const portInputRef = useRef<HTMLInputElement | null>(null)
// Fetch Charon internal IP on mount (legacy: CPMP internal IP)
useEffect(() => {
@@ -384,6 +392,20 @@ export default function ProxyHostForm({ host, onSubmit, onCancel }: ProxyHostFor
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
if (!formData.forward_port) {
portInputRef.current?.setCustomValidity('Port is required')
portInputRef.current?.reportValidity()
portInputRef.current?.focus()
return
}
if (formData.forward_port < 1 || formData.forward_port > 65535) {
portInputRef.current?.setCustomValidity('Port must be between 1 and 65535')
portInputRef.current?.reportValidity()
portInputRef.current?.focus()
return
}
// Validate DNS provider for wildcard domains
if (hasWildcardDomain && !formData.dns_provider_id) {
toast.error('DNS provider is required for wildcard domains')
@@ -571,50 +593,51 @@ export default function ProxyHostForm({ host, onSubmit, onCancel }: ProxyHostFor
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* Docker Container Quick Select */}
<div>
<label htmlFor="connection-source" className="block text-sm font-medium text-gray-300 mb-2">
<label className="block text-sm font-medium text-gray-300 mb-2">
Source
</label>
<select
id="connection-source"
value={connectionSource}
onChange={e => setConnectionSource(e.target.value)}
className="w-full bg-gray-900 border border-gray-700 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="custom">Custom / Manual</option>
<option value="local">Local (Docker Socket)</option>
{remoteServers
.filter(s => s.provider === 'docker' && s.enabled)
.map(server => (
<option key={server.uuid} value={server.uuid}>
{server.name} ({server.host})
</option>
))
}
</select>
<Select value={connectionSource} onValueChange={setConnectionSource}>
<SelectTrigger className="w-full bg-gray-900 border-gray-700 text-white" aria-label="Source">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="custom">Custom / Manual</SelectItem>
<SelectItem value="local">Local (Docker Socket)</SelectItem>
{remoteServers
.filter(s => s.provider === 'docker' && s.enabled)
.map(server => (
<SelectItem key={server.uuid} value={server.uuid}>
{server.name} ({server.host})
</SelectItem>
))
}
</SelectContent>
</Select>
</div>
<div>
<label htmlFor="quick-select-docker" className="block text-sm font-medium text-gray-300 mb-2">
<label className="block text-sm font-medium text-gray-300 mb-2">
Containers
</label>
<select
id="quick-select-docker"
onChange={e => handleContainerSelect(e.target.value)}
disabled={dockerLoading || connectionSource === 'custom'}
className="w-full bg-gray-900 border border-gray-700 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-50"
<Select
value=""
onValueChange={e => e && handleContainerSelect(e)}
>
<option value="">
{connectionSource === 'custom'
<SelectTrigger className="w-full bg-gray-900 border-gray-700 text-white disabled:opacity-50" disabled={dockerLoading || connectionSource === 'custom'} aria-label="Containers">
<SelectValue placeholder={connectionSource === 'custom'
? 'Select a source to view containers'
: (dockerLoading ? 'Loading containers...' : '-- Select a container --')}
</option>
{dockerContainers.map(container => (
<option key={container.id} value={container.id}>
{container.names[0]} ({container.image})
</option>
))}
</select>
: (dockerLoading ? 'Loading containers...' : 'Select a container')}
/>
</SelectTrigger>
<SelectContent>
{dockerContainers.map(container => (
<SelectItem key={container.id} value={container.id}>
{container.names[0]} ({container.image})
</SelectItem>
))}
</SelectContent>
</Select>
{dockerError && connectionSource !== 'custom' && (
<div className="mt-2 p-3 bg-red-500/10 border border-red-500/30 rounded-lg">
<div className="flex items-start gap-2">
@@ -639,22 +662,21 @@ export default function ProxyHostForm({ host, onSubmit, onCancel }: ProxyHostFor
<div className="space-y-4">
{domains.length > 0 && (
<div>
<label htmlFor="base-domain" className="block text-sm font-medium text-gray-300 mb-2">
<label className="block text-sm font-medium text-gray-300 mb-2">
Base Domain (Auto-fill)
</label>
<select
id="base-domain"
value={selectedDomain}
onChange={e => handleBaseDomainChange(e.target.value)}
className="w-full bg-gray-900 border border-gray-700 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="">-- Select a base domain --</option>
{domains.map(domain => (
<option key={domain.uuid} value={domain.name}>
{domain.name}
</option>
))}
</select>
<Select value={selectedDomain} onValueChange={handleBaseDomainChange}>
<SelectTrigger className="w-full bg-gray-900 border-gray-700 text-white" aria-label="Base Domain (Auto-fill)">
<SelectValue placeholder="Select a base domain" />
</SelectTrigger>
<SelectContent>
{domains.map(domain => (
<SelectItem key={domain.uuid} value={domain.name}>
{domain.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
<div>
@@ -677,16 +699,16 @@ export default function ProxyHostForm({ host, onSubmit, onCancel }: ProxyHostFor
{/* Forward Details */}
<div className="grid grid-cols-3 gap-4">
<div>
<label htmlFor="forward-scheme" className="block text-sm font-medium text-gray-300 mb-2">Scheme</label>
<select
id="forward-scheme"
value={formData.forward_scheme}
onChange={e => setFormData({ ...formData, forward_scheme: e.target.value })}
className="w-full bg-gray-900 border border-gray-700 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value="http">HTTP</option>
<option value="https">HTTPS</option>
</select>
<label className="block text-sm font-medium text-gray-300 mb-2">Scheme</label>
<Select value={formData.forward_scheme} onValueChange={scheme => setFormData({ ...formData, forward_scheme: scheme })}>
<SelectTrigger className="w-full bg-gray-900 border-gray-700 text-white" aria-label="Scheme">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="http">HTTP</SelectItem>
<SelectItem value="https">HTTPS</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<label htmlFor="forward-host" className="block text-sm font-medium text-gray-300 mb-2">
@@ -721,9 +743,11 @@ export default function ProxyHostForm({ host, onSubmit, onCancel }: ProxyHostFor
required
min="1"
max="65535"
ref={portInputRef}
value={formData.forward_port}
onChange={e => {
const v = parseInt(e.target.value)
portInputRef.current?.setCustomValidity('')
setFormData({ ...formData, forward_port: Number.isNaN(v) ? 0 : v })
}}
className="w-full bg-gray-900 border border-gray-700 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
@@ -736,19 +760,20 @@ export default function ProxyHostForm({ host, onSubmit, onCancel }: ProxyHostFor
<label className="block text-sm font-medium text-gray-300 mb-2">
SSL Certificate
</label>
<select
value={formData.certificate_id || 0}
onChange={e => setFormData({ ...formData, certificate_id: parseInt(e.target.value) || null })}
className="w-full bg-gray-900 border border-gray-700 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value={0}>Auto-manage with Let's Encrypt (recommended)</option>
{certificates.map(cert => (
<option key={cert.id || cert.domain} value={cert.id ?? 0}>
{(cert.name || cert.domain)}
{cert.provider ? ` (${cert.provider})` : ''}
</option>
))}
</select>
<Select value={String(formData.certificate_id || 0)} onValueChange={e => setFormData({ ...formData, certificate_id: parseInt(e) || null })}>
<SelectTrigger className="w-full bg-gray-900 border-gray-700 text-white" aria-label="SSL Certificate">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="0">Auto-manage with Let's Encrypt (recommended)</SelectItem>
{certificates.map(cert => (
<SelectItem key={cert.id || cert.domain} value={String(cert.id ?? 0)}>
{(cert.name || cert.domain)}
{cert.provider ? ` (${cert.provider})` : ''}
</SelectItem>
))}
</SelectContent>
</Select>
<p className="text-xs text-gray-500 mt-1">
Choose an existing certificate if already issued for these domains, or let Charon request/renew via Let's Encrypt automatically.
</p>
@@ -804,37 +829,39 @@ export default function ProxyHostForm({ host, onSubmit, onCancel }: ProxyHostFor
<span className="text-gray-500 font-normal ml-2">(Optional)</span>
</label>
<select
value={formData.security_header_profile_id || 0}
onChange={e => {
const value = e.target.value === "0" ? null : parseInt(e.target.value) || null
<Select
value={String(formData.security_header_profile_id || 0)}
onValueChange={e => {
const value = e === "0" ? null : parseInt(e) || null
setFormData({ ...formData, security_header_profile_id: value })
}}
className="w-full bg-gray-900 border border-gray-700 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<option value={0}>None (No Security Headers)</option>
<optgroup label="Quick Presets">
<SelectTrigger className="w-full bg-gray-900 border-gray-700 text-white" aria-label="Security Headers">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="0">None (No Security Headers)</SelectItem>
{securityProfiles
?.filter(p => p.is_preset)
.sort((a, b) => a.security_score - b.security_score)
.map(profile => (
<option key={profile.id} value={profile.id}>
<SelectItem key={profile.id} value={String(profile.id)}>
{profile.name} (Score: {profile.security_score}/100)
</option>
</SelectItem>
))}
</optgroup>
{(securityProfiles?.filter(p => !p.is_preset) || []).length > 0 && (
<optgroup label="Custom Profiles">
{(securityProfiles || [])
.filter(p => !p.is_preset)
.map(profile => (
<option key={profile.id} value={profile.id}>
{profile.name} (Score: {profile.security_score}/100)
</option>
))}
</optgroup>
)}
</select>
{(securityProfiles?.filter(p => !p.is_preset) || []).length > 0 && (
<>
{(securityProfiles || [])
.filter(p => !p.is_preset)
.map(profile => (
<SelectItem key={profile.id} value={String(profile.id)}>
{profile.name} (Score: {profile.security_score}/100)
</SelectItem>
))}
</>
)}
</SelectContent>
</Select>
{formData.security_header_profile_id && (() => {
const selected = securityProfiles?.find(p => p.id === formData.security_header_profile_id)
@@ -893,30 +920,33 @@ export default function ProxyHostForm({ host, onSubmit, onCancel }: ProxyHostFor
{/* Application Preset */}
<div>
<label htmlFor="application-preset" className="block text-sm font-medium text-gray-300 mb-2">
<label className="block text-sm font-medium text-gray-300 mb-2">
Application Preset
<span className="text-gray-500 font-normal ml-2">(Optional)</span>
</label>
<select
id="application-preset"
<Select
value={formData.application}
onChange={e => {
const preset = e.target.value as ApplicationPreset
onValueChange={preset => {
const presetVal = preset as ApplicationPreset
// Apply with advanced_config logic
const needsWebsockets = ['plex', 'jellyfin', 'emby', 'homeassistant', 'vaultwarden'].includes(preset)
const needsWebsockets = ['plex', 'jellyfin', 'emby', 'homeassistant', 'vaultwarden'].includes(presetVal)
// Delegate to shared logic which will auto-apply or prompt
tryApplyPreset(preset)
tryApplyPreset(presetVal)
// Ensure we still enable websockets when preset implies it
setFormData(prev => ({ ...prev, websocket_support: needsWebsockets || prev.websocket_support }))
}}
className="w-full bg-gray-900 border border-gray-700 rounded-lg px-4 py-2 text-white focus:outline-none focus:ring-2 focus:ring-blue-500"
>
{APPLICATION_PRESETS.map(preset => (
<option key={preset.value} value={preset.value}>
{preset.label} - {preset.description}
</option>
))}
</select>
<SelectTrigger className="w-full bg-gray-900 border-gray-700 text-white" aria-label="Application Preset">
<SelectValue />
</SelectTrigger>
<SelectContent>
{APPLICATION_PRESETS.map(preset => (
<SelectItem key={preset.value} value={preset.value}>
{preset.label} - {preset.description}
</SelectItem>
))}
</SelectContent>
</Select>
<p className="text-xs text-gray-500 mt-1">
Presets automatically configure headers for remote access behind tunnels/CGNAT.
</p>