blob: 94a7ba896ab60244befaecbab70ad7469e249561 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
'use strict';
const assert = require('node:assert/strict');
const test = require('node:test');
const {
parseDataTypeMapOperations,
parseJavaOperations,
parseTypeScriptOperations,
validateContract
} = require('./check-websocket-contract');
test('parses simple Java enum constants while ignoring comments', () => {
const operations = parseJavaOperations(`
public class Message {
public enum OP {
FIRST, // a comment containing },
/* another comment containing }, */ SECOND,
THIRD,
}
}
`);
assert.deepEqual([...operations], ['FIRST', 'SECOND', 'THIRD']);
});
test('accepts an optional Java enum semicolon', () => {
const operations = parseJavaOperations('public enum OP { FIRST, SECOND; }');
assert.deepEqual([...operations], ['FIRST', 'SECOND']);
});
test('fails closed for unsupported or malformed Java enum declarations', () => {
assert.throws(() => parseJavaOperations('public enum OP { FIRST,,SECOND }'), /contains an empty operation/);
assert.throws(
() => parseJavaOperations('public enum OP { FIRST("value") }'),
/must contain only simple enum constants/
);
assert.throws(() => parseJavaOperations('public enum OP { FIRST, \/\* unclosed'), /unclosed block comment/);
assert.throws(
() => parseJavaOperations('public enum OP { FIRST }\npublic enum OP { SECOND }'),
/Expected exactly one public enum OP/
);
});
test('classifies frontend-only TypeScript operations from inline JSDoc', () => {
const operations = parseTypeScriptOperations(`
export enum OP {
WIRE = 'WIRE',
/** @frontendOnly Emitted locally without using the websocket. */
LOCAL = 'LOCAL'
}
`);
assert.deepEqual([...operations.wireOperations], ['WIRE']);
assert.deepEqual([...operations.frontendOnlyOperations], ['LOCAL']);
});
test('requires valid TypeScript wire values and frontend-only explanations', () => {
assert.throws(
() => parseTypeScriptOperations("export enum OP { FIRST = 'SECOND' }"),
/must use the wire value 'FIRST'/
);
assert.throws(
() =>
parseTypeScriptOperations(`
export enum OP {
/** @frontendOnly */
LOCAL = 'LOCAL'
}
`),
/must explain why it is @frontendOnly/
);
});
test('compares Java and TypeScript operation sets without requiring the same order', () => {
const javaOperations = new Set(['FIRST', 'SECOND']);
const typeScriptOperations = parseTypeScriptOperations(`
export enum OP {
SECOND = 'SECOND',
FIRST = 'FIRST',
/** @frontendOnly Emitted locally without using the websocket. */
LOCAL = 'LOCAL'
}
`);
const dataTypeMaps = `
interface MessageSendDataTypeMap { [OP.FIRST]: undefined; }
interface MessageReceiveDataTypeMap { [OP.SECOND]: undefined; [OP.LOCAL]: undefined; }
`;
const sendOperations = parseDataTypeMapOperations(dataTypeMaps, 'MessageSendDataTypeMap');
const receiveOperations = parseDataTypeMapOperations(dataTypeMaps, 'MessageReceiveDataTypeMap');
assert.doesNotThrow(() => validateContract(javaOperations, typeScriptOperations, sendOperations, receiveOperations));
});
test('reports operation drift and rejects frontend-only send operations', () => {
const javaOperations = new Set(['FIRST', 'MISSING']);
const typeScriptOperations = parseTypeScriptOperations(`
export enum OP {
FIRST = 'FIRST',
EXTRA = 'EXTRA',
/** @frontendOnly Emitted locally without using the websocket. */
LOCAL = 'LOCAL'
}
`);
assert.throws(
() => validateContract(javaOperations, typeScriptOperations, new Set(), new Set(['LOCAL'])),
/missing from TypeScript=\[MISSING\], extra in TypeScript=\[EXTRA\]/
);
const matchingTypeScriptOperations = parseTypeScriptOperations(`
export enum OP {
FIRST = 'FIRST',
MISSING = 'MISSING',
/** @frontendOnly Emitted locally without using the websocket. */
LOCAL = 'LOCAL'
}
`);
assert.throws(
() => validateContract(javaOperations, matchingTypeScriptOperations, new Set(['LOCAL']), new Set(['LOCAL'])),
/Frontend-only operation LOCAL cannot be in MessageSendDataTypeMap/
);
});