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
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
import { SubscriptionProcessor } from '../src/sync/processors/subscription';
import { TransformerMutationType, processSubscriptionVariables } from '../src/sync/utils';
import { SchemaModel, InternalSchema } from '../src/types';

describe('Subscription Variables - Edge Cases & Safety', () => {
let mockGraphQL: jest.Mock;

beforeEach(() => {
mockGraphQL = jest.fn();
jest.clearAllMocks();
});

const createTestSchema = (): InternalSchema => ({
namespaces: {
user: {
name: 'user',
models: {
Todo: {
name: 'Todo',
pluralName: 'Todos',
syncable: true,
attributes: [],
fields: {
id: {
name: 'id',
type: 'ID',
isRequired: true,
isArray: false,
},
},
},
},
relationships: {},
enums: {},
nonModels: {},
},
},
version: '1',
codegenVersion: '3.0.0',
});

describe('Mutation Protection', () => {
it('should not allow mutations to affect cached values', () => {
const schema = createTestSchema();
const sharedObject = { storeId: 'initial' };

const cache = new WeakMap();
const result1 = processSubscriptionVariables(
schema.namespaces.user.models.Todo,
TransformerMutationType.CREATE,
sharedObject,
cache,
);

sharedObject.storeId = 'mutated';

const result2 = processSubscriptionVariables(
schema.namespaces.user.models.Todo,
TransformerMutationType.CREATE,
sharedObject,
cache,
);

expect(result1).toEqual(result2);
expect(result2?.storeId).not.toBe('mutated');
});

it('should handle circular references gracefully', () => {
const schema = createTestSchema();
const circular: any = { storeId: 'test' };
circular.self = circular;

const cache = new WeakMap();
const result = processSubscriptionVariables(
schema.namespaces.user.models.Todo,
TransformerMutationType.CREATE,
circular,
cache,
);

expect(result).toBeDefined();
expect(result?.storeId).toBe('test');
});
});

describe('Invalid Input Handling', () => {
it('should reject non-object static variables', () => {
const schema = createTestSchema();

const testCases = [
{ value: 'string', desc: 'string' },
{ value: 123, desc: 'number' },
{ value: true, desc: 'boolean' },
{ value: ['array'], desc: 'array' },
];

testCases.forEach(({ value, desc }) => {
const cache = new WeakMap();
const result = processSubscriptionVariables(
schema.namespaces.user.models.Todo,
TransformerMutationType.CREATE,
value as any,
cache,
);

expect(result).toBeUndefined();
});
});

it('should handle Object.create(null) objects', () => {
const schema = createTestSchema();
const nullProtoObj = Object.create(null);
nullProtoObj.storeId = 'test';

const cache = new WeakMap();
const result = processSubscriptionVariables(
schema.namespaces.user.models.Todo,
TransformerMutationType.CREATE,
nullProtoObj,
cache,
);

expect(result).toBeDefined();
expect(result?.storeId).toBe('test');
});

it('should handle function that throws', () => {
const schema = createTestSchema();

const cache = new WeakMap();
const mockFn = () => {
throw new Error('Function error');
};
const result = processSubscriptionVariables(
schema.namespaces.user.models.Todo,
TransformerMutationType.CREATE,
mockFn,
cache,
);

expect(result).toBeUndefined();
});

it('should handle function returning non-object', () => {
const schema = createTestSchema();

const testCases = [
{ value: null, desc: 'null' },
{ value: undefined, desc: 'undefined' },
{ value: 'string', desc: 'string' },
{ value: 123, desc: 'number' },
{ value: ['array'], desc: 'array' },
];

testCases.forEach(({ value, desc }) => {
const cache = new WeakMap();
const mockFn = () => value;
const result = processSubscriptionVariables(
schema.namespaces.user.models.Todo,
TransformerMutationType.CREATE,
mockFn as any,
cache,
);

expect(result).toBeUndefined();
});
});
});

describe('Cache Behavior', () => {
it('should only call function once per operation', () => {
const schema = createTestSchema();
const mockFn = jest.fn(() => ({ storeId: 'test' }));

const cache = new WeakMap();
for (let i = 0; i < 5; i++) {
processSubscriptionVariables(
schema.namespaces.user.models.Todo,
TransformerMutationType.CREATE,
mockFn,
cache,
);
}

expect(mockFn).toHaveBeenCalledTimes(1);
expect(mockFn).toHaveBeenCalledWith(TransformerMutationType.CREATE);

processSubscriptionVariables(
schema.namespaces.user.models.Todo,
TransformerMutationType.UPDATE,
mockFn,
cache,
);

expect(mockFn).toHaveBeenCalledTimes(2);
expect(mockFn).toHaveBeenCalledWith(TransformerMutationType.UPDATE);
});

it('should clear cache on stop', async () => {
const schema = createTestSchema();
const mockFn = jest.fn(() => ({ storeId: 'test' }));

const processor = new SubscriptionProcessor(
schema,
new WeakMap(),
{},
'DEFAULT' as any,
jest.fn(),
{ InternalAPI: { graphql: mockGraphQL } } as any,
{
subscriptionVariables: {
Todo: mockFn,
},
},
);

let cache = new WeakMap();
processSubscriptionVariables(
schema.namespaces.user.models.Todo,
TransformerMutationType.CREATE,
mockFn,
cache,
);
expect(mockFn).toHaveBeenCalledTimes(1);

await processor.stop();
cache = new WeakMap();

processSubscriptionVariables(
schema.namespaces.user.models.Todo,
TransformerMutationType.CREATE,
mockFn,
cache,
);

expect(mockFn).toHaveBeenCalledTimes(2);
});
});
});
Loading