Skip to content
Draft
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
48 changes: 48 additions & 0 deletions packages/adk/tests/fetch-utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* ADK Fetch Utils Unit Tests
*
* Tests for fetch-utils.ts functions that handle fetch response normalization.
*/

import { describe, it, expect } from 'vitest';
import { toText } from '../src/base/fetch-utils';

describe('toText', () => {
it('should return string as-is', async () => {
const result = await toText('hello world');
expect(result).toBe('hello world');
});

it('should handle Response-like object with text method', async () => {
const mockResponse = {
text: () => Promise.resolve('response text'),
};
const result = await toText(mockResponse);
expect(result).toBe('response text');
});

it('should convert null to empty string', async () => {
const result = await toText(null);
expect(result).toBe('');
});

it('should convert undefined to empty string', async () => {
const result = await toText(undefined);
expect(result).toBe('');
});

it('should convert number to string', async () => {
const result = await toText(123);
expect(result).toBe('123');
});

it('should convert object to string', async () => {
const result = await toText({ key: 'value' });
expect(result).toBe('[object Object]');
});

it('should handle object with text property but not a function', async () => {
const result = await toText({ text: 'not a function' });
expect(result).toBe('[object Object]');
});
});
280 changes: 280 additions & 0 deletions packages/adk/tests/registry.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
/**
* ADK Registry Unit Tests
*
* Tests for registry.ts functions that handle ADT type to ADK kind mapping
* and object type registration/resolution.
*/

import { describe, it, expect, beforeEach } from 'vitest';
import {
parseAdtType,
getMainType,
registerObjectType,
resolveType,
resolveKind,
getKindForType,
getTypeForKind,
isTypeRegistered,
getRegisteredTypes,
getRegisteredKinds,
getEndpointForType,
} from '../src/base/registry';
import * as kinds from '../src/base/kinds';
import type { AdkKind } from '../src/base/kinds';

// Mock AdkObject class for testing
class MockAdkObject {
constructor(
public ctx: unknown,
public nameOrData: string | unknown,

Check warning on line 29 in packages/adk/tests/registry.test.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

'unknown' overrides all other types in this union type.

See more on https://sonarcloud.io/project/issues?id=abapify_adt-cli&issues=AZ1-eMumXISY38E6Tzsi&open=AZ1-eMumXISY38E6Tzsi&pullRequest=97
) {}
}

describe('parseAdtType', () => {
it('should parse full type with sub type', () => {
const result = parseAdtType('DEVC/K');
expect(result).toEqual({
full: 'DEVC/K',
main: 'DEVC',
sub: 'K',
});
});

it('should parse main type without sub type', () => {
const result = parseAdtType('CLAS');
expect(result).toEqual({
full: 'CLAS',
main: 'CLAS',
sub: undefined,
});
});

it('should handle lowercase input', () => {
const result = parseAdtType('tabl/ds');
expect(result).toEqual({
full: 'tabl/ds',
main: 'TABL',
sub: 'DS',
});
});

it('should handle empty sub type', () => {
const result = parseAdtType('TABL/');
expect(result).toEqual({
full: 'TABL/',
main: 'TABL',
sub: '',
});
});
});

describe('getMainType', () => {
it('should return main type from full type', () => {
expect(getMainType('DEVC/K')).toBe('DEVC');
});

it('should return type as-is for main type', () => {
expect(getMainType('CLAS')).toBe('CLAS');
});

it('should handle lowercase input', () => {
expect(getMainType('prog')).toBe('PROG');
});
});

