Skip to content

Commit 502d616

Browse files
authored
feat: configurable ledgers and cleanup (issue #2607) (#1614)
Signed-off-by: Mac Deluca <[email protected]>
1 parent c2a4587 commit 502d616

File tree

7 files changed

+496
-65
lines changed

7 files changed

+496
-65
lines changed

.changeset/nine-dryers-pull.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@bifold/core': patch
3+
---
4+
5+
Refactored ledgers API to be configurable

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
"lint": "eslint --color .",
2323
"typecheck": "yarn workspaces foreach --all --topological-dev -p run typecheck",
2424
"release": "yarn build && yarn changeset publish --no-git-tag",
25-
"changeset-version": "changeset version && yarn install --no-immutable"
25+
"changeset-version": "changeset version && yarn install --no-immutable",
26+
"ledgers": "ts-node ./scripts/refresh-ledgers.ts"
2627
},
2728
"devDependencies": {
2829
"@changesets/cli": "~2.29.5",
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
import fs from 'fs'
2+
import path from 'path'
3+
import axios from 'axios'
4+
import {
5+
getIndyLedgers,
6+
IndyLedger,
7+
IndyLedgerFileSystem,
8+
readIndyLedgersFromFile,
9+
writeIndyLedgersToFile,
10+
} from '../../src/utils/ledger'
11+
12+
const INDY_LEDGER_RECORD = {
13+
smn: {
14+
name: 'Sovrin Main Net',
15+
indyNamespace: 'sovrin',
16+
genesisUrl: 'SovrinURL',
17+
},
18+
vn: {
19+
name: 'Local von-network',
20+
indyNamespace: 'local:dev-docker',
21+
genesisUrl: 'LocalVonNetworkURL',
22+
},
23+
'vn-linux': {
24+
name: 'Linux Local von-network',
25+
indyNamespace: 'local:dev-linux',
26+
genesisUrl: 'LinuxLocalVonNetworkURL',
27+
},
28+
}
29+
30+
const fileSystem: IndyLedgerFileSystem = {
31+
writeFile: (filePath: string, data: string) => fs.writeFileSync(filePath, data, 'utf8'),
32+
readFile: (filePath: string) => fs.readFileSync(filePath, 'utf8'),
33+
fileExists: (filePath: string) => fs.existsSync(filePath),
34+
pathResolve: (filePath: string) => path.resolve(filePath),
35+
}
36+
37+
jest.mock('axios')
38+
39+
describe('ledger utils', () => {
40+
beforeEach(() => {
41+
jest.clearAllMocks()
42+
})
43+
44+
describe('getIndyLedgers', () => {
45+
it('should return an array of indy ledgers', async () => {
46+
const getMock = axios.get as jest.Mock
47+
48+
getMock.mockResolvedValueOnce({ data: INDY_LEDGER_RECORD })
49+
getMock.mockResolvedValueOnce({ data: 'genesisDataA ' }) // intentional whitespace for trimming test
50+
getMock.mockResolvedValueOnce({ data: 'genesisDataB' })
51+
52+
const ledgers = await getIndyLedgers([
53+
{
54+
ledgerId: IndyLedger.SOVERIN_MAIN_NET,
55+
isProduction: false,
56+
},
57+
{
58+
ledgerId: IndyLedger.LOCAL_VON_NETWORK,
59+
doNotConnectOnStartup: true,
60+
isProduction: true,
61+
},
62+
])
63+
64+
expect(getMock).toHaveBeenCalledTimes(3)
65+
66+
expect(ledgers).toHaveLength(2)
67+
68+
expect(ledgers[0]).toStrictEqual({
69+
id: 'SovrinMainNet',
70+
connectOnStartup: true,
71+
isProduction: false,
72+
indyNamespace: 'sovrin',
73+
genesisTransactions: 'genesisDataA',
74+
})
75+
76+
expect(ledgers[1]).toStrictEqual({
77+
id: 'Local',
78+
connectOnStartup: false,
79+
isProduction: true,
80+
indyNamespace: 'local:dev-docker',
81+
genesisTransactions: 'genesisDataB',
82+
})
83+
})
84+
85+
it('should return an empty array if no ledgers are provided', async () => {
86+
const ledgers = await getIndyLedgers([])
87+
expect(ledgers).toHaveLength(0)
88+
})
89+
90+
it('should throw an error if a ledger is not found', async () => {
91+
const getMock = axios.get as jest.Mock
92+
93+
getMock.mockResolvedValueOnce({ data: INDY_LEDGER_RECORD })
94+
95+
await expect(
96+
getIndyLedgers([
97+
{
98+
ledgerId: 'non-existent-ledger' as IndyLedger,
99+
isProduction: false,
100+
},
101+
])
102+
).rejects.toThrow('Ledger config for non-existent-ledger not found')
103+
104+
expect(getMock).toHaveBeenCalledTimes(1)
105+
})
106+
})
107+
108+
describe('writeIndyLedgersToFile', () => {
109+
it('should write ledgers to a JSON file', () => {
110+
const filePath = './test-ledgers.json'
111+
112+
writeIndyLedgersToFile(fileSystem, filePath, [
113+
{
114+
id: 'SovrinMainNet',
115+
isProduction: false,
116+
indyNamespace: 'sovrin',
117+
genesisTransactions: 'A',
118+
},
119+
])
120+
121+
const writtenContent = fs.readFileSync(filePath, 'utf8')
122+
expect(JSON.parse(writtenContent)).toStrictEqual([
123+
{
124+
id: 'SovrinMainNet',
125+
isProduction: false,
126+
indyNamespace: 'sovrin',
127+
genesisTransactions: 'A',
128+
},
129+
])
130+
131+
// Clean up
132+
fs.unlinkSync(filePath)
133+
134+
expect(fs.existsSync(filePath)).toBe(false)
135+
})
136+
137+
it('should throw an error if the file path is invalid', () => {
138+
expect(() => writeIndyLedgersToFile(fileSystem, './invalid-path/ledgers.txt', [])).toThrow(
139+
'File path must point to a JSON file'
140+
)
141+
})
142+
})
143+
144+
describe('readIndyLedgersFromFile', () => {
145+
it('should read ledgers from a JSON file', () => {
146+
const filePath = './test-ledgers.json'
147+
fs.writeFileSync(
148+
filePath,
149+
JSON.stringify([
150+
{
151+
id: 'SovrinMainNet',
152+
isProduction: false,
153+
indyNamespace: 'sovrin',
154+
genesisTransactions: 'A',
155+
},
156+
]),
157+
'utf8'
158+
)
159+
160+
const readLedgers = readIndyLedgersFromFile(fileSystem, filePath)
161+
expect(readLedgers).toStrictEqual([
162+
{
163+
id: 'SovrinMainNet',
164+
isProduction: false,
165+
indyNamespace: 'sovrin',
166+
genesisTransactions: 'A',
167+
},
168+
])
169+
170+
// Clean up
171+
fs.unlinkSync(filePath)
172+
173+
expect(fs.existsSync(filePath)).toBe(false)
174+
})
175+
176+
it('should skip writing to file if the new ledgers are the same', () => {
177+
const filePath = './test-ledgers.json'
178+
const ledgers = [
179+
{
180+
id: 'SovrinMainNet',
181+
isProduction: false,
182+
indyNamespace: 'sovrin',
183+
genesisTransactions: 'A',
184+
},
185+
]
186+
187+
// Write initial ledgers
188+
writeIndyLedgersToFile(fileSystem, filePath, ledgers)
189+
190+
// Read from file
191+
const readLedgers = readIndyLedgersFromFile(fileSystem, filePath)
192+
expect(readLedgers).toStrictEqual(ledgers)
193+
194+
const initialStats = fs.statSync(filePath)
195+
196+
// Attempt to write the same ledgers again
197+
writeIndyLedgersToFile(fileSystem, filePath, ledgers)
198+
199+
const finalStats = fs.statSync(filePath)
200+
201+
// Check that the modified time did not change
202+
expect(finalStats.mtime).toEqual(initialStats.mtime)
203+
204+
// Check that the file was not modified
205+
const writtenContent = fs.readFileSync(filePath, 'utf8')
206+
expect(JSON.parse(writtenContent)).toStrictEqual(ledgers)
207+
208+
// Clean up
209+
fs.unlinkSync(filePath)
210+
211+
expect(fs.existsSync(filePath)).toBe(false)
212+
})
213+
214+
it('should throw an error if the file path is invalid', () => {
215+
expect(() => readIndyLedgersFromFile(fileSystem, './invalid-path/ledgers.txt')).toThrow(
216+
'File path must point to a JSON file'
217+
)
218+
})
219+
220+
it('should throw is malformed JSON is read', () => {
221+
const filePath = './test-ledgers.json'
222+
fs.writeFileSync(filePath, 'This is not JSON', 'utf8')
223+
224+
try {
225+
readIndyLedgersFromFile(fileSystem, filePath)
226+
expect(true).toBe(false)
227+
} catch (error) {
228+
expect((error as Error).message).toContain('Failed to read ledgers from file')
229+
}
230+
231+
// Clean up
232+
fs.unlinkSync(filePath)
233+
234+
expect(fs.existsSync(filePath)).toBe(false)
235+
})
236+
})
237+
})

packages/core/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,9 @@ export {
103103
} from './utils/helpers'
104104
export { isValidAnonCredsCredential, getCredentialIdentifiers } from './utils/credential'
105105
export { buildFieldsFromAnonCredsCredential } from './utils/oca'
106+
export { IndyLedger, getIndyLedgers, readIndyLedgersFromFile, writeIndyLedgersToFile } from './utils/ledger'
106107

108+
export type { IndyLedgerConfig, IndyLedgerJSON, IndyLedgersRecord, IndyLedgerFileSystem } from './utils/ledger'
107109
export type { AnimatedComponents } from './animated-components'
108110
export type {
109111
ISVGAssets,

0 commit comments

Comments
 (0)