-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Twenty config core implementation #11595
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
Merged
Merged
Changes from 72 commits
Commits
Show all changes
99 commits
Select commit
Hold shift + click to select a range
e556261
change system_var to config_variable and user_var to user_variable an…
ehconitin 3ad7cb2
add IS_CONFIG_VAR_IN_DB_ENABLED
ehconitin 1f0a9a6
Merge remote-tracking branch 'upstream/main' into config-prep-2
ehconitin f61b830
lint
ehconitin b671d96
Merge remote-tracking branch 'upstream/main' into config-prep-2
ehconitin e58ef31
rename service spec
ehconitin 31ed7e7
Copy over from POC
ehconitin 46ba298
continue
ehconitin d5c6038
twenty config module is global?
ehconitin eeb7448
wip: const renaming
ehconitin 07744af
hasty commit, needs rework :)
ehconitin 448cf9a
Merge remote-tracking branch 'upstream/main' into twenty-config-core-…
ehconitin 2c9748f
Merge remote-tracking branch 'upstream/main' into twenty-config-core-…
ehconitin 7c072cb
refactor
ehconitin 8a1bee7
Merge remote-tracking branch 'upstream/main' into twenty-config-core-…
ehconitin 3a4c317
grep review
ehconitin 1ed5e8f
Merge remote-tracking branch 'upstream/main' into twenty-config-core-…
ehconitin 1eece63
dont need this overenginnered code, max entries would always be same …
ehconitin be2bf64
WIP: tests - add cache test
ehconitin 071b147
WIP: tests -- add tests for drivers
ehconitin f0fe2d9
WIP: tests -- add test for storage service
ehconitin 5b917f5
Merge remote-tracking branch 'upstream/main' into twenty-config-core-…
ehconitin c52cecf
fix test
ehconitin 3f9f84d
Merge remote-tracking branch 'upstream/main' into twenty-config-core-…
ehconitin da53140
Merge remote-tracking branch 'upstream/main' into config-prep-2
ehconitin 5f1f1aa
Merge remote-tracking branch 'upstream/main' into twenty-config-core-…
ehconitin 6646b76
Merge remote-tracking branch 'upstream/config-prep-2' into twenty-con…
ehconitin 0f8012b
WIP: tests -- fix config cache test
ehconitin 337ff08
WIP: tests -- add more test cases in db config driver
ehconitin b0c8021
WIP: tests -- fix incorrect env driver test
ehconitin 0c79d81
WIP: tests -- fix test
ehconitin 4f96a72
change IS_CONFIG_VAR_IN_DB_ENABLED to IS_CONFIG_VARIABLES_IN_DB_ENABLED
ehconitin df938fa
lint
ehconitin af7fe90
Merge remote-tracking branch 'upstream/config-prep-2' into twenty-con…
ehconitin e0bd022
lint
ehconitin 2462f0e
WIP: tests -- scavenging test cases
ehconitin b259e41
fix
ehconitin 1b9d524
WIP: continue
ehconitin 62c93a6
fix
ehconitin 9b58af4
materialize config source into a enum
ehconitin 44b1a00
small improvements
ehconitin fac8f30
rename
ehconitin c3b61e9
fix
ehconitin 2182804
cleaning
ehconitin cd5fc92
Merge branch 'main' into config-prep-2
ehconitin 2a11b8d
Merge remote-tracking branch 'upstream/config-prep-2' into twenty-con…
ehconitin ee2131b
fix initialization bug
ehconitin a409d21
add service test
ehconitin 51fdd5c
grep review
ehconitin 0ca0bf1
rename
ehconitin 51811b6
Merge remote-tracking branch 'upstream/main' into twenty-config-core-…
ehconitin b29a0f8
Merge branch 'main' into twenty-config-core-implementation
charlesBochet 427fbcb
Merge remote-tracking branch 'upstream/main' into twenty-config-core-…
ehconitin 49b532c
rethink conversion
ehconitin 3aadb2e
Merge remote-tracking branch 'upstream/main' into twenty-config-core-…
ehconitin ebc03b1
improvements
ehconitin 9650281
automate simple validation and transformers
ehconitin 02e5ca1
Merge remote-tracking branch 'upstream/main' into twenty-config-core-…
ehconitin afa4f73
WIP: use p retry instead of custom retry mechanism
ehconitin 1504a20
fix tests
ehconitin 8ac0b15
hasty commit
ehconitin 35535a8
remove p retry
ehconitin c1cf699
remove lib
ehconitin 84776c0
Merge remote-tracking branch 'upstream/main' into twenty-config-core-…
ehconitin 5286dee
Merge remote-tracking branch 'upstream/main' into twenty-config-core-…
ehconitin 5ce55be
add @nestjs/schedule package
ehconitin edbe5e2
continue
ehconitin 970fbbf
fix
ehconitin 42d7061
imrove
ehconitin 44373a9
Merge remote-tracking branch 'upstream/main' into twenty-config-core-…
ehconitin 2ffce8e
rename timestamp to registered at
ehconitin f0a06ba
lint
ehconitin ba2a645
add debounce mechanism to prevent duplicate config refreshes
ehconitin 2d2de86
lint
ehconitin f58a3ae
Merge remote-tracking branch 'upstream/main' into twenty-config-core-…
ehconitin c62f677
simplify
ehconitin 61aae91
lint
ehconitin 50c5565
Merge remote-tracking branch 'upstream/main' into twenty-config-core-…
ehconitin 0393cfc
refactor
ehconitin c8fdaca
converters improvements
ehconitin 2af3020
add safe guards on conversion
ehconitin 9f267e7
add tests and more improvements
ehconitin 883f50a
lint
ehconitin 8fef8d1
Merge remote-tracking branch 'upstream/main' into twenty-config-core-…
ehconitin 04d8b68
Merge branch 'main' into twenty-config-core-implementation
FelixMalfait 275918f
Merge remote-tracking branch 'upstream/main' into twenty-config-core-…
ehconitin a273216
Refactor config system: Eliminate custom state management for NestJS …
ehconitin df9a755
Merge remote-tracking branch 'upstream/main' into twenty-config-core-…
ehconitin 496b4f3
rename lingo
ehconitin 79c8e3a
continue
ehconitin 44660cb
grep
ehconitin a887bf8
lint
ehconitin 7ff696a
last one I promise
ehconitin af26e74
Fix one tesdt
FelixMalfait f3c1690
fix
ehconitin f1c112b
Merge remote-tracking branch 'upstream/main' into twenty-config-core-…
ehconitin e2344f0
fix test
ehconitin 649a68e
lint
ehconitin b37d278
fix test
ehconitin 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
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
287 changes: 287 additions & 0 deletions
287
...server/src/engine/core-modules/twenty-config/cache/__tests__/config-cache.service.spec.ts
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,287 @@ | ||
import { ScheduleModule } from '@nestjs/schedule'; | ||
import { Test, TestingModule } from '@nestjs/testing'; | ||
|
||
import { ConfigCacheService } from 'src/engine/core-modules/twenty-config/cache/config-cache.service'; | ||
import { ConfigVariables } from 'src/engine/core-modules/twenty-config/config-variables'; | ||
import { CONFIG_VARIABLES_CACHE_TTL } from 'src/engine/core-modules/twenty-config/constants/config-variables-cache-ttl'; | ||
|
||
describe('ConfigCacheService', () => { | ||
let service: ConfigCacheService; | ||
|
||
const withMockedDate = (timeOffset: number, callback: () => void) => { | ||
const originalNow = Date.now; | ||
|
||
try { | ||
Date.now = jest.fn(() => originalNow() + timeOffset); | ||
callback(); | ||
} finally { | ||
Date.now = originalNow; | ||
} | ||
}; | ||
|
||
beforeEach(async () => { | ||
jest.useFakeTimers(); | ||
ehconitin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
const module: TestingModule = await Test.createTestingModule({ | ||
imports: [ScheduleModule.forRoot()], | ||
providers: [ConfigCacheService], | ||
}).compile(); | ||
|
||
service = module.get<ConfigCacheService>(ConfigCacheService); | ||
}); | ||
|
||
afterEach(() => { | ||
service.onModuleDestroy(); | ||
jest.useRealTimers(); | ||
}); | ||
|
||
it('should be defined', () => { | ||
expect(service).toBeDefined(); | ||
}); | ||
|
||
describe('get and set', () => { | ||
it('should set and get a value from cache', () => { | ||
const key = 'AUTH_PASSWORD_ENABLED' as keyof ConfigVariables; | ||
const value = true; | ||
|
||
service.set(key, value); | ||
const result = service.get(key); | ||
|
||
expect(result.value).toBe(value); | ||
expect(result.isStale).toBe(false); | ||
}); | ||
|
||
it('should return undefined for non-existent key', () => { | ||
const result = service.get( | ||
'AUTH_PASSWORD_ENABLED' as keyof ConfigVariables, | ||
); | ||
|
||
expect(result.value).toBeUndefined(); | ||
expect(result.isStale).toBe(false); | ||
}); | ||
|
||
it('should handle different value types', () => { | ||
const booleanKey = 'AUTH_PASSWORD_ENABLED' as keyof ConfigVariables; | ||
const stringKey = 'EMAIL_FROM_ADDRESS' as keyof ConfigVariables; | ||
const numberKey = 'NODE_PORT' as keyof ConfigVariables; | ||
|
||
service.set(booleanKey, true); | ||
service.set(stringKey, 'test@example.com'); | ||
service.set(numberKey, 3000); | ||
|
||
expect(service.get(booleanKey).value).toBe(true); | ||
expect(service.get(stringKey).value).toBe('test@example.com'); | ||
expect(service.get(numberKey).value).toBe(3000); | ||
}); | ||
}); | ||
|
||
describe('negative lookup cache', () => { | ||
it('should check if a negative cache entry exists', () => { | ||
const key = 'TEST_KEY' as keyof ConfigVariables; | ||
|
||
service.markKeyAsMissing(key); | ||
const result = service.isKeyKnownMissing(key); | ||
|
||
expect(result).toBe(true); | ||
}); | ||
|
||
it('should return false for negative cache entry check when not in cache', () => { | ||
const key = 'NON_EXISTENT_KEY' as keyof ConfigVariables; | ||
|
||
const result = service.isKeyKnownMissing(key); | ||
|
||
expect(result).toBe(false); | ||
}); | ||
|
||
it('should return false for negative cache entry check when expired', () => { | ||
const key = 'TEST_KEY' as keyof ConfigVariables; | ||
|
||
service.markKeyAsMissing(key); | ||
|
||
// Mock a date beyond the TTL | ||
jest.spyOn(Date, 'now').mockReturnValueOnce(Date.now() + 1000000); | ||
ehconitin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
expect(service.isKeyKnownMissing(key)).toBe(false); | ||
}); | ||
}); | ||
|
||
describe('clear operations', () => { | ||
it('should clear specific key', () => { | ||
const key1 = 'AUTH_PASSWORD_ENABLED' as keyof ConfigVariables; | ||
const key2 = 'EMAIL_FROM_ADDRESS' as keyof ConfigVariables; | ||
|
||
service.set(key1, true); | ||
service.set(key2, 'test@example.com'); | ||
service.clear(key1); | ||
|
||
expect(service.get(key1).value).toBeUndefined(); | ||
expect(service.get(key2).value).toBe('test@example.com'); | ||
}); | ||
|
||
it('should clear all entries', () => { | ||
const key1 = 'AUTH_PASSWORD_ENABLED' as keyof ConfigVariables; | ||
const key2 = 'EMAIL_FROM_ADDRESS' as keyof ConfigVariables; | ||
|
||
service.set(key1, true); | ||
service.set(key2, 'test@example.com'); | ||
service.clearAll(); | ||
|
||
expect(service.get(key1).value).toBeUndefined(); | ||
expect(service.get(key2).value).toBeUndefined(); | ||
}); | ||
}); | ||
|
||
describe('cache expiration', () => { | ||
it('should mark entries as stale after TTL', () => { | ||
const key = 'AUTH_PASSWORD_ENABLED' as keyof ConfigVariables; | ||
const value = true; | ||
|
||
service.set(key, value); | ||
|
||
withMockedDate(CONFIG_VARIABLES_CACHE_TTL + 1, () => { | ||
const result = service.get(key); | ||
|
||
expect(result.value).toBe(value); | ||
expect(result.isStale).toBe(true); | ||
}); | ||
}); | ||
|
||
it('should not mark entries as stale before TTL', () => { | ||
const key = 'AUTH_PASSWORD_ENABLED' as keyof ConfigVariables; | ||
const value = true; | ||
|
||
service.set(key, value); | ||
|
||
withMockedDate(CONFIG_VARIABLES_CACHE_TTL - 1, () => { | ||
const result = service.get(key); | ||
|
||
expect(result.value).toBe(value); | ||
expect(result.isStale).toBe(false); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('getCacheInfo', () => { | ||
it('should return correct cache information', () => { | ||
const key1 = 'AUTH_PASSWORD_ENABLED' as keyof ConfigVariables; | ||
const key2 = 'EMAIL_FROM_ADDRESS' as keyof ConfigVariables; | ||
const key3 = 'NODE_PORT' as keyof ConfigVariables; | ||
|
||
service.set(key1, true); | ||
service.set(key2, 'test@example.com'); | ||
service.markKeyAsMissing(key3); | ||
|
||
const info = service.getCacheInfo(); | ||
|
||
expect(info.positiveEntries).toBe(2); | ||
expect(info.negativeEntries).toBe(1); | ||
expect(info.cacheKeys).toContain(key1); | ||
expect(info.cacheKeys).toContain(key2); | ||
expect(info.cacheKeys).not.toContain(key3); | ||
expect(service.isKeyKnownMissing(key3)).toBe(true); | ||
}); | ||
|
||
it('should not include expired entries in cache info', () => { | ||
const key = 'AUTH_PASSWORD_ENABLED' as keyof ConfigVariables; | ||
|
||
service.set(key, true); | ||
|
||
withMockedDate(CONFIG_VARIABLES_CACHE_TTL + 1, () => { | ||
const info = service.getCacheInfo(); | ||
|
||
expect(info.positiveEntries).toBe(0); | ||
expect(info.negativeEntries).toBe(0); | ||
expect(info.cacheKeys).toHaveLength(0); | ||
}); | ||
}); | ||
|
||
it('should properly count cache entries', () => { | ||
const key1 = 'KEY1' as keyof ConfigVariables; | ||
const key2 = 'KEY2' as keyof ConfigVariables; | ||
const key3 = 'KEY3' as keyof ConfigVariables; | ||
|
||
// Add some values to the cache | ||
service.set(key1, 'value1'); | ||
service.set(key2, 'value2'); | ||
service.markKeyAsMissing(key3); | ||
|
||
const cacheInfo = service.getCacheInfo(); | ||
|
||
expect(cacheInfo.positiveEntries).toBe(2); | ||
expect(cacheInfo.negativeEntries).toBe(1); | ||
expect(cacheInfo.cacheKeys).toContain(key1); | ||
expect(cacheInfo.cacheKeys).toContain(key2); | ||
expect(service.isKeyKnownMissing(key3)).toBe(true); | ||
}); | ||
}); | ||
|
||
describe('module lifecycle', () => { | ||
it('should clear cache on module destroy', () => { | ||
const key = 'TEST_KEY' as keyof ConfigVariables; | ||
|
||
service.set(key, 'test'); | ||
|
||
service.onModuleDestroy(); | ||
|
||
expect(service.get(key).value).toBeUndefined(); | ||
}); | ||
}); | ||
|
||
describe('getExpiredKeys', () => { | ||
it('should return expired keys from both positive and negative caches', () => { | ||
const expiredKey1 = 'EXPIRED_KEY1' as keyof ConfigVariables; | ||
const expiredKey2 = 'EXPIRED_KEY2' as keyof ConfigVariables; | ||
const expiredNegativeKey = | ||
'EXPIRED_NEGATIVE_KEY' as keyof ConfigVariables; | ||
|
||
// Set up keys that will expire | ||
service.set(expiredKey1, 'value1'); | ||
service.set(expiredKey2, 'value2'); | ||
service.markKeyAsMissing(expiredNegativeKey); | ||
|
||
// Make the above keys expire | ||
withMockedDate(CONFIG_VARIABLES_CACHE_TTL + 1, () => { | ||
// Add a fresh key after the time change | ||
const freshKey = 'FRESH_KEY' as keyof ConfigVariables; | ||
|
||
service.set(freshKey, 'value3'); | ||
|
||
const expiredKeys = service.getExpiredKeys(); | ||
|
||
expect(expiredKeys).toContain(expiredKey1); | ||
expect(expiredKeys).toContain(expiredKey2); | ||
expect(expiredKeys).toContain(expiredNegativeKey); | ||
expect(expiredKeys).not.toContain(freshKey); | ||
}); | ||
}); | ||
|
||
it('should return empty array when no keys are expired', () => { | ||
const key1 = 'KEY1' as keyof ConfigVariables; | ||
const key2 = 'KEY2' as keyof ConfigVariables; | ||
const negativeKey = 'NEGATIVE_KEY' as keyof ConfigVariables; | ||
|
||
service.set(key1, 'value1'); | ||
service.set(key2, 'value2'); | ||
service.markKeyAsMissing(negativeKey); | ||
|
||
const expiredKeys = service.getExpiredKeys(); | ||
|
||
expect(expiredKeys).toHaveLength(0); | ||
}); | ||
|
||
it('should not have duplicates if a key is in both caches', () => { | ||
const key = 'DUPLICATE_KEY' as keyof ConfigVariables; | ||
|
||
// Manually manipulate the caches to simulate a key in both caches | ||
service.set(key, 'value'); | ||
service.markKeyAsMissing(key); | ||
|
||
withMockedDate(CONFIG_VARIABLES_CACHE_TTL + 1, () => { | ||
const expiredKeys = service.getExpiredKeys(); | ||
|
||
// Should only appear once in the result | ||
expect(expiredKeys.filter((k) => k === key)).toHaveLength(1); | ||
}); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: ANALYTICS_ENABLED is empty but should have a default boolean value (true/false)