describe('registerObjectType', () => {
beforeEach(() => {
// Reset would be needed here but since we're using internal registry,
// we'll test in isolation by importing fresh functions
});
Comment on lines +86 to +89
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify shared mutable registry state + current no-op beforeEach
rg -n -C2 --type=ts 'const (registry|adtToKind|kindToAdt)\s*=\s*new Map' packages/adk/src/base/registry.ts
rg -n -C4 --type=ts 'beforeEach\(' packages/adk/tests/registry.test.ts
rg -n --type=ts 'registerObjectType\(' packages/adk/tests/registry.test.ts

Repository: abapify/adt-cli

Length of output: 1706


🏁 Script executed:

#!/bin/bash
# Check if there's a reset/clear function for the registry
rg -n --type=ts 'export.*function.*(reset|clear)' packages/adk/src/base/registry.ts

# Check full registry.ts to see all exports
rg -n --type=ts '^export' packages/adk/src/base/registry.ts

# Check test structure - look for test isolation patterns
head -100 packages/adk/tests/registry.test.ts

Repository: abapify/adt-cli

Length of output: 3570


🏁 Script executed:

#!/bin/bash
# View entire registry.test.ts to understand test structure
wc -l packages/adk/tests/registry.test.ts

# Look at test structure - see if tests are separate describe blocks or same block
rg -n 'describe\(|it\(' packages/adk/tests/registry.test.ts | head -50

Repository: abapify/adt-cli

Length of output: 2228


🏁 Script executed:

#!/bin/bash
# View the registerObjectType test suite in detail (lines 85-125)
sed -n '85,155p' packages/adk/tests/registry.test.ts

Repository: abapify/adt-cli

Length of output: 2206


🏁 Script executed:

#!/bin/bash
# View the test that claims to check empty state (lines 220-235)
sed -n '219,280p' packages/adk/tests/registry.test.ts

Repository: abapify/adt-cli

Length of output: 2066


Reset registry state between tests to eliminate order-dependent behavior.

The module-level Maps (registry, adtToKind, kindToAdt) are never reset between test cases. Tests that register the same types interfere with each other—particularly the test at line 230 which explicitly checks for "empty array" but assumes fresh state (as noted in its comment). The beforeEach hook is a no-op with a misleading comment; "importing fresh functions" does not create isolation because all tests share the same module-level mutable state. Export a reset function from packages/adk/src/base/registry.ts and call it in the beforeEach hook.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/adk/tests/registry.test.ts` around lines 86 - 89, The tests share
module-level mutable Maps (registry, adtToKind, kindToAdt) so add and export a
reset function (e.g., resetRegistry) in the registry module that clears these
Maps, then import and call resetRegistry() in the test file's beforeEach to
ensure each test runs with a fresh registry state; reference the existing
symbols registry, adtToKind, kindToAdt and call the new resetRegistry() from the
beforeEach in registry.test.ts.


it('should register a type with endpoint and nameTransform', () => {
const mockConstructor = MockAdkObject as any;

registerObjectType('PROG', kinds.Program, mockConstructor, {
endpoint: 'abap/ programs',
nameTransform: 'preserve',
});

const entry = resolveType('PROG');
expect(entry).toBeDefined();
expect(entry?.kind).toBe(kinds.Program);
expect(entry?.endpoint).toBe('abap/ programs');
expect(entry?.nameTransform).toBe('preserve');
});

it('should register without optional parameters', () => {
const mockConstructor = MockAdkObject as any;

registerObjectType('TEST', 'TestType' as AdkKind, mockConstructor);

const entry = resolveType('TEST');
expect(entry).toBeDefined();
expect(entry?.kind).toBe('TestType');
});

it('should handle case-insensitive registration', () => {
const mockConstructor = MockAdkObject as any;

registerObjectType('prog', kinds.Program, mockConstructor);

expect(resolveType('PROG')).toBeDefined();
expect(resolveType('prog')).toBeDefined();
});
});

describe('resolveType', () => {
it('should resolve exact type match first', () => {
const mockConstructor = MockAdkObject as any;

registerObjectType('MYTAB', kinds.Table, mockConstructor, {
endpoint: 'ddic/tables',
});
registerObjectType(
'MYTAB/DS',
kinds.Structure as AdkKind,
mockConstructor,
{ endpoint: 'ddic/structs' },
);

const entry = resolveType('MYTAB/DS');
expect(entry?.endpoint).toBe('ddic/structs');
});

it('should fall back to main type if full type not found', () => {
const mockConstructor = MockAdkObject as any;

registerObjectType('ANOTAB', kinds.Table, mockConstructor, {
endpoint: 'ddic/tables',
});

const entry = resolveType('ANOTAB/DS');
expect(entry?.endpoint).toBe('ddic/tables');
});

it('should return undefined for unregistered type', () => {
const entry = resolveType('UNREGISTERED');
expect(entry).toBeUndefined();
});
});

describe('getKindForType', () => {
it('should return kind for registered type', () => {
const mockConstructor = MockAdkObject as any;
registerObjectType('CLAS', kinds.Class, mockConstructor);

const kind = getKindForType('CLAS');
expect(kind).toBe(kinds.Class);
});

it('should return kind for full type', () => {
const mockConstructor = MockAdkObject as any;
registerObjectType('TABL', kinds.Table, mockConstructor);

const kind = getKindForType('TABL/DS');
expect(kind).toBe(kinds.Table);
});

it('should return undefined for unregistered type', () => {
const kind = getKindForType('UNREG');
expect(kind).toBeUndefined();
});
});

describe('getTypeForKind', () => {
it('should return ADT type for registered kind', () => {
const mockConstructor = MockAdkObject as any;
registerObjectType('CLAS', kinds.Class, mockConstructor);

const type = getTypeForKind(kinds.Class);
expect(type).toBe('CLAS');
});

it('should return undefined for unregistered kind', () => {
const type = getTypeForKind('UnknownKind' as AdkKind);
expect(type).toBeUndefined();
});
});

describe('isTypeRegistered', () => {
it('should return true for registered type', () => {
const mockConstructor = MockAdkObject as any;
registerObjectType('CLAS', kinds.Class, mockConstructor);

expect(isTypeRegistered('CLAS')).toBe(true);
});

it('should return false for unregistered type', () => {
expect(isTypeRegistered('UNREG')).toBe(false);
});

it('should check main type only', () => {
const mockConstructor = MockAdkObject as any;
registerObjectType('TABL', kinds.Table, mockConstructor);

expect(isTypeRegistered('TABL/DS')).toBe(true);
});
});

describe('getRegisteredTypes', () => {
it('should return array of registered types', () => {
const mockConstructor = MockAdkObject as any;
registerObjectType('TYPE1', 'Type1' as AdkKind, mockConstructor);
registerObjectType('TYPE2', 'Type2' as AdkKind, mockConstructor);

const types = getRegisteredTypes();
expect(types).toContain('TYPE1');
expect(types).toContain('TYPE2');
});

it('should return empty array when nothing registered', () => {
// Note: This assumes fresh state - may need adjustment
const types = getRegisteredTypes();
expect(Array.isArray(types)).toBe(true);
});
Comment on lines +230 to +234
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

The “empty array” test does not validate emptiness.

Line 233 only checks array shape, not that it is empty. After state isolation is fixed, assert toEqual([]) (or toHaveLength(0)).

Suggested assertion update
-    expect(Array.isArray(types)).toBe(true);
+    expect(types).toEqual([]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it('should return empty array when nothing registered', () => {
// Note: This assumes fresh state - may need adjustment
const types = getRegisteredTypes();
expect(Array.isArray(types)).toBe(true);
});
it('should return empty array when nothing registered', () => {
// Note: This assumes fresh state - may need adjustment
const types = getRegisteredTypes();
expect(types).toEqual([]);
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/adk/tests/registry.test.ts` around lines 230 - 234, The test "should
return empty array when nothing registered" currently only asserts the result is
an array; update it to assert emptiness instead (e.g., replace the
Array.isArray(types) assertion with expect(types).toEqual([]) or
expect(types).toHaveLength(0)) so getRegisteredTypes() is validated to return an
empty array when no types are registered.

});

