Skip to content

Commit fb4d5f9

Browse files
sandsinhSandesh Sinha
andauthored
fix: adds a function to get latest token by createdAt (#1597)
## Summary - Adds `findLastCreatedBySiteIdAndTokenType` to `TokenCollection` to retrieve the most recently created token for a given site and token type, regardless of cycle - Useful when callers need the latest token across all cycles rather than a specific cycle - Validates required parameters and throws `DataAccessError` on missing input ## Test plan - [ ] Unit tests pass: `npm test -w packages/spacecat-shared-data-access` - [ ] Integration tests pass: `npm run test:it -w packages/spacecat-shared-data-access` (requires Docker) - [ ] Verify `findLastCreatedBySiteIdAndTokenType` returns null when no tokens exist for a site/type - [ ] Verify it returns the most recently created token when multiple cycles exist - [ ] Verify it throws when `siteId` or `tokenType` is missing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Sandesh Sinha <sandsinh@Sandeshs-MacBook-Pro.local>
1 parent 388249d commit fb4d5f9

3 files changed

Lines changed: 104 additions & 0 deletions

File tree

packages/spacecat-shared-data-access/src/models/token/token.collection.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,21 @@ class TokenCollection extends BaseCollection {
4242
* @returns {Promise<import('./token.model.js').default|null>} Token instance
4343
* (existing or newly created), or null when none exists and createIfNotFound is false.
4444
*/
45+
/**
46+
* Finds the most recently created Token for a given siteId and tokenType,
47+
* regardless of cycle. Returns null if no token exists.
48+
*
49+
* @param {string} siteId - Site ID (UUID).
50+
* @param {string} tokenType - Token type (e.g. grant_cwv, grant_broken_backlinks).
51+
* @returns {Promise<import('./token.model.js').default|null>} The last created Token, or null.
52+
*/
53+
async findLastCreatedBySiteIdAndTokenType(siteId, tokenType) {
54+
if (!hasText(siteId) || !hasText(tokenType)) {
55+
throw new DataAccessError('TokenCollection.findLastCreatedBySiteIdAndTokenType: siteId and tokenType are required');
56+
}
57+
return this.findByIndexKeys({ siteId, tokenType }, { order: 'desc' });
58+
}
59+
4560
async findBySiteIdAndTokenType(siteId, tokenType, options = {}) {
4661
if (!hasText(siteId) || !hasText(tokenType)) {
4762
throw new DataAccessError('TokenCollection.findBySiteIdAndTokenType: siteId and tokenType are required');

packages/spacecat-shared-data-access/test/it/token/token.test.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,63 @@ describe('Token IT', () => {
9595
});
9696
});
9797

98+
describe('findLastCreatedBySiteIdAndTokenType', () => {
99+
// sites[2] — no tokens seeded for this site, so tests start clean
100+
const siteId = '56a691db-d32e-4308-ac99-a21de0580557';
101+
const tokenType = 'grant_cwv';
102+
103+
it('returns null when no token exists for the site and token type', async () => {
104+
const token = await Token.findLastCreatedBySiteIdAndTokenType(siteId, tokenType);
105+
expect(token).to.be.null;
106+
});
107+
108+
it('returns the token when exactly one cycle exists', async () => {
109+
const created = await Token.create({
110+
siteId,
111+
tokenType,
112+
cycle: '2025-01',
113+
total: 3,
114+
used: 0,
115+
});
116+
117+
const found = await Token.findLastCreatedBySiteIdAndTokenType(siteId, tokenType);
118+
119+
expect(found).to.be.an('object');
120+
expect(found.getSiteId()).to.equal(siteId);
121+
expect(found.getTokenType()).to.equal(tokenType);
122+
expect(found.getCycle()).to.equal('2025-01');
123+
expect(found.getId()).to.equal(created.getId());
124+
});
125+
126+
it('returns the most recent cycle when multiple cycles exist', async () => {
127+
// '2025-01' already created above; add a newer cycle
128+
await Token.create({
129+
siteId,
130+
tokenType,
131+
cycle: '2025-06',
132+
total: 5,
133+
used: 1,
134+
});
135+
136+
const found = await Token.findLastCreatedBySiteIdAndTokenType(siteId, tokenType);
137+
138+
expect(found.getCycle()).to.equal('2025-06');
139+
expect(found.getTotal()).to.equal(5);
140+
});
141+
142+
it('throws when siteId is missing', async () => {
143+
await expect(
144+
Token.findLastCreatedBySiteIdAndTokenType('', tokenType),
145+
).to.be.rejectedWith(/required/);
146+
});
147+
148+
it('throws when tokenType is missing', async () => {
149+
await expect(
150+
Token.findLastCreatedBySiteIdAndTokenType(siteId, ''),
151+
).to.be.rejectedWith(/required/);
152+
});
153+
});
154+
98155
describe('allBySiteId', () => {
99156
// fixtures.sites[1].siteId — isolated from findBySiteIdAndTokenType /
100157
// grantSuggestions tests so seeded historical-cycle rows do not leak into

packages/spacecat-shared-data-access/test/unit/models/token/token.collection.test.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,38 @@ describe('TokenCollection', () => {
6363
});
6464
});
6565

66+
describe('findLastCreatedBySiteIdAndTokenType', () => {
67+
it('returns the last created token when found', async () => {
68+
instance.findByIndexKeys = stub().resolves(model);
69+
70+
const result = await instance.findLastCreatedBySiteIdAndTokenType('site-1', 'grant_cwv');
71+
72+
expect(result).to.equal(model);
73+
expect(instance.findByIndexKeys).to.have.been.calledOnceWith(
74+
{ siteId: 'site-1', tokenType: 'grant_cwv' },
75+
{ order: 'desc' },
76+
);
77+
});
78+
79+
it('returns null when no token exists', async () => {
80+
instance.findByIndexKeys = stub().resolves(null);
81+
82+
const result = await instance.findLastCreatedBySiteIdAndTokenType('site-1', 'grant_cwv');
83+
84+
expect(result).to.be.null;
85+
});
86+
87+
it('throws if siteId is missing', async () => {
88+
await expect(instance.findLastCreatedBySiteIdAndTokenType(undefined, 'grant_cwv'))
89+
.to.be.rejectedWith(/siteId|required/);
90+
});
91+
92+
it('throws if tokenType is missing', async () => {
93+
await expect(instance.findLastCreatedBySiteIdAndTokenType('site-1', ''))
94+
.to.be.rejectedWith(/tokenType|required/);
95+
});
96+
});
97+
6698
describe('findBySiteIdAndTokenType', () => {
6799
let expectedCycle;
68100

0 commit comments

Comments
 (0)