Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions src/components/StateChip.property.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright Contributors to Agones a Series of LF Projects, LLC.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as fc from 'fast-check';
import React from 'react';
import { describe, expect, it } from 'vitest';
import { StateChip } from './StateChip';

/**
* The complete set of valid MUI Chip color values.
*
* @see {@link https://mui.com/material-ui/api/chip/#chip-prop-color | MUI Chip color prop}
*/
const VALID_CHIP_COLORS = new Set([
'default',
'primary',
'secondary',
'error',
'info',
'success',
'warning',
]);

/**
* Known Agones GameServer lifecycle states and their expected chip colors.
*
* @see {@link https://agones.dev/site/docs/reference/gameserver/#gameserver-state-diagram | Agones GameServer State Diagram}
*/
const KNOWN_STATE_COLORS: Record<string, string> = {
PortAllocation: 'info',
Creating: 'info',
Starting: 'info',
Scheduled: 'info',
RequestReady: 'info',
Ready: 'success',
Allocated: 'warning',
Reserved: 'secondary',
Shutdown: 'default',
Error: 'error',
Unhealthy: 'error',
};

describe('StateChip β€” property tests', () => {
it('should never crash on any arbitrary string input', () => {
fc.assert(
fc.property(fc.string(), state => {
const element = React.createElement(StateChip, { state });
expect(element).toBeDefined();
expect(element.type).toBe(StateChip);
})
);
});

it('should always resolve to a valid MUI Chip color for any input', () => {
fc.assert(
fc.property(fc.string(), state => {
// Replicate the exact lookup logic used by StateChip:
// STATE_COLORS[state] ?? 'default'
// Use Object.hasOwn to avoid prototype keys like __proto__
const resolved = Object.prototype.hasOwnProperty.call(KNOWN_STATE_COLORS, state)
? KNOWN_STATE_COLORS[state]
: 'default';
expect(VALID_CHIP_COLORS.has(resolved)).toBe(true);
})
);
});

it('should map every known Agones state to its documented color', () => {
fc.assert(
fc.property(fc.constantFrom(...Object.keys(KNOWN_STATE_COLORS)), state => {
const STATE_COLORS: Record<string, string> = KNOWN_STATE_COLORS;
const resolved = STATE_COLORS[state] ?? 'default';
expect(resolved).toBe(KNOWN_STATE_COLORS[state]);
})
);
});
});
107 changes: 107 additions & 0 deletions src/resources/fleet.property.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Copyright Contributors to Agones a Series of LF Projects, LLC.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as fc from 'fast-check';
import { describe, expect, it } from 'vitest';
import { Fleet } from './fleet';

/**
* Arbitrary that generates random Agones Fleet JSON blobs.
* Covers present/absent status fields and different strategy configurations.
*/
function fcFleetJson(): fc.Arbitrary<any> {
return fc.record({
apiVersion: fc.constant('agones.dev/v1'),
kind: fc.constant('Fleet'),
metadata: fc.record({
name: fc.string({ minLength: 1, maxLength: 30 }),
namespace: fc.string({ minLength: 1, maxLength: 30 }),
uid: fc.uuid(),
}),
spec: fc.record({
replicas: fc.nat({ max: 100 }),
scheduling: fc.oneof(fc.constantFrom('Packed', 'Distributed'), fc.constant('')),
template: fc.constant({}),
strategy: fc.option(
fc.record({
type: fc.oneof(fc.constantFrom('RollingUpdate', 'Recreate'), fc.constant('')),
rollingUpdate: fc.option(
fc.record({
maxSurge: fc.oneof(fc.nat({ max: 10 }), fc.constant('25%')),
maxUnavailable: fc.oneof(fc.nat({ max: 10 }), fc.constant('25%')),
}),
{ nil: undefined }
),
}),
{ nil: undefined }
),
}),
status: fc.option(
fc.record({
replicas: fc.option(fc.nat({ max: 100 }), { nil: undefined }),
readyReplicas: fc.option(fc.nat({ max: 100 }), { nil: undefined }),
reservedReplicas: fc.option(fc.nat({ max: 100 }), { nil: undefined }),
allocatedReplicas: fc.option(fc.nat({ max: 100 }), { nil: undefined }),
}),
{ nil: undefined }
),
});
}

