-
Notifications
You must be signed in to change notification settings - Fork 1
Add unit tests for adk registry and fetch-utils #97
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
ThePlenkov
wants to merge
4
commits into
main
Choose a base branch
from
add-adk-unit-tests
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+323
−0
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
108495c
Add unit tests for adk registry and fetch-utils
openhands-agent 61d852b
Merge branch 'main' into add-adk-unit-tests
ThePlenkov e95c8b0
Merge branch 'main' into add-adk-unit-tests
ThePlenkov 5bd40b8
review(adk tests): fix toText expectations, isolate registry state
ThePlenkov File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| /** | ||
| * 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 () => { | ||
| expect(await toText(null)).toBe(''); | ||
| }); | ||
|
|
||
| it('should convert undefined to empty string', async () => { | ||
| expect(await toText(undefined)).toBe(''); | ||
| }); | ||
|
|
||
| it('should convert number to string', async () => { | ||
| expect(await toText(123)).toBe('123'); | ||
| }); | ||
|
|
||
| it('should JSON-stringify plain objects', async () => { | ||
| const result = await toText({ key: 'value' }); | ||
| expect(result).toBe('{"key":"value"}'); | ||
| }); | ||
|
|
||
| it('should JSON-stringify objects with non-function text property', async () => { | ||
| // `text` is not a function, so the Response-like branch is skipped and | ||
| // the value falls through to the JSON.stringify path. | ||
| const result = await toText({ text: 'not a function' }); | ||
| expect(result).toBe('{"text":"not a function"}'); | ||
| }); | ||
|
|
||
| it('should fall back to String() when JSON.stringify throws', async () => { | ||
| // JSON.stringify throws on circular references — the catch branch in | ||
| // toText() must return the default string coercion instead of propagating. | ||
| const circular: Record<string, unknown> = {}; | ||
| circular.self = circular; | ||
| const result = await toText(circular); | ||
| expect(result).toBe('[object Object]'); | ||
| }); | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,250 @@ | ||
| /** | ||
| * ADK Registry Unit Tests | ||
| * | ||
| * Tests for registry.ts functions that handle ADT type to ADK kind mapping | ||
| * and object type registration/resolution. | ||
| * | ||
| * NOTE: The registry is module-level singleton state (`registry`, `adtToKind`, | ||
| * `kindToAdt` Maps in `src/base/registry.ts`). Each test resets it via | ||
| * `__resetRegistryForTests()` so ordering and import side effects from other | ||
| * modules cannot leak in. Do not remove the `beforeEach` call below. | ||
| */ | ||
|
|
||
| import { describe, it, expect, beforeEach } from 'vitest'; | ||
| import { | ||
| parseAdtType, | ||
| getMainType, | ||
| registerObjectType, | ||
| resolveType, | ||
| resolveKind, | ||
| getKindForType, | ||
| getTypeForKind, | ||
| isTypeRegistered, | ||
| getRegisteredTypes, | ||
| getRegisteredKinds, | ||
| getEndpointForType, | ||
| __resetRegistryForTests, | ||
| type AdkObjectConstructor, | ||
| } from '../src/base/registry'; | ||
| import * as kinds from '../src/base/kinds'; | ||
| import type { AdkKind } from '../src/base/kinds'; | ||
|
|
||
| // Minimal stand-in for an AdkObject constructor; tests only care about | ||
| // identity round-tripping through the registry, not the object shape. | ||
| class MockAdkObject { | ||
| constructor( | ||
| public ctx: unknown, | ||
| public nameOrData: unknown, | ||
| ) {} | ||
| } | ||
|
|
||
| // Single typed cast — the mock intentionally does not implement the full | ||
| // AdkObject contract, so we bridge through `unknown` once here rather than | ||
| // sprinkling `as any` at every call site. | ||
| const mockCtor = MockAdkObject as unknown as AdkObjectConstructor; | ||
|
|
||
| beforeEach(() => { | ||
| __resetRegistryForTests(); | ||
| }); | ||
|
|
||
| describe('parseAdtType', () => { | ||
| it('should parse full type with sub type', () => { | ||
| expect(parseAdtType('DEVC/K')).toEqual({ | ||
| full: 'DEVC/K', | ||
| main: 'DEVC', | ||
| sub: 'K', | ||
| }); | ||
| }); | ||
|
|
||
| it('should parse main type without sub type', () => { | ||
| expect(parseAdtType('CLAS')).toEqual({ | ||
| full: 'CLAS', | ||
| main: 'CLAS', | ||
| sub: undefined, | ||
| }); | ||
| }); | ||
|
|
||
| it('should handle lowercase input', () => { | ||
| expect(parseAdtType('tabl/ds')).toEqual({ | ||
| full: 'tabl/ds', | ||
| main: 'TABL', | ||
| sub: 'DS', | ||
| }); | ||
| }); | ||
|
|
||
| it('should handle empty sub type', () => { | ||
| expect(parseAdtType('TABL/')).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', () => { | ||
| it('should register a type with endpoint and nameTransform', () => { | ||
| registerObjectType('PROG', kinds.Program, mockCtor, { | ||
| 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', () => { | ||
| registerObjectType('TEST', 'TestType' as AdkKind, mockCtor); | ||
|
|
||
| const entry = resolveType('TEST'); | ||
| expect(entry).toBeDefined(); | ||
| expect(entry?.kind).toBe('TestType'); | ||
| }); | ||
|
|
||
| it('should handle case-insensitive registration', () => { | ||
| registerObjectType('prog', kinds.Program, mockCtor); | ||
|
|
||
| expect(resolveType('PROG')).toBeDefined(); | ||
| expect(resolveType('prog')).toBeDefined(); | ||
| }); | ||
|
ThePlenkov marked this conversation as resolved.
|
||
| }); | ||
|
|
||
| describe('resolveType', () => { | ||
| it('should resolve exact type match first', () => { | ||
| registerObjectType('MYTAB', kinds.Table, mockCtor, { | ||
| endpoint: 'ddic/tables', | ||
| }); | ||
| registerObjectType('MYTAB/DS', kinds.Structure as AdkKind, mockCtor, { | ||
| endpoint: 'ddic/structs', | ||
| }); | ||
|
|
||
| expect(resolveType('MYTAB/DS')?.endpoint).toBe('ddic/structs'); | ||
| }); | ||
|
|
||
| it('should fall back to main type if full type not found', () => { | ||
| registerObjectType('ANOTAB', kinds.Table, mockCtor, { | ||
| endpoint: 'ddic/tables', | ||
| }); | ||
|
|
||
| expect(resolveType('ANOTAB/DS')?.endpoint).toBe('ddic/tables'); | ||
| }); | ||
|
|
||
| it('should return undefined for unregistered type', () => { | ||
| expect(resolveType('UNREGISTERED')).toBeUndefined(); | ||
| }); | ||
| }); | ||
|
|
||
| describe('getKindForType', () => { | ||
| it('should return kind for registered type', () => { | ||
| registerObjectType('CLAS', kinds.Class, mockCtor); | ||
| expect(getKindForType('CLAS')).toBe(kinds.Class); | ||
| }); | ||
|
|
||
| it('should return kind for full type', () => { | ||
| registerObjectType('TABL', kinds.Table, mockCtor); | ||
| expect(getKindForType('TABL/DS')).toBe(kinds.Table); | ||
| }); | ||
|
|
||
| it('should return undefined for unregistered type', () => { | ||
| expect(getKindForType('UNREG')).toBeUndefined(); | ||
| }); | ||
| }); | ||
|
|
||
| describe('getTypeForKind', () => { | ||
| it('should return ADT type for registered kind', () => { | ||
| registerObjectType('CLAS', kinds.Class, mockCtor); | ||
| expect(getTypeForKind(kinds.Class)).toBe('CLAS'); | ||
| }); | ||
|
|
||
| it('should return undefined for unregistered kind', () => { | ||
| expect(getTypeForKind('UnknownKind' as AdkKind)).toBeUndefined(); | ||
| }); | ||
| }); | ||
|
|
||
| describe('isTypeRegistered', () => { | ||
| it('should return true for registered main type', () => { | ||
| registerObjectType('CLAS', kinds.Class, mockCtor); | ||
| expect(isTypeRegistered('CLAS')).toBe(true); | ||
| }); | ||
|
|
||
| it('should return false for unregistered type', () => { | ||
| expect(isTypeRegistered('UNREG')).toBe(false); | ||
| }); | ||
|
|
||
| it('should treat full types as registered when main type is registered', () => { | ||
| registerObjectType('TABL', kinds.Table, mockCtor); | ||
| expect(isTypeRegistered('TABL/DS')).toBe(true); | ||
| }); | ||
| }); | ||
|
|
||
| describe('getRegisteredTypes', () => { | ||
| it('should return array of registered types', () => { | ||
| registerObjectType('TYPE1', 'Type1' as AdkKind, mockCtor); | ||
| registerObjectType('TYPE2', 'Type2' as AdkKind, mockCtor); | ||
|
|
||
| const types = getRegisteredTypes(); | ||
| expect(types).toContain('TYPE1'); | ||
| expect(types).toContain('TYPE2'); | ||
| }); | ||
|
|
||
| it('should return empty array when nothing registered', () => { | ||
| // Registry was cleared in beforeEach; no registrations have occurred in | ||
| // this test yet, so the list must be empty (not just array-shaped). | ||
| expect(getRegisteredTypes()).toEqual([]); | ||
| }); | ||
|
ThePlenkov marked this conversation as resolved.
ThePlenkov marked this conversation as resolved.
|
||
| }); | ||
|
|
||
| describe('getRegisteredKinds', () => { | ||
| it('should return array of registered kinds', () => { | ||
| registerObjectType('TYPE1', 'Kind1' as AdkKind, mockCtor); | ||
|
|
||
| const kindsList = getRegisteredKinds(); | ||
| expect(kindsList).toContain('Kind1'); | ||
| }); | ||
| }); | ||
|
|
||
| describe('resolveKind', () => { | ||
| it('should resolve registered kind to entry', () => { | ||
| registerObjectType('CLAS', kinds.Class, mockCtor, { | ||
| 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', () => { | ||
| expect(resolveKind('UnknownKind' as AdkKind)).toBeUndefined(); | ||
| }); | ||
| }); | ||
|
|
||
| describe('getEndpointForType', () => { | ||
| it('should return endpoint for registered type', () => { | ||
| registerObjectType('PROG', kinds.Program, mockCtor, { | ||
| endpoint: 'abap/programs', | ||
| }); | ||
|
|
||
| expect(getEndpointForType('PROG')).toBe('abap/programs'); | ||
| }); | ||
|
|
||
| it('should return undefined for unregistered type', () => { | ||
| expect(getEndpointForType('UNREG')).toBeUndefined(); | ||
| }); | ||
| }); | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.