describe('getRegisteredKinds', () => {
it('should return array of registered kinds', () => {
const mockConstructor = MockAdkObject as any;
registerObjectType('TYPE1', 'Kind1' as AdkKind, mockConstructor);

const kinds_list = getRegisteredKinds();
expect(kinds_list).toContain('Kind1');
});
});

describe('resolveKind', () => {
it('should resolve registered kind to entry', () => {
const mockConstructor = MockAdkObject as any;
registerObjectType('CLAS', kinds.Class, mockConstructor, {
endpoint: 'oo/classes',
});

const entry = resolveKind(kinds.Class);
expect(entry?.kind).toBe(kinds.Class);
expect(entry?.endpoint).toBe('oo/classes');
});

it('should return undefined for unregistered kind', () => {
const entry = resolveKind('UnknownKind' as AdkKind);
expect(entry).toBeUndefined();
});
});

describe('getEndpointForType', () => {
it('should return endpoint for registered type', () => {
const mockConstructor = MockAdkObject as any;
registerObjectType('PROG', kinds.Program, mockConstructor, {
endpoint: 'abap/programs',
});

const endpoint = getEndpointForType('PROG');
expect(endpoint).toBe('abap/programs');
});

it('should return undefined for unregistered type', () => {
const endpoint = getEndpointForType('UNREG');
expect(endpoint).toBeUndefined();
});
});
Loading