describe('Fleet getters β€” property tests', () => {
it('numeric getters should always return typeof number', () => {
fc.assert(
fc.property(fcFleetJson(), json => {
const fleet = new Fleet(json);
expect(typeof fleet.desiredReplicas).toBe('number');
expect(typeof fleet.currentReplicas).toBe('number');
expect(typeof fleet.allocatedReplicas).toBe('number');
expect(typeof fleet.readyReplicas).toBe('number');
expect(typeof fleet.reservedReplicas).toBe('number');
})
);
});

it('scheduling should always default to "Packed" when falsy', () => {
fc.assert(
fc.property(fcFleetJson(), json => {
const fleet = new Fleet(json);
const result = fleet.scheduling;
expect(typeof result).toBe('string');
expect(result.length).toBeGreaterThan(0);
// When spec.scheduling is empty/falsy, it defaults to 'Packed'
if (!json.spec.scheduling) {
expect(result).toBe('Packed');
}
})
);
});

it('strategy should always default to "RollingUpdate" when falsy', () => {
fc.assert(
fc.property(fcFleetJson(), json => {
const fleet = new Fleet(json);
const result = fleet.strategy;
expect(typeof result).toBe('string');
expect(result.length).toBeGreaterThan(0);
// When spec.strategy.type is empty/falsy/missing, it defaults to 'RollingUpdate'
if (!json.spec.strategy?.type) {
expect(result).toBe('RollingUpdate');
}
})
);
});
});
102 changes: 102 additions & 0 deletions src/resources/fleetautoscaler.property.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright Contributors to Agones a Series of LF Projects, LLC.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as fc from 'fast-check';
import { describe, expect, it } from 'vitest';
import { FleetAutoscaler } from './fleetautoscaler';

/**
* Arbitrary that generates random Agones FleetAutoscaler JSON blobs.
* Covers present/absent status fields and various policy types.
*/
function fcFleetAutoscalerJson(): fc.Arbitrary<any> {
return fc.record({
apiVersion: fc.constant('autoscaling.agones.dev/v1'),
kind: fc.constant('FleetAutoscaler'),
metadata: fc.record({
name: fc.string({ minLength: 1, maxLength: 30 }),
namespace: fc.string({ minLength: 1, maxLength: 30 }),
uid: fc.uuid(),
}),
spec: fc.record({
fleetName: fc.string({ minLength: 1, maxLength: 30 }),
policy: fc.record({
type: fc.constantFrom('Buffer', 'Webhook', 'Counter', 'List', 'Schedule', 'Chain'),
buffer: fc.option(
fc.record({
bufferSize: fc.oneof(fc.nat({ max: 50 }), fc.constant('50%')),
minReplicas: fc.nat({ max: 100 }),
maxReplicas: fc.nat({ max: 100 }),
}),
{ nil: undefined }
),
}),
sync: fc.option(
fc.record({
type: fc.constant('FixedInterval'),
fixedInterval: fc.option(fc.record({ seconds: fc.nat({ max: 300 }) }), {
nil: undefined,
}),
}),
{ nil: undefined }
),
}),
status: fc.option(
fc.record({
currentReplicas: fc.option(fc.nat({ max: 100 }), { nil: undefined }),
desiredReplicas: fc.option(fc.nat({ max: 100 }), { nil: undefined }),
ableToScale: fc.option(fc.boolean(), { nil: undefined }),
scalingLimited: fc.option(fc.boolean(), { nil: undefined }),
lastScaleTime: fc.option(
fc.date().map(d => d.toISOString()),
{ nil: undefined }
),
lastAppliedPolicy: fc.option(fc.string({ maxLength: 30 }), { nil: undefined }),
}),
{ nil: undefined }
),
});
}

describe('FleetAutoscaler getters β€” property tests', () => {
it('currentReplicas and desiredReplicas should always return numbers', () => {
fc.assert(
fc.property(fcFleetAutoscalerJson(), json => {
const fas = new FleetAutoscaler(json);
expect(typeof fas.currentReplicas).toBe('number');
expect(typeof fas.desiredReplicas).toBe('number');
})
);
});

it('ableToScale should always return a boolean', () => {
fc.assert(
fc.property(fcFleetAutoscalerJson(), json => {
const fas = new FleetAutoscaler(json);
expect(typeof fas.ableToScale).toBe('boolean');
})
);
});

it('scalingLimited should always return a boolean', () => {
fc.assert(
fc.property(fcFleetAutoscalerJson(), json => {
const fas = new FleetAutoscaler(json);
expect(typeof fas.scalingLimited).toBe('boolean');
})
);
});
});
Loading