Skip to content

Commit

Permalink
fix: return 404 when gettings tags for a non existing feature (#3560)
Browse files Browse the repository at this point in the history
From the discussion here
#3496 (comment)

This PR checks the existence of the feature before trying to get tags
for the feature. Doing so by stealing the exists method from the feature
store, since that's what we need to know exists, and avoiding having the
feature store as a dependency to the featureTagStore seemed reasonable.
  • Loading branch information
Christopher Kolstad committed Apr 19, 2023
1 parent 0e80484 commit 4f7fd46
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 15 deletions.
39 changes: 26 additions & 13 deletions src/lib/db/feature-tag-store.ts
Expand Up @@ -9,6 +9,7 @@ import {
IFeatureTagStore,
} from '../types/stores/feature-tag-store';
import { Db } from './db';
import NotFoundError from '../error/notfound-error';

const COLUMNS = ['feature_name', 'tag_type', 'tag_value'];
const TABLE = 'feature_tag';
Expand Down Expand Up @@ -95,12 +96,27 @@ class FeatureTagStore implements IFeatureTagStore {

async getAllTagsForFeature(featureName: string): Promise<ITag[]> {
const stopTimer = this.timer('getAllForFeature');
const rows = await this.db
.select(COLUMNS)
.from<FeatureTagTable>(TABLE)
.where({ feature_name: featureName });
stopTimer();
return rows.map(this.featureTagRowToTag);
if (await this.featureExists(featureName)) {
const rows = await this.db
.select(COLUMNS)
.from<FeatureTagTable>(TABLE)
.where({ feature_name: featureName });
stopTimer();
return rows.map(this.featureTagRowToTag);
} else {
throw new NotFoundError(
`Could not find feature with name ${featureName}`,
);
}
}

async featureExists(featureName: string): Promise<boolean> {
const result = await this.db.raw(
'SELECT EXISTS (SELECT 1 FROM features WHERE name = ?) AS present',
[featureName],
);
const { present } = result.rows[0];
return present;
}

async getAllByFeatures(features: string[]): Promise<IFeatureTag[]> {
Expand Down Expand Up @@ -187,13 +203,10 @@ class FeatureTagStore implements IFeatureTagStore {
}

featureTagRowToTag(row: FeatureTagTable): ITag {
if (row) {
return {
value: row.tag_value,
type: row.tag_type,
};
}
return null;
return {
value: row.tag_value,
type: row.tag_type,
};
}

rowToFeatureAndTag(row: FeatureTagTable): IFeatureAndTag {
Expand Down
2 changes: 1 addition & 1 deletion src/lib/routes/admin-api/feature.ts
Expand Up @@ -120,7 +120,7 @@ class FeatureController extends Controller {
operationId: 'listTags',
responses: {
200: createResponseSchema('tagsSchema'),
...getStandardResponses(401),
...getStandardResponses(401, 403, 404),
},
}),
],
Expand Down
Expand Up @@ -6182,6 +6182,12 @@ If the provided project does not exist, the list of events will be empty.",
"401": {
"description": "Authorization information is missing or invalid. Provide a valid API token as the \`authorization\` header, e.g. \`authorization:*.*.my-admin-token\`.",
},
"403": {
"description": "User credentials are valid but does not have enough privileges to execute this operation",
},
"404": {
"description": "The requested resource was not found.",
},
},
"summary": "Get all tags for a feature.",
"tags": [
Expand Down
20 changes: 19 additions & 1 deletion src/test/e2e/stores/feature-tag-store.e2e.test.ts
Expand Up @@ -2,6 +2,7 @@ import { IFeatureTagStore } from 'lib/types/stores/feature-tag-store';
import { IFeatureToggleStore } from 'lib/types/stores/feature-toggle-store';
import dbInit from '../helpers/database-init';
import getLogger from '../../fixtures/no-logger';
import NotFoundError from '../../../lib/error/notfound-error';

let stores;
let db;
Expand Down Expand Up @@ -43,7 +44,7 @@ test('should tag feature', async () => {
expect(featureTag.tagValue).toBe(tag.value);
});

test('feature tag exits', async () => {
test('feature tag exists', async () => {
await featureTagStore.tagFeature(featureName, tag);
const exists = await featureTagStore.exists({
featureName,
Expand Down Expand Up @@ -97,3 +98,20 @@ test('should import feature tags', async () => {
const all = await featureTagStore.getAll();
expect(all).toHaveLength(2);
});

test('should throw not found error if feature does not exist', async () => {
await expect(async () =>
featureTagStore.getAllTagsForFeature('non.existing.toggle'),
).rejects.toThrow(
new NotFoundError(
`Could not find feature with name non.existing.toggle`,
),
);
});

test('Returns empty tag list for existing feature with no tags', async () => {
const name = 'feature.with.no.tags';
await featureToggleStore.create('default', { name });
let tags = await featureTagStore.getAllTagsForFeature(name);
expect(tags).toHaveLength(0);
});

0 comments on commit 4f7fd46

Please sign in to comment.