-
Notifications
You must be signed in to change notification settings - Fork 7
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
[i18n-Audio] Add DB schemas and shared API/store stubs #3987
Changes from 5 commits
9087cdf
e8fab0c
e4f9007
e5f6511
8299d88
dff26d8
58cbdd3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,3 +12,27 @@ create table system_settings ( | |
id integer primary key check (id = 1), | ||
data text not null -- JSON blob | ||
); | ||
|
||
create table languages ( | ||
code text primary key | ||
); | ||
|
||
create table ui_strings ( | ||
language_code text primary key, | ||
data text not null, -- JSON blob - see libs/types/UiStringTranslationsSchema | ||
foreign key (language_code) references languages(code) | ||
); | ||
|
||
create table audio_clips ( | ||
id text not null, | ||
language_code text not null, | ||
data_base64 text not null, -- Base64-encoded audio bytes | ||
primary key (language_code, id), | ||
foreign key (language_code) references languages(code) | ||
); | ||
|
||
create table ui_string_audio_keys ( | ||
language_code text primary key, | ||
data text not null, -- JSON blob - see libs/types/UiStringTranslationsSchema | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we want to reference There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, thanks for the catch! |
||
foreign key (language_code) references languages(code) | ||
); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,12 +18,17 @@ import { | |
singlePrecinctSelectionFor, | ||
} from '@votingworks/utils'; | ||
import { Buffer } from 'buffer'; | ||
import { createBallotPackageZipArchive, MockUsb } from '@votingworks/backend'; | ||
import { | ||
createBallotPackageZipArchive, | ||
createUiStringsApiMock, | ||
MockUsb, | ||
} from '@votingworks/backend'; | ||
import { Server } from 'http'; | ||
import * as grout from '@votingworks/grout'; | ||
import { | ||
DEFAULT_SYSTEM_SETTINGS, | ||
ElectionDefinition, | ||
LanguageCode, | ||
SinglePrecinctSelection, | ||
safeParseSystemSettings, | ||
} from '@votingworks/types'; | ||
|
@@ -40,6 +45,14 @@ jest.mock('@votingworks/utils', (): typeof import('@votingworks/utils') => { | |
}; | ||
}); | ||
|
||
const mockUiStringsApi = createUiStringsApiMock(); | ||
jest.mock('@votingworks/backend', (): typeof import('@votingworks/backend') => { | ||
return { | ||
...jest.requireActual('@votingworks/backend'), | ||
createUiStringsApi: () => mockUiStringsApi, | ||
}; | ||
}); | ||
|
||
let apiClient: grout.Client<Api>; | ||
let mockAuth: InsertedSmartCardAuthApi; | ||
let mockUsb: MockUsb; | ||
|
@@ -62,6 +75,7 @@ beforeEach(async () => { | |
afterEach(() => { | ||
stateMachine.stopMachineService(); | ||
server?.close(); | ||
jest.resetAllMocks(); | ||
}); | ||
|
||
async function setUpUsbAndConfigureElection( | ||
|
@@ -240,3 +254,40 @@ test('configureWithSampleBallotPackageForIntegrationTest configures electionGene | |
const electionDefinitionResult = await apiClient.getElectionDefinition(); | ||
expect(electionDefinitionResult).toEqual(electionGeneralDefinition); | ||
}); | ||
|
||
test('installs UI Strings API', async () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm taking a sec to wrap my mind around the difference between these tests and the tests in It seems like the main thing tested in this file that's not tested in I wonder if we can just assume that the Grout client will work fine, given I might be missing something! Just wondering if we can get rid of these There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, thanks - I lost perspective on this after a couple iterations. Initially had the shared test runner just taking in a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 Figured that might've been the case re iterations! |
||
mockOf(mockUiStringsApi.getAvailableLanguages).mockImplementation(() => [ | ||
LanguageCode.SPANISH, | ||
]); | ||
await expect(apiClient.getAvailableLanguages()).resolves.toEqual([ | ||
LanguageCode.SPANISH, | ||
]); | ||
|
||
mockOf(mockUiStringsApi.getUiStrings).mockImplementation(() => ({ | ||
foo: 'bar', | ||
})); | ||
await expect( | ||
apiClient.getUiStrings({ languageCode: LanguageCode.SPANISH }) | ||
).resolves.toEqual({ | ||
foo: 'bar', | ||
}); | ||
|
||
mockOf(mockUiStringsApi.getUiStringAudioKeys).mockImplementation(() => ({ | ||
foo: ['123', '456'], | ||
})); | ||
await expect( | ||
apiClient.getUiStringAudioKeys({ languageCode: LanguageCode.SPANISH }) | ||
).resolves.toEqual({ | ||
foo: ['123', '456'], | ||
}); | ||
|
||
mockOf(mockUiStringsApi.getAudioClipsBase64).mockImplementation(() => ({ | ||
abc: 'data-abc', | ||
})); | ||
await expect( | ||
apiClient.getAudioClipsBase64({ | ||
languageCode: LanguageCode.ENGLISH, | ||
audioKeys: ['abc'], | ||
}) | ||
).resolves.toEqual({ abc: 'data-abc' }); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import tmp from 'tmp'; | ||
|
||
import { createMockUsb, runUiStringApiTests } from '@votingworks/backend'; | ||
import { fakeLogger } from '@votingworks/logging'; | ||
|
||
import { buildMockInsertedSmartCardAuth } from '@votingworks/auth'; | ||
import { Store } from './store'; | ||
import { createWorkspace } from './util/workspace'; | ||
import { buildApi } from './app'; | ||
|
||
const store = Store.memoryStore(); | ||
const workspace = createWorkspace(tmp.dirSync().name, { store }); | ||
|
||
afterAll(() => { | ||
workspace.reset(); | ||
}); | ||
|
||
runUiStringApiTests({ | ||
api: buildApi( | ||
buildMockInsertedSmartCardAuth(), | ||
createMockUsb().mock, | ||
fakeLogger(), | ||
workspace | ||
), | ||
store: store.getUiStringsStore(), | ||
}); |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -2,6 +2,7 @@ | |||||||||||||||||||||||||||||||||
// The durable datastore for configuration info. | ||||||||||||||||||||||||||||||||||
// | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
import { UiStringsStore, createUiStringStore } from '@votingworks/backend'; | ||||||||||||||||||||||||||||||||||
import { Optional } from '@votingworks/basics'; | ||||||||||||||||||||||||||||||||||
import { Client as DbClient } from '@votingworks/db'; | ||||||||||||||||||||||||||||||||||
import { | ||||||||||||||||||||||||||||||||||
|
@@ -21,7 +22,10 @@ const SchemaPath = join(__dirname, '../schema.sql'); | |||||||||||||||||||||||||||||||||
* Manages a data store for imported election definition and system settings | ||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||
export class Store { | ||||||||||||||||||||||||||||||||||
private constructor(private readonly client: DbClient) {} | ||||||||||||||||||||||||||||||||||
private constructor( | ||||||||||||||||||||||||||||||||||
private readonly client: DbClient, | ||||||||||||||||||||||||||||||||||
private readonly uiStringsStore: UiStringsStore | ||||||||||||||||||||||||||||||||||
) {} | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
getDbPath(): string { | ||||||||||||||||||||||||||||||||||
return this.client.getDatabasePath(); | ||||||||||||||||||||||||||||||||||
|
@@ -31,14 +35,18 @@ export class Store { | |||||||||||||||||||||||||||||||||
* Builds and returns a new store whose data is kept in memory. | ||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||
static memoryStore(): Store { | ||||||||||||||||||||||||||||||||||
return new Store(DbClient.memoryClient(SchemaPath)); | ||||||||||||||||||||||||||||||||||
const client = DbClient.memoryClient(SchemaPath); | ||||||||||||||||||||||||||||||||||
const uiStringsStore = createUiStringStore(client); | ||||||||||||||||||||||||||||||||||
return new Store(client, uiStringsStore); | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||
* Builds and returns a new store at `dbPath`. | ||||||||||||||||||||||||||||||||||
*/ | ||||||||||||||||||||||||||||||||||
static fileStore(dbPath: string): Store { | ||||||||||||||||||||||||||||||||||
return new Store(DbClient.fileClient(dbPath, SchemaPath)); | ||||||||||||||||||||||||||||||||||
const client = DbClient.fileClient(dbPath, SchemaPath); | ||||||||||||||||||||||||||||||||||
const uiStringsStore = createUiStringStore(client); | ||||||||||||||||||||||||||||||||||
return new Store(client, uiStringsStore); | ||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice! I like these patterns that you're setting for shared store and API code. I'd been with experimenting with similar ideas recently, e.g. vxsuite/apps/scan/backend/src/store.ts Lines 36 to 40 in 6c454a5
vxsuite/apps/scan/backend/src/store.ts Lines 958 to 968 in 6c454a5
I like that what you're setting up is more automatic than what I'd set up. Looks like we can add new methods to Re SQL schemas, I too just copied the schemas since we don't yet have a clear pattern for sharing those - that seems fine to me. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice! I missed those - makes me feel better knowing that we have other instances where we're sharing the query code |
||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
/** | ||||||||||||||||||||||||||||||||||
|
@@ -184,4 +192,8 @@ export class Store { | |||||||||||||||||||||||||||||||||
if (!result) return undefined; | ||||||||||||||||||||||||||||||||||
return safeParseSystemSettings(result.data).unsafeUnwrap(); | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
getUiStringsStore(): UiStringsStore { | ||||||||||||||||||||||||||||||||||
return this.uiStringsStore; | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
} |
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.
Double checking my understanding: These
id
values are the same as the audio keys as described in the spec, e.g.d5b21f
, correct?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.
Yeah, these correspond the audio keys - and I'm realising the inconsistent naming is confusing (especially once I started building on this PR 😅 ). I think I'm going to settle on
id
and change the property names in related schemas toid
/audioId
to make things more consistentThere 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.
Nice, I like that!