Skip to content

Commit f2a76f7

Browse files
authored
CheckSingle endpoint support (#32)
* fix: fix generated code and update client and models * ci: codegen in the pipeline * fix: init submodules in CI * ci: update version in release pipeline * feat: classify single method * test: add tests for classify single * fix: request encoding * fix: check for resize in request
1 parent ad84b9f commit f2a76f7

16 files changed

Lines changed: 219 additions & 1637 deletions

File tree

.github/workflows/functional-tests.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,18 @@ jobs:
2929
- name: Checkout code
3030
uses: actions/checkout@v5
3131

32+
- name: Submodules
33+
run: git submodule update --init --recursive
34+
3235
- name: Setup Node.js
3336
uses: volta-cli/action@v4
3437

3538
- name: Install dependencies
3639
run: npm ci --no-audit
3740

41+
- name: Generate code
42+
run: npm run codegen
43+
3844
- name: Build project
3945
run: npm run build --if-present
4046

.github/workflows/nodejs.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,18 @@ jobs:
1313
- name: Checkout code
1414
uses: actions/checkout@v5
1515

16+
- name: Submodules
17+
run: git submodule update --init --recursive
18+
1619
- name: Setup Node.js
1720
uses: volta-cli/action@v4
1821

1922
- name: Install dependencies
2023
run: npm ci --no-audit
2124

25+
- name: Generate code
26+
run: npm run codegen
27+
2228
- name: Run linting
2329
run: npm run lint --if-present
2430

.github/workflows/release.yml

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
on: workflow_dispatch
1+
on:
2+
release:
3+
types: [published]
24

35
jobs:
46
publish:
@@ -10,11 +12,30 @@ jobs:
1012
- uses: actions/checkout@v5
1113
- uses: actions/setup-node@v5
1214
with:
13-
node-version: "22"
15+
node-version: '22'
16+
17+
- name: Submodules
18+
run: git submodule update --init --recursive
19+
20+
- name: Set version from tag
21+
run: |
22+
if [[ "$GITHUB_REF" == refs/tags/* ]]; then
23+
TAG_VERSION=${GITHUB_REF#refs/tags/}
24+
# Remove 'v' prefix if present
25+
VERSION=${TAG_VERSION#v}
26+
echo "Setting version to: $VERSION"
27+
npm version "$VERSION"
28+
else
29+
echo "Not building from a tag, using default version"
30+
fi
31+
32+
- run: npm run codegen
33+
1434
- run: npm ci
35+
1536
- run: npm run build:release
1637
- uses: JS-DevTools/npm-publish@v3
1738
with:
1839
token: ${{ secrets.GITHUB_TOKEN }}
19-
registry: "https://npm.pkg.github.com"
40+
registry: 'https://npm.pkg.github.com'
2041
access: restricted

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,5 @@ dist/
3131
.env
3232

3333
.venv
34+
35+
src/generated

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
src/generated/

.prettierrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
"parser": "typescript",
33
"semi": true,
44
"singleQuote": true,
5-
"trailingComma": "all"
5+
"trailingComma": "all",
66
}

__tests__/functional/main.functional.test.ts

Lines changed: 104 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
1-
import { describe, it, } from 'vitest';
2-
import { ClassificationOutput, ClassifierSdk, type ClassifyImageInput, ImageFormat } from '../../src';
1+
import { describe, it } from 'vitest';
2+
import {
3+
ClassificationOutput,
4+
ClassifierSdk,
5+
type ClassifyImageInput,
6+
ImageFormat,
7+
} from '../../src';
38
import fs from 'fs';
49
import { randomUUID } from 'crypto';
510

611
describe('ClassifierSdk Functional Tests', () => {
712
describe('listDeployments', () => {
8-
it('should listDeployments and return responses (smoke test)', async ({ expect }) => {
13+
it('should listDeployments and return responses (smoke test)', async ({
14+
expect,
15+
}) => {
916
// This is a smoke test. You must have a running gRPC server at localhost:50051 for this to pass.
1017
// You may want to mock the gRPC client for true unit testing.
1118
const sdk = new ClassifierSdk({
@@ -15,8 +22,8 @@ describe('ClassifierSdk Functional Tests', () => {
1522
issuerUrl: process.env.VITE_OAUTH_ISSUER,
1623
clientId: process.env.VITE_ATHENA_CLIENT_ID,
1724
clientSecret: process.env.VITE_ATHENA_CLIENT_SECRET,
18-
scope: 'manage:classify'
19-
}
25+
scope: 'manage:classify',
26+
},
2027
});
2128

2229
let error: any = null;
@@ -28,11 +35,41 @@ describe('ClassifierSdk Functional Tests', () => {
2835
}
2936
// Assert error is unset
3037
expect(error).toBeNull();
31-
}, 10000)
38+
}, 10000);
39+
});
40+
41+
describe('classifySingle', () => {
42+
it('should classify a single image and return the response', async ({
43+
expect,
44+
}) => {
45+
const imagePath = __dirname + '/448x448.jpg';
46+
const sdk = new ClassifierSdk({
47+
deploymentId: process.env.VITE_ATHENA_DEPLOYMENT_ID,
48+
affiliate: process.env.VITE_ATHENA_AFFILIATE,
49+
authentication: {
50+
issuerUrl: process.env.VITE_OAUTH_ISSUER,
51+
clientId: process.env.VITE_ATHENA_CLIENT_ID,
52+
clientSecret: process.env.VITE_ATHENA_CLIENT_SECRET,
53+
scope: 'manage:classify',
54+
},
55+
});
56+
57+
const input: ClassifyImageInput = {
58+
data: fs.createReadStream(imagePath),
59+
format: ImageFormat.PNG,
60+
};
61+
62+
const response = await sdk.classifySingle(input);
63+
expect(response.classifications).toBe(true);
64+
expect(response.error).toBeNull();
65+
}, 10000);
3266
});
3367

3468
describe('classifyImage', () => {
35-
it('should classify 10 images in a single request and return responses (integration smoke test)', async ({ expect, annotate }) => {
69+
it('should classify 10 images in a single request and return responses (integration smoke test)', async ({
70+
expect,
71+
annotate,
72+
}) => {
3673
// This is a smoke test. You must have a running gRPC server at localhost:50051 for this to pass.
3774
// You may want to mock the gRPC client for true unit testing.
3875
const imagePath = __dirname + '/448x448.jpg';
@@ -43,23 +80,27 @@ describe('ClassifierSdk Functional Tests', () => {
4380
issuerUrl: process.env.VITE_OAUTH_ISSUER,
4481
clientId: process.env.VITE_ATHENA_CLIENT_ID,
4582
clientSecret: process.env.VITE_ATHENA_CLIENT_SECRET,
46-
scope: 'manage:classify'
47-
}
83+
scope: 'manage:classify',
84+
},
4885
});
4986

5087
// Generate 10 unique correlationIds
51-
const correlationIds = Array.from({ length: 10 }, () => randomUUID().toString());
88+
const correlationIds = Array.from({ length: 10 }, () =>
89+
randomUUID().toString(),
90+
);
5291

5392
correlationIds.sort((a, b) => a.localeCompare(b));
5493

5594
annotate(`Correlation IDs: ${correlationIds.join(', ')}`);
5695

5796
// Create 10 input objects, each with a new stream and unique correlationId
58-
const inputs: ClassifyImageInput[] = correlationIds.map((correlationId) => ({
59-
data: fs.createReadStream(imagePath),
60-
format: ImageFormat.PNG,
61-
correlationId
62-
}));
97+
const inputs: ClassifyImageInput[] = correlationIds.map(
98+
(correlationId) => ({
99+
data: fs.createReadStream(imagePath),
100+
format: ImageFormat.PNG,
101+
correlationId,
102+
}),
103+
);
63104

64105
// Create a promise to wrap the event emitter event 'data'
65106
const promise = new Promise<ClassificationOutput[]>((resolve, reject) => {
@@ -107,23 +148,26 @@ describe('ClassifierSdk Functional Tests', () => {
107148
// Check that all correlationIds are present in the outputs
108149
expect(outputs.length).toBe(correlationIds.length);
109150

110-
const expectedOutputs = correlationIds.map(id => (
111-
{
112-
correlationId: id,
113-
classifications: expect.toBeOneOf([expect.arrayContaining([
151+
const expectedOutputs = correlationIds.map((id) => ({
152+
correlationId: id,
153+
classifications: expect.toBeOneOf([
154+
expect.arrayContaining([
114155
{
115156
label: expect.any(String),
116-
weight: expect.any(Number)
117-
}
118-
]), []])
119-
}
120-
));
157+
weight: expect.any(Number),
158+
},
159+
]),
160+
[],
161+
]),
162+
}));
121163

122164
expect(outputs).toMatchObject(expectedOutputs);
123165
}, 120000);
124166

125-
it('should classify with raw uint8 resize return responses (integration smoke test)', async ({ expect, annotate }) => {
126-
167+
it('should classify with raw uint8 resize return responses (integration smoke test)', async ({
168+
expect,
169+
annotate,
170+
}) => {
127171
const imagePath = __dirname + '/448x448.jpg';
128172
const sdk = new ClassifierSdk({
129173
deploymentId: process.env.VITE_ATHENA_DEPLOYMENT_ID,
@@ -132,8 +176,8 @@ describe('ClassifierSdk Functional Tests', () => {
132176
issuerUrl: process.env.VITE_OAUTH_ISSUER,
133177
clientId: process.env.VITE_ATHENA_CLIENT_ID,
134178
clientSecret: process.env.VITE_ATHENA_CLIENT_SECRET,
135-
scope: 'manage:classify'
136-
}
179+
scope: 'manage:classify',
180+
},
137181
});
138182

139183
const correlationId = randomUUID();
@@ -148,7 +192,9 @@ describe('ClassifierSdk Functional Tests', () => {
148192
}, 30000);
149193

150194
sdk.on('data', (data) => {
151-
const byCorrelationId = data.outputs.filter(o => o.correlationId === correlationId);
195+
const byCorrelationId = data.outputs.filter(
196+
(o) => o.correlationId === correlationId,
197+
);
152198
if (byCorrelationId.length > 0) {
153199
clearTimeout(timeout);
154200
resolve(byCorrelationId);
@@ -185,20 +231,26 @@ describe('ClassifierSdk Functional Tests', () => {
185231
expect(first).toMatchObject([
186232
{
187233
correlationId,
188-
classifications: expect.toBeOneOf([expect.arrayContaining([
189-
{
190-
label: expect.any(String),
191-
weight: expect.any(Number)
192-
}
193-
]), []])
194-
} as ClassificationOutput
234+
classifications: expect.toBeOneOf([
235+
expect.arrayContaining([
236+
{
237+
label: expect.any(String),
238+
weight: expect.any(Number),
239+
},
240+
]),
241+
[],
242+
]),
243+
} as ClassificationOutput,
195244
]);
196245

197246
// Accept either a successful call or a connection error (for CI/dev convenience)
198247
expect(error).toBeUndefined();
199248
}, 120000);
200249

201-
it('should classify return responses (integration smoke test)', async ({ expect, annotate }) => {
250+
it('should classify return responses (integration smoke test)', async ({
251+
expect,
252+
annotate,
253+
}) => {
202254
// This is a smoke test. You must have a running gRPC server at localhost:50051 for this to pass.
203255
// You may want to mock the gRPC client for true unit testing.
204256
const imagePath = __dirname + '/448x448.jpg';
@@ -209,8 +261,8 @@ describe('ClassifierSdk Functional Tests', () => {
209261
issuerUrl: process.env.VITE_OAUTH_ISSUER,
210262
clientId: process.env.VITE_ATHENA_CLIENT_ID,
211263
clientSecret: process.env.VITE_ATHENA_CLIENT_SECRET,
212-
scope: 'manage:classify'
213-
}
264+
scope: 'manage:classify',
265+
},
214266
});
215267

216268
const correlationId = randomUUID();
@@ -225,7 +277,9 @@ describe('ClassifierSdk Functional Tests', () => {
225277
}, 30000);
226278

227279
sdk.on('data', (data) => {
228-
const byCorrelationId = data.outputs.filter(o => o.correlationId === correlationId);
280+
const byCorrelationId = data.outputs.filter(
281+
(o) => o.correlationId === correlationId,
282+
);
229283
if (byCorrelationId.length > 0) {
230284
clearTimeout(timeout);
231285
resolve(byCorrelationId);
@@ -262,13 +316,16 @@ describe('ClassifierSdk Functional Tests', () => {
262316
expect(first).toMatchObject([
263317
{
264318
correlationId,
265-
classifications: expect.toBeOneOf([expect.arrayContaining([
266-
{
267-
label: expect.any(String),
268-
weight: expect.any(Number)
269-
}
270-
]), []])
271-
} as ClassificationOutput
319+
classifications: expect.toBeOneOf([
320+
expect.arrayContaining([
321+
{
322+
label: expect.any(String),
323+
weight: expect.any(Number),
324+
},
325+
]),
326+
[],
327+
]),
328+
} as ClassificationOutput,
272329
]);
273330

274331
// Accept either a successful call or a connection error (for CI/dev convenience)

__tests__/unit/main.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,10 @@ describe('ClassifierSdk', () => {
122122
expect(typeof sdk.sendClassifyRequest).toBe('function');
123123
});
124124

125+
it('should have classifySingle method', () => {
126+
expect(typeof sdk.classifySingle).toBe('function');
127+
});
128+
125129
it('should throw error when sendClassifyRequest called without open', async () => {
126130
const input = {
127131
data: Buffer.from('test'),

eslint.config.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export default tseslint.config(
1919
'src/athena/**',
2020
'athena-protobufs/**',
2121
'docs/**',
22+
'**/generated/**',
2223
],
2324
},
2425
eslint.configs.recommended,

0 commit comments

Comments
 (0)