Adding a node should immediately provision it.

This commit is contained in:
2025-11-09 00:58:06 +00:00
parent 35bc44bc32
commit 4307bc9996
2 changed files with 19 additions and 23 deletions

View File

@@ -106,7 +106,7 @@ describe('NodeForm Integration Tests', () => {
}); });
}); });
it('auto-fills currentIp from detection', async () => { it('auto-fills targetIp from detection', async () => {
const config = createMockConfig(); const config = createMockConfig();
vi.mocked(useInstanceConfig).mockReturnValue(mockUseInstanceConfig(config)); vi.mocked(useInstanceConfig).mockReturnValue(mockUseInstanceConfig(config));
vi.mocked(useNodes).mockReturnValue(mockUseNodes([])); vi.mocked(useNodes).mockReturnValue(mockUseNodes([]));
@@ -122,8 +122,8 @@ describe('NodeForm Integration Tests', () => {
{ wrapper: createWrapper(createTestQueryClient()) } { wrapper: createWrapper(createTestQueryClient()) }
); );
const currentIpInput = screen.getByLabelText(/current ip/i) as HTMLInputElement; const targetIpInput = screen.getByLabelText(/target ip/i) as HTMLInputElement;
expect(currentIpInput.value).toBe('192.168.1.75'); expect(targetIpInput.value).toBe('192.168.1.75');
}); });
it('submits form with correct data', async () => { it('submits form with correct data', async () => {
@@ -132,7 +132,8 @@ describe('NodeForm Integration Tests', () => {
vi.mocked(useInstanceConfig).mockReturnValue(mockUseInstanceConfig(config)); vi.mocked(useInstanceConfig).mockReturnValue(mockUseInstanceConfig(config));
vi.mocked(useNodes).mockReturnValue(mockUseNodes([])); vi.mocked(useNodes).mockReturnValue(mockUseNodes([]));
const detection = createMockHardwareInfo(); // Don't provide detection.ip so VIP-based auto-calculation happens
const detection = createMockHardwareInfo({ ip: undefined });
render( render(
<NodeForm <NodeForm
@@ -154,7 +155,6 @@ describe('NodeForm Integration Tests', () => {
role: 'controlplane', role: 'controlplane',
disk: '/dev/sda', disk: '/dev/sda',
interface: 'eth0', interface: 'eth0',
currentIp: '192.168.1.50',
maintenance: true, maintenance: true,
schematicId: 'default-schematic-123', schematicId: 'default-schematic-123',
targetIp: '192.168.1.101', targetIp: '192.168.1.101',
@@ -201,7 +201,8 @@ describe('NodeForm Integration Tests', () => {
vi.mocked(useInstanceConfig).mockReturnValue(mockUseInstanceConfig(config)); vi.mocked(useInstanceConfig).mockReturnValue(mockUseInstanceConfig(config));
vi.mocked(useNodes).mockReturnValue(mockUseNodes([])); vi.mocked(useNodes).mockReturnValue(mockUseNodes([]));
const detection = createMockHardwareInfo(); // Don't provide detection.ip so VIP-based auto-calculation happens
const detection = createMockHardwareInfo({ ip: undefined });
render( render(
<NodeForm <NodeForm
@@ -239,7 +240,8 @@ describe('NodeForm Integration Tests', () => {
vi.mocked(useInstanceConfig).mockReturnValue(mockUseInstanceConfig(config)); vi.mocked(useInstanceConfig).mockReturnValue(mockUseInstanceConfig(config));
vi.mocked(useNodes).mockReturnValue(mockUseNodes(existingNodes)); vi.mocked(useNodes).mockReturnValue(mockUseNodes(existingNodes));
const detection = createMockHardwareInfo(); // Don't provide detection.ip so VIP-based auto-calculation happens
const detection = createMockHardwareInfo({ ip: undefined });
render( render(
<NodeForm <NodeForm
@@ -275,7 +277,8 @@ describe('NodeForm Integration Tests', () => {
vi.mocked(useInstanceConfig).mockReturnValue(mockUseInstanceConfig(config)); vi.mocked(useInstanceConfig).mockReturnValue(mockUseInstanceConfig(config));
vi.mocked(useNodes).mockReturnValue(mockUseNodes(existingNodes)); vi.mocked(useNodes).mockReturnValue(mockUseNodes(existingNodes));
const detection = createMockHardwareInfo(); // Don't provide detection.ip so VIP-based auto-calculation happens
const detection = createMockHardwareInfo({ ip: undefined });
render( render(
<NodeForm <NodeForm
@@ -306,7 +309,6 @@ describe('NodeForm Integration Tests', () => {
role: 'controlplane', role: 'controlplane',
disk: '/dev/nvme0n1', disk: '/dev/nvme0n1',
targetIp: '192.168.1.105', targetIp: '192.168.1.105',
currentIp: '192.168.1.60',
interface: 'eth1', interface: 'eth1',
schematicId: 'existing-schematic-456', schematicId: 'existing-schematic-456',
maintenance: false, maintenance: false,
@@ -327,14 +329,8 @@ describe('NodeForm Integration Tests', () => {
const targetIpInput = screen.getByLabelText(/target ip/i) as HTMLInputElement; const targetIpInput = screen.getByLabelText(/target ip/i) as HTMLInputElement;
expect(targetIpInput.value).toBe('192.168.1.105'); expect(targetIpInput.value).toBe('192.168.1.105');
const currentIpInput = screen.getByLabelText(/current ip/i) as HTMLInputElement;
expect(currentIpInput.value).toBe('192.168.1.60');
const schematicInput = screen.getByLabelText(/schematic id/i) as HTMLInputElement; const schematicInput = screen.getByLabelText(/schematic id/i) as HTMLInputElement;
expect(schematicInput.value).toBe('existing-schematic-456'); expect(schematicInput.value).toBe('existing-schematic-456');
const maintenanceCheckbox = screen.getByLabelText(/maintenance/i) as HTMLInputElement;
expect(maintenanceCheckbox.checked).toBe(false);
}); });
it('does NOT auto-generate hostname', async () => { it('does NOT auto-generate hostname', async () => {
@@ -418,7 +414,6 @@ describe('NodeForm Integration Tests', () => {
role: 'controlplane', role: 'controlplane',
disk: '/dev/nvme0n1', disk: '/dev/nvme0n1',
targetIp: '192.168.1.105', targetIp: '192.168.1.105',
currentIp: '192.168.1.60',
interface: 'eth0', interface: 'eth0',
schematicId: 'existing-schematic-456', schematicId: 'existing-schematic-456',
maintenance: false, maintenance: false,
@@ -553,7 +548,6 @@ describe('NodeForm Integration Tests', () => {
disk: '/dev/nvme0n1', disk: '/dev/nvme0n1',
interface: 'eth1', interface: 'eth1',
targetIp: '192.168.1.105', targetIp: '192.168.1.105',
currentIp: '192.168.1.60',
schematicId: 'existing-schematic', schematicId: 'existing-schematic',
maintenance: false, maintenance: false,
}; };
@@ -589,7 +583,6 @@ describe('NodeForm Integration Tests', () => {
disk: '/dev/nvme0n1', // NOT /dev/sda from detection disk: '/dev/nvme0n1', // NOT /dev/sda from detection
interface: 'eth1', // NOT eth0 from detection interface: 'eth1', // NOT eth0 from detection
targetIp: '192.168.1.105', targetIp: '192.168.1.105',
currentIp: '192.168.1.60',
}); });
}); });
}); });
@@ -881,8 +874,9 @@ describe('NodeForm Integration Tests', () => {
const hostnameInput = screen.getByLabelText(/hostname/i) as HTMLInputElement; const hostnameInput = screen.getByLabelText(/hostname/i) as HTMLInputElement;
expect(hostnameInput.value).toBe('test-control-1'); expect(hostnameInput.value).toBe('test-control-1');
const currentIpInput = screen.getByLabelText(/current ip/i) as HTMLInputElement; // Control plane nodes should auto-calculate targetIp from VIP (192.168.1.100 + 1)
expect(currentIpInput.value).toBe(''); const targetIpInput = screen.getByLabelText(/target ip/i) as HTMLInputElement;
expect(targetIpInput.value).toBe('192.168.1.101');
const diskInput = screen.getByLabelText(/disk/i) as HTMLInputElement; const diskInput = screen.getByLabelText(/disk/i) as HTMLInputElement;
expect(diskInput.value).toBe(''); expect(diskInput.value).toBe('');
@@ -906,8 +900,8 @@ describe('NodeForm Integration Tests', () => {
{ wrapper: createWrapper(createTestQueryClient()) } { wrapper: createWrapper(createTestQueryClient()) }
); );
const currentIpInput = screen.getByLabelText(/current ip/i) as HTMLInputElement; const targetIpInput = screen.getByLabelText(/target ip/i) as HTMLInputElement;
expect(currentIpInput.value).toBe('192.168.1.75'); expect(targetIpInput.value).toBe('192.168.1.75');
}); });
it('handles detection with no disks', async () => { it('handles detection with no disks', async () => {
@@ -1219,7 +1213,6 @@ describe('NodeForm Integration Tests', () => {
role: 'worker' as const, role: 'worker' as const,
disk: '/dev/sda', disk: '/dev/sda',
interface: 'eth0', interface: 'eth0',
currentIp: '192.168.1.50',
maintenance: true, maintenance: true,
}; };

View File

@@ -54,6 +54,9 @@ export function useNodes(instanceName: string | null | undefined) {
const applyMutation = useMutation({ const applyMutation = useMutation({
mutationFn: (nodeName: string) => nodesApi.apply(instanceName!, nodeName), mutationFn: (nodeName: string) => nodesApi.apply(instanceName!, nodeName),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['instances', instanceName, 'nodes'] });
},
}); });
const fetchTemplatesMutation = useMutation({ const fetchTemplatesMutation = useMutation({