@@ -1154,6 +1154,151 @@ def test_upload_without_runner_defaults_to_ray(self):
11541154 program = Program .objects .get (title = "Defaultrunnerfunction" )
11551155 assert program .runner == Program .RAY
11561156
1157+ def test_upload_with_arguments_schema_stores_it (self ):
1158+ """Upload with arguments_schema saves the schema on the Program."""
1159+ schema = json .dumps ({"type" : "object" , "properties" : {"shots" : {"type" : "integer" }}})
1160+ fake_file = ContentFile (b"print('hello')" )
1161+ fake_file .name = "test.tar"
1162+ TestUtils .authorize_client (user = "test_user" , client = self .client )
1163+
1164+ with self .settings (MEDIA_ROOT = self .MEDIA_ROOT ):
1165+ response = self .client .post (
1166+ "/api/v1/programs/upload/" ,
1167+ data = {
1168+ "title" : "schema-function" ,
1169+ "entrypoint" : "main.py" ,
1170+ "dependencies" : "[]" ,
1171+ "arguments_schema" : schema ,
1172+ "artifact" : fake_file ,
1173+ },
1174+ )
1175+ assert response .status_code == status .HTTP_200_OK
1176+ program = Program .objects .get (title = "schema-function" )
1177+ assert program .arguments_schema == schema
1178+
1179+ def test_upload_with_invalid_schema_returns_400 (self ):
1180+ """Upload with malformed JSON as arguments_schema returns 400."""
1181+ fake_file = ContentFile (b"print('hello')" )
1182+ fake_file .name = "test.tar"
1183+ TestUtils .authorize_client (user = "test_user" , client = self .client )
1184+
1185+ with self .settings (MEDIA_ROOT = self .MEDIA_ROOT ):
1186+ response = self .client .post (
1187+ "/api/v1/programs/upload/" ,
1188+ data = {
1189+ "title" : "bad-schema-function" ,
1190+ "entrypoint" : "main.py" ,
1191+ "dependencies" : "[]" ,
1192+ "arguments_schema" : "not-valid-json{{{" ,
1193+ "artifact" : fake_file ,
1194+ },
1195+ )
1196+ assert response .status_code == status .HTTP_400_BAD_REQUEST
1197+
1198+ def test_reupload_without_schema_preserves_existing_schema (self ):
1199+ """Re-uploading a function without arguments_schema keeps the existing schema."""
1200+ schema = json .dumps ({"type" : "object" })
1201+ user = TestUtils .authorize_client (user = "test_user" , client = self .client )
1202+ TestUtils .create_program (
1203+ program_title = "preserve-schema-func" ,
1204+ author = user ,
1205+ entrypoint = "main.py" ,
1206+ arguments_schema = schema ,
1207+ )
1208+ fake_file = ContentFile (b"print('hello')" )
1209+ fake_file .name = "test.tar"
1210+
1211+ with self .settings (MEDIA_ROOT = self .MEDIA_ROOT ):
1212+ response = self .client .post (
1213+ "/api/v1/programs/upload/" ,
1214+ data = {
1215+ "title" : "preserve-schema-func" ,
1216+ "entrypoint" : "main.py" ,
1217+ "dependencies" : "[]" ,
1218+ "artifact" : fake_file ,
1219+ },
1220+ )
1221+ assert response .status_code == status .HTTP_200_OK
1222+ program = Program .objects .get (title = "preserve-schema-func" , author = user )
1223+ assert program .arguments_schema == schema
1224+
1225+ def test_upload_all_fields_stores_all_fields (self ):
1226+ """Upload with all optional fields - every field is persisted to the DB."""
1227+ fake_file = ContentFile (b"print('hello')" )
1228+ fake_file .name = "test.tar"
1229+ user = TestUtils .authorize_client (user = "test_user" , client = self .client )
1230+ schema = json .dumps ({"type" : "object" , "properties" : {"n" : {"type" : "integer" }}})
1231+ env_vars = json .dumps ({"MY_KEY" : "MY_VALUE" })
1232+
1233+ with self .settings (MEDIA_ROOT = self .MEDIA_ROOT ):
1234+ response = self .client .post (
1235+ "/api/v1/programs/upload/" ,
1236+ data = {
1237+ "title" : "full-function" ,
1238+ "entrypoint" : "custom_entry.py" ,
1239+ "dependencies" : "[]" ,
1240+ "env_vars" : env_vars ,
1241+ "description" : "My description" ,
1242+ "version" : "1.2.3" ,
1243+ "runner" : Program .RAY ,
1244+ "arguments_schema" : schema ,
1245+ "artifact" : fake_file ,
1246+ },
1247+ )
1248+ assert response .status_code == status .HTTP_200_OK
1249+ program = Program .objects .get (title = "full-function" , author = user )
1250+ assert program .entrypoint == "custom_entry.py"
1251+ assert program .description == "My description"
1252+ assert program .version == "1.2.3"
1253+ assert program .runner == Program .RAY
1254+ assert program .arguments_schema == schema
1255+ assert program .env_vars not in ("{}" , "" )
1256+
1257+ def test_reupload_with_only_required_fields_preserves_optional_fields (self ):
1258+ """Re-uploading with title+entrypoint must preserve all previously set optional fields.
1259+
1260+ This test proves empirically which fields survive a minimal re-upload.
1261+ Fields that used to RESET (runner, dependencies, env_vars) must be PRESERVED.
1262+ """
1263+ schema = json .dumps ({"type" : "object" })
1264+ user = TestUtils .authorize_client (user = "test_user" , client = self .client )
1265+ TestUtils .get_or_create_ce_project (project_name = "test-project" , project_id = "test-id" )
1266+ TestUtils .create_program (
1267+ program_title = "reupload-test-func" ,
1268+ author = user ,
1269+ entrypoint = "original.py" ,
1270+ description = "Original description" ,
1271+ version = "2.0.0" ,
1272+ runner = Program .FLEETS ,
1273+ dependencies = '["numpy==1.26.0"]' ,
1274+ env_vars = '{"MY_KEY":"MY_VALUE"}' ,
1275+ arguments_schema = schema ,
1276+ )
1277+ fake_file = ContentFile (b"print('updated')" )
1278+ fake_file .name = "update.tar"
1279+
1280+ with self .settings (MEDIA_ROOT = self .MEDIA_ROOT , CE_DEFAULT_PROJECT_NAME = "test-project" ):
1281+ response = self .client .post (
1282+ "/api/v1/programs/upload/" ,
1283+ data = {
1284+ "title" : "reupload-test-func" ,
1285+ "entrypoint" : "new_main.py" ,
1286+ "artifact" : fake_file ,
1287+ },
1288+ )
1289+ assert response .status_code == status .HTTP_200_OK
1290+ program = Program .objects .get (title = "reupload-test-func" , author = user )
1291+
1292+ # Already preserved (existing behavior):
1293+ assert program .description == "Original description"
1294+ assert program .version == "2.0.0"
1295+ assert program .arguments_schema == schema
1296+
1297+ # Now also preserved (previously reset):
1298+ assert program .runner == Program .FLEETS
1299+ assert json .loads (program .dependencies ) == ["numpy==1.26.0" ]
1300+ assert program .env_vars not in ("{}" , "" )
1301+
11571302
11581303@pytest .mark .django_db
11591304class TestProgramApiRuntimeInstances :
0 commit comments