-
Notifications
You must be signed in to change notification settings - Fork 185
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[JS] chore: MemoryFork and TestMemoryFork unit tests (#1051)
## Linked issues closes: #1052 ## Details Unit tests added for: (100% coverage on % statements, branch, funcs, and lines). - [x] `MemoryFork.ts` - [x] `TestMemoryFork.ts` ## Attestation Checklist - [x] My code follows the style guidelines of this project - I have checked for/fixed spelling, linting, and other errors - I have commented my code for clarity - I have made corresponding changes to the documentation (we use [TypeDoc](https://typedoc.org/) to document our code) - My changes generate no new warnings - I have added tests that validates my changes, and provides sufficient test coverage. I have tested with: - Local testing - E2E testing in Teams - New and existing unit tests pass locally with my changes
- Loading branch information
Showing
3 changed files
with
269 additions
and
6 deletions.
There are no files selected for viewing
This file contains 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,181 @@ | ||
import assert from 'assert'; | ||
import { MemoryFork } from './MemoryFork'; | ||
import { TestMemoryFork } from './TestMemoryFork'; | ||
|
||
describe('MemoryFork', () => { | ||
let mockMemory: MemoryFork; | ||
let testMemoryFork: TestMemoryFork; | ||
|
||
beforeEach(() => { | ||
testMemoryFork = new TestMemoryFork(); | ||
testMemoryFork.setValue('User.userId', '987'); | ||
mockMemory = new MemoryFork(testMemoryFork); | ||
}); | ||
|
||
it('should throw invalid state path error due to many substrings', () => { | ||
const path = 'random.random.conversationId'; | ||
assert.throws(() => mockMemory.deleteValue(path), new Error(`Invalid state path: ${path}`)); | ||
}); | ||
|
||
describe('setValue', () => { | ||
it('should assign a value to memory, where scope does not yet exist', () => { | ||
const path = 'Conversation.conversationId'; | ||
mockMemory.setValue(path, '123'); | ||
assert.equal(mockMemory.hasValue(path), true); | ||
assert.equal(mockMemory.getValue(path), '123'); | ||
}); | ||
|
||
it('should assign a value to memory, where scope already exists', () => { | ||
const pathOne = 'Conversation.conversationId'; | ||
mockMemory.setValue(pathOne, '123'); | ||
const pathTwo = 'Conversation.userId'; | ||
mockMemory.setValue(pathTwo, '432'); | ||
assert.equal(mockMemory.hasValue(pathOne), true); | ||
assert.equal(mockMemory.hasValue(pathTwo), true); | ||
assert.equal(mockMemory.getValue(pathOne), '123'); | ||
assert.equal(mockMemory.getValue(pathTwo), '432'); | ||
}); | ||
}); | ||
|
||
describe('getValue', () => { | ||
it('should retrieve existing value from forked memory', () => { | ||
const path = 'Conversation.conversationId'; | ||
mockMemory.setValue(path, '123'); | ||
assert.equal(mockMemory.getValue(path), '123'); | ||
}); | ||
|
||
it('should retrieve value from original memory, where forked memory does not contain specified scope', () => { | ||
const path = 'User.userId'; | ||
assert.equal(mockMemory.getValue(path), '987'); | ||
}); | ||
|
||
it('should retrieve value from original memory, where forked memory does not have specified name', () => { | ||
const pathOne = 'User.tokenId'; | ||
mockMemory.setValue(pathOne, '432'); | ||
const pathTwo = 'User.userId'; | ||
assert.equal(mockMemory.getValue(pathTwo), '987'); | ||
}); | ||
|
||
it('should retrieve value from original memory, where value exists in both original and forked', () => { | ||
const path = 'User.userId'; | ||
mockMemory.setValue(path, '567'); | ||
assert.equal(mockMemory.getValue(path), '567'); | ||
}); | ||
|
||
it('should return null as no value exists in forked and original memory', () => { | ||
const path = 'Conversation.tokenId'; | ||
assert.equal(mockMemory.getValue(path), null); | ||
}); | ||
}); | ||
|
||
describe('hasValue', () => { | ||
it('should check value from original memory, where forked memory does not contain specified scope', () => { | ||
const path = 'User.userId'; | ||
assert.equal(mockMemory.hasValue(path), true); | ||
}); | ||
|
||
it('should check value from original memory, where forked memory does not have specified name', () => { | ||
const pathOne = 'Conversation.conversationId'; | ||
mockMemory.setValue(pathOne, '123'); | ||
const pathTwo = 'Conversation.tokenId'; | ||
assert.equal(mockMemory.hasValue(pathTwo), false); | ||
}); | ||
|
||
it('should check non-existing value from original memory, where scope exists', () => { | ||
const path = 'User.tokenId'; | ||
assert.equal(mockMemory.hasValue(path), false); | ||
}); | ||
|
||
it('should check non-existing value from original memory, where scope does not exist', () => { | ||
const path = 'Conversation.tokenId'; | ||
assert.equal(mockMemory.hasValue(path), false); | ||
}); | ||
|
||
it('should perform a check using defaulted temp scope', () => { | ||
const path = 'conversationId'; | ||
assert.equal(mockMemory.hasValue(path), false); | ||
}); | ||
}); | ||
|
||
describe('deleteValue', () => { | ||
it('should delete the value from forked memory', () => { | ||
const path = 'Conversation.conversationId'; | ||
mockMemory.setValue(path, '123'); | ||
mockMemory.deleteValue(path); | ||
assert.equal(mockMemory.hasValue(path), false); | ||
}); | ||
|
||
it('should delete the defaulted temp scope and name value from forked memory', () => { | ||
const path = 'conversationId'; | ||
mockMemory.setValue(path, '123'); | ||
mockMemory.deleteValue(path); | ||
assert.equal(mockMemory.hasValue(path), false); | ||
}); | ||
|
||
it('should delete and check a non-existing value from forked memory, where scope does not exist', () => { | ||
const pathTwo = 'User.conversationId'; | ||
mockMemory.deleteValue(pathTwo); | ||
assert.equal(mockMemory.hasValue(pathTwo), false); | ||
}); | ||
|
||
it('should delete and check a non-existing value from forked memory, where name does not exist', () => { | ||
const pathOne = 'Conversation.conversationId'; | ||
mockMemory.setValue(pathOne, '123'); | ||
const pathTwo = 'Conversation.tokenId'; | ||
mockMemory.deleteValue(pathTwo); | ||
assert.equal(mockMemory.hasValue(pathTwo), false); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('TestMemoryFork', () => { | ||
let testMemoryFork: TestMemoryFork; | ||
|
||
beforeEach(() => { | ||
testMemoryFork = new TestMemoryFork(); | ||
testMemoryFork.setValue('User.userId', '123'); | ||
}); | ||
|
||
it('should throw invalid state path error due to many substrings', () => { | ||
const path = 'random.random.conversationId'; | ||
assert.throws(() => testMemoryFork.deleteValue(path), new Error(`Invalid state path: ${path}`)); | ||
}); | ||
|
||
it('should assign a new value where scope already exists', () => { | ||
const path = 'User.conversationId'; | ||
testMemoryFork.setValue(path, '987'); | ||
assert.equal(testMemoryFork.hasValue(path), true); | ||
}); | ||
|
||
it('should retrieve an existing value', () => { | ||
const path = 'User.userId'; | ||
assert.equal(testMemoryFork.getValue(path), '123'); | ||
}); | ||
|
||
it('should return null for non-existing value', () => { | ||
const path = 'User.tokenId'; | ||
assert.equal(testMemoryFork.getValue(path), null); | ||
}); | ||
|
||
it('should return true for checking an existing value', () => { | ||
const path = 'User.userId'; | ||
assert.equal(testMemoryFork.hasValue(path), true); | ||
}); | ||
|
||
it('should return false for checking a non-existent value, where name does not exist', () => { | ||
const path = 'User.tokenId'; | ||
assert.equal(testMemoryFork.hasValue(path), false); | ||
}); | ||
|
||
it('should check and delete an existing value', () => { | ||
const path = 'User.userId'; | ||
testMemoryFork.deleteValue(path); | ||
assert.equal(testMemoryFork.hasValue(path), false); | ||
}); | ||
|
||
it('should check and delete a non-existing value', () => { | ||
const path = 'Temp.userId'; | ||
testMemoryFork.deleteValue(path); | ||
assert.equal(testMemoryFork.hasValue(path), false); | ||
}); | ||
}); |
This file contains 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 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,82 @@ | ||
import { Memory } from './MemoryFork'; | ||
|
||
/** | ||
* @private | ||
*/ | ||
const TEMP_SCOPE = 'temp'; | ||
|
||
/** | ||
* A test version of the Memory class used by unit tests. | ||
*/ | ||
export class TestMemoryFork implements Memory { | ||
private readonly _fork: Record<string, Record<string, unknown>> = {}; | ||
|
||
/** | ||
* Deletes the value from the original memory. | ||
* @param path Path to the value to check in the form of `[scope].property`. If scope is omitted, the value is checked in the temporary scope. | ||
*/ | ||
public deleteValue(path: string): void { | ||
const { scope, name } = this.getScopeAndName(path); | ||
if (this._fork.hasOwnProperty(scope) && this._fork[scope].hasOwnProperty(name)) { | ||
delete this._fork[scope][name]; | ||
} | ||
} | ||
|
||
/** | ||
* Checks if the value exists in the original memory. | ||
* @param path Path to the value to check in the form of `[scope].property`. If scope is omitted, the value is checked in the temporary scope. | ||
*/ | ||
public hasValue(path: string): boolean { | ||
const { scope, name } = this.getScopeAndName(path); | ||
if (this._fork.hasOwnProperty(scope)) { | ||
return this._fork[scope].hasOwnProperty(name); | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* Retrieves the value from the original memory. Otherwise, returns null if value does not exist. | ||
* @param path Path to the value to check in the form of `[scope].property`. If scope is omitted, the value is checked in the temporary scope. | ||
*/ | ||
public getValue<TValue = unknown>(path: string): TValue { | ||
const { scope, name } = this.getScopeAndName(path); | ||
if (this._fork.hasOwnProperty(scope)) { | ||
if (this._fork[scope].hasOwnProperty(name)) { | ||
return this._fork[scope][name] as TValue; | ||
} | ||
} | ||
|
||
return null as TValue; | ||
} | ||
|
||
/** | ||
* Sets the value in the original memory. | ||
* @param path Path to the value to check in the form of `[scope].property`. If scope is omitted, the value is checked in the temporary scope. | ||
* @param value Value to assign. | ||
*/ | ||
public setValue(path: string, value: unknown): void { | ||
const { scope, name } = this.getScopeAndName(path); | ||
if (!this._fork.hasOwnProperty(scope)) { | ||
this._fork[scope] = {}; | ||
} | ||
|
||
this._fork[scope][name] = value; | ||
} | ||
|
||
/** | ||
* @param path Path to the value to check in the form of `[scope].property`. If scope is omitted, the value is checked in the temporary scope. | ||
* @private | ||
*/ | ||
private getScopeAndName(path: string): { scope: string; name: string } { | ||
// Get variable scope and name | ||
const parts = path.split('.'); | ||
if (parts.length > 2) { | ||
throw new Error(`Invalid state path: ${path}`); | ||
} else if (parts.length === 1) { | ||
parts.unshift(TEMP_SCOPE); | ||
} | ||
|
||
return { scope: parts[0], name: parts[1] }; | ||
} | ||
} |