chore: implement NPM/JSON import routes and fix SMTP persistence

Phase 3 of skipped tests remediation - enables 7 previously skipped E2E tests

Backend:

Add NPM import handler with session-based upload/commit/cancel
Add JSON import handler with Charon/NPM format support
Fix SMTP SaveSMTPConfig using transaction-based upsert
Add comprehensive unit tests for new handlers
Frontend:

Add ImportNPM page component following ImportCaddy pattern
Add ImportJSON page component with format detection
Add useNPMImport and useJSONImport React Query hooks
Add API clients for npm/json import endpoints
Register routes in App.tsx and navigation in Layout.tsx
Add i18n keys for new import pages
Tests:

7 E2E tests now enabled and passing
Backend coverage: 86.8%
Reduced total skipped tests from 98 to 91
Closes: Phase 3 of skipped-tests-remediation plan
This commit is contained in:
GitHub Actions
2026-01-22 21:10:01 +00:00
parent b60e0be5fb
commit bc15e976b2
21 changed files with 3771 additions and 476 deletions

View File

@@ -0,0 +1,84 @@
import { useState } from 'react';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import {
uploadJSONExport,
commitJSONImport,
cancelJSONImport,
JSONImportPreview,
JSONImportCommitResult,
} from '../api/jsonImport';
/**
* Hook for managing JSON import workflow.
* Provides upload, commit, and cancel functionality with state management.
*/
export function useJSONImport() {
const queryClient = useQueryClient();
const [preview, setPreview] = useState<JSONImportPreview | null>(null);
const [sessionId, setSessionId] = useState<string | null>(null);
const [commitResult, setCommitResult] = useState<JSONImportCommitResult | null>(null);
const uploadMutation = useMutation({
mutationFn: uploadJSONExport,
onSuccess: (data) => {
setPreview(data);
setSessionId(data.session.id);
},
});
const commitMutation = useMutation({
mutationFn: ({
resolutions,
names,
}: {
resolutions: Record<string, string>;
names: Record<string, string>;
}) => {
if (!sessionId) throw new Error('No active session');
return commitJSONImport(sessionId, resolutions, names);
},
onSuccess: (data) => {
setCommitResult(data);
setPreview(null);
setSessionId(null);
queryClient.invalidateQueries({ queryKey: ['proxy-hosts'] });
},
});
const cancelMutation = useMutation({
mutationFn: cancelJSONImport,
onSuccess: () => {
setPreview(null);
setSessionId(null);
},
});
const clearCommitResult = () => {
setCommitResult(null);
};
const reset = () => {
setPreview(null);
setSessionId(null);
setCommitResult(null);
};
return {
preview,
sessionId,
loading: uploadMutation.isPending,
error: uploadMutation.error,
upload: uploadMutation.mutateAsync,
commit: (resolutions: Record<string, string>, names: Record<string, string>) =>
commitMutation.mutateAsync({ resolutions, names }),
committing: commitMutation.isPending,
commitError: commitMutation.error,
commitResult,
clearCommitResult,
cancel: cancelMutation.mutateAsync,
cancelling: cancelMutation.isPending,
reset,
};
}
export type { JSONImportPreview, JSONImportCommitResult };