Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ jobs:
with:
# The companion workflow parses this artifact as untrusted data only.
name: codeql-pr-results-${{ matrix.language }}
path: results/${{ matrix.language }}.sarif
path: results/*.sarif
if-no-files-found: error
retention-days: 1

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@
import java.lang.annotation.Target;
import java.util.Map;
import org.springframework.util.CollectionUtils;
import tools.jackson.core.JsonParser;
import tools.jackson.core.JsonToken;
import tools.jackson.databind.DeserializationContext;
import tools.jackson.databind.annotation.JsonDeserialize;
import tools.jackson.databind.deser.std.StdScalarDeserializer;
import tools.jackson.databind.type.LogicalType;

@PriorityDto.ValidPriority
@Schema(types = {"object"},
Expand All @@ -40,13 +46,15 @@ public record PriorityDto(
@Min(value = 0, message = "defaultPriority must be >= 0")
@Max(value = MAX_PRIORITY, message = "defaultPriority must be <= " + MAX_PRIORITY)
@Schema(description = "Default priority.")
@JsonDeserialize(using = PriorityValueDeserializer.class)
Long defaultPriority,

@Nullable
@Size(max = MAX_PER_ACCOUNT_ENTRIES,
message = "Maximum number of perAccountPriority entries of " + MAX_PER_ACCOUNT_ENTRIES
+ " is exceeded.")
@Schema(description = "Per-account priority overrides, keyed by account ID.")
@JsonDeserialize(contentUsing = PriorityValueDeserializer.class)
Map<String,
@Min(value = 0, message = "priority must be >= 0")
@Max(value = MAX_PRIORITY, message = "priority must be <= " + MAX_PRIORITY)
Expand All @@ -57,6 +65,8 @@ public record PriorityDto(
static final int MAX_PER_ACCOUNT_ENTRIES = 64;
private static final String MESG_DEFAULT_REQUIRED_WITH_OVERRIDES =
"Invalid priority: 'defaultPriority' is required when 'perAccountPriority' has entries";
private static final String MESG_PRIORITY_MUST_BE_INTEGER =
"priority must be an integer";

@Constraint(validatedBy = ValidPriorityValidator.class)
@Target(ElementType.TYPE)
Expand Down Expand Up @@ -88,4 +98,24 @@ public boolean isValid(PriorityDto value, ConstraintValidatorContext context) {
return true;
}
}

/** Deserializes priority values while rejecting floating-point JSON numbers. */
public static class PriorityValueDeserializer extends StdScalarDeserializer<Long> {
public PriorityValueDeserializer() {
super(Long.class);
}

@Override
public LogicalType logicalType() {
return LogicalType.Integer;
}

@Override
public Long deserialize(JsonParser parser, DeserializationContext context) {
if (parser.hasToken(JsonToken.VALUE_NUMBER_FLOAT)) {
return context.reportInputMismatch(Long.class, MESG_PRIORITY_MUST_BE_INTEGER);
}
return _parseLong(parser, context, Long.class);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,85 @@ void createWithDefaultPriorityAboveMaxIsRejected() {
assertThat(response.getBody()).contains("defaultPriority");
}

@Test
void updateWithFractionalDefaultPriorityIsRejectedWithoutChangingPriority() {
var created = createLlmFunction(uniqueName("fractional-default"), priorityConfig(7L, null));
var updateToken = MOCK_OAUTH2_TOKEN_SERVER.getJwt(TEST_CLIENT_SUBJECT,
List.of(SCOPE_UPDATE_FUNCTION), 100);
var updateEntity = RequestEntity.put(URI.create("/v2/nvcf/functions/" + created.id()
+ "/versions/" + created.versionId()))
.contentType(MediaType.APPLICATION_JSON)
.header("Authorization", "Bearer " + updateToken)
.body("""
{
"llmInvocationConfig": {
"priority": {
"defaultPriority": 1.5
}
}
}
""");

var updateResponse = testRestTemplate.exchange(updateEntity, String.class);

assertThat(updateResponse.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);

var getToken = MOCK_OAUTH2_TOKEN_SERVER.getJwt(TEST_CLIENT_SUBJECT,
List.of(SCOPE_LIST_FUNCTIONS), 100);
var getEntity = RequestEntity.get(URI.create("/v2/nvcf/functions/" + created.id()
+ "/versions/" + created.versionId()))
.header("Authorization", "Bearer " + getToken)
.build();
var getResponse = testRestTemplate.exchange(getEntity, FunctionResponse.class);

assertThat(getResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(getResponse.getBody()).isNotNull();
assertThat(getResponse.getBody().function().llmInvocationConfig().priority().defaultPriority())
.isEqualTo(7L);
}

@Test
void updateWithFractionalPerAccountPriorityIsRejectedWithoutChangingPriority() {
var created = createLlmFunction(
uniqueName("fractional-per-account"),
priorityConfig(7L, Map.of(OVERRIDE_NCA_ID, 3L)));
var updateToken = MOCK_OAUTH2_TOKEN_SERVER.getJwt(TEST_CLIENT_SUBJECT,
List.of(SCOPE_UPDATE_FUNCTION), 100);
var updateEntity = RequestEntity.put(URI.create("/v2/nvcf/functions/" + created.id()
+ "/versions/" + created.versionId()))
.contentType(MediaType.APPLICATION_JSON)
.header("Authorization", "Bearer " + updateToken)
.body("""
{
"llmInvocationConfig": {
"priority": {
"defaultPriority": 7,
"perAccountPriority": {
"nca-override": 1.5
}
}
}
}
""");

var updateResponse = testRestTemplate.exchange(updateEntity, String.class);

assertThat(updateResponse.getStatusCode()).isEqualTo(HttpStatus.BAD_REQUEST);

var getToken = MOCK_OAUTH2_TOKEN_SERVER.getJwt(TEST_CLIENT_SUBJECT,
List.of(SCOPE_LIST_FUNCTIONS), 100);
var getEntity = RequestEntity.get(URI.create("/v2/nvcf/functions/" + created.id()
+ "/versions/" + created.versionId()))
.header("Authorization", "Bearer " + getToken)
.build();
var getResponse = testRestTemplate.exchange(getEntity, FunctionResponse.class);

assertThat(getResponse.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(getResponse.getBody()).isNotNull();
assertThat(getResponse.getBody().function().llmInvocationConfig().priority())
.isEqualTo(new PriorityDto(7L, Map.of(OVERRIDE_NCA_ID, 3L)));
}

@Test
void updateWithModelUpdatesAndLlmInvocationConfigAppliesBothToAllVersions() {
var name = uniqueName("update-both");
Expand Down
Loading