Skip to content

Commit 36a03aa

Browse files
authored
Fix flow.json fork field usage in flow test --fork <CUSTOM_FORK_NETWORK> (#2248)
1 parent b2d3c26 commit 36a03aa

File tree

2 files changed

+365
-4
lines changed

2 files changed

+365
-4
lines changed

internal/test/test.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -194,24 +194,36 @@ func testCode(
194194

195195
// Resolve network labels using flow.json state
196196
resolveNetworkFromState := func(label string) (string, bool) {
197-
network, err := state.Networks().ByName(strings.ToLower(strings.TrimSpace(label)))
197+
normalizedLabel := strings.ToLower(strings.TrimSpace(label))
198+
network, err := state.Networks().ByName(normalizedLabel)
198199
if err != nil || network == nil {
199200
return "", false
200201
}
201-
if strings.TrimSpace(network.Host) == "" {
202+
203+
// If network has a fork, resolve the fork network's host
204+
host := strings.TrimSpace(network.Host)
205+
if network.Fork != "" {
206+
forkName := strings.ToLower(strings.TrimSpace(network.Fork))
207+
forkNetwork, err := state.Networks().ByName(forkName)
208+
if err != nil {
209+
return "", false
210+
}
211+
host = strings.TrimSpace(forkNetwork.Host)
212+
}
213+
214+
if host == "" {
202215
return "", false
203216
}
204217

205218
// Track network resolution for current test file (indicates pragma-based fork usage)
206219
// Only track if it's not the default "testing" network
207-
normalizedLabel := strings.ToLower(strings.TrimSpace(label))
208220
if currentTestFile != "" && normalizedLabel != "testing" {
209221
if _, exists := fileNetworkResolutions[currentTestFile]; !exists {
210222
fileNetworkResolutions[currentTestFile] = normalizedLabel
211223
}
212224
}
213225

214-
return network.Host, true
226+
return host, true
215227
}
216228

217229
// Configure fork mode if requested

internal/test/test_test.go

Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -900,3 +900,352 @@ func TestForkMode_AutodetectFailureRequiresExplicitNetwork(t *testing.T) {
900900
require.Error(t, err)
901901
assert.ErrorContains(t, err, "failed to get chain ID from fork host")
902902
}
903+
904+
func TestNetworkForkResolution_Success(t *testing.T) {
905+
t.Parallel()
906+
907+
_, state, _ := util.TestMocks(t)
908+
909+
// Add mainnet network with a host
910+
state.Networks().AddOrUpdate(config.Network{
911+
Name: "mainnet",
912+
Host: "access.mainnet.nodes.onflow.org:9000",
913+
})
914+
915+
// Add mainnet-fork that references mainnet via Fork field (no host)
916+
state.Networks().AddOrUpdate(config.Network{
917+
Name: "mainnet-fork",
918+
Fork: "mainnet",
919+
})
920+
921+
// Create a simple test that uses the test_fork pragma
922+
testScript := []byte(`
923+
#test_fork(network: "mainnet-fork", height: nil)
924+
925+
import Test
926+
927+
access(all) fun testSimple() {
928+
Test.assert(true)
929+
}
930+
`)
931+
932+
testFiles := map[string][]byte{
933+
"test_fork_resolution.cdc": testScript,
934+
}
935+
936+
result, err := testCode(testFiles, state, flagsTests{})
937+
938+
require.NoError(t, err)
939+
require.Len(t, result.Results, 1)
940+
assert.NoError(t, result.Results["test_fork_resolution.cdc"][0].Error)
941+
}
942+
943+
func TestNetworkForkResolution_ForkNetworkNotFound(t *testing.T) {
944+
t.Parallel()
945+
946+
_, state, _ := util.TestMocks(t)
947+
948+
// Add mainnet-fork that references non-existent network
949+
state.Networks().AddOrUpdate(config.Network{
950+
Name: "mainnet-fork",
951+
Fork: "nonexistent",
952+
})
953+
954+
// Create a simple test that uses the fork network
955+
testScript := []byte(`
956+
#test_fork(network: "mainnet-fork", height: nil)
957+
958+
import Test
959+
960+
access(all) fun testSimple() {
961+
Test.assert(true)
962+
}
963+
`)
964+
965+
testFiles := map[string][]byte{
966+
"test_fork_missing.cdc": testScript,
967+
}
968+
969+
_, err := testCode(testFiles, state, flagsTests{})
970+
971+
require.Error(t, err)
972+
assert.ErrorContains(t, err, "could not resolve network")
973+
}
974+
975+
func TestNetworkForkResolution_ForkNetworkHasNoHost(t *testing.T) {
976+
t.Parallel()
977+
978+
_, state, _ := util.TestMocks(t)
979+
980+
// Add mainnet network with no host
981+
state.Networks().AddOrUpdate(config.Network{
982+
Name: "mainnet",
983+
})
984+
985+
// Add mainnet-fork that references mainnet with no host
986+
state.Networks().AddOrUpdate(config.Network{
987+
Name: "mainnet-fork",
988+
Fork: "mainnet",
989+
})
990+
991+
// Create a simple test that uses the fork network
992+
testScript := []byte(`
993+
#test_fork(network: "mainnet-fork", height: nil)
994+
995+
import Test
996+
997+
access(all) fun testSimple() {
998+
Test.assert(true)
999+
}
1000+
`)
1001+
1002+
testFiles := map[string][]byte{
1003+
"test_fork_no_host.cdc": testScript,
1004+
}
1005+
1006+
_, err := testCode(testFiles, state, flagsTests{})
1007+
1008+
// Should fail with network resolution error
1009+
require.Error(t, err)
1010+
assert.ErrorContains(t, err, "network resolver could not resolve network")
1011+
}
1012+
1013+
func TestNetworkForkResolution_WithOwnHost(t *testing.T) {
1014+
t.Parallel()
1015+
1016+
_, state, _ := util.TestMocks(t)
1017+
1018+
// Add mainnet network
1019+
state.Networks().AddOrUpdate(config.Network{
1020+
Name: "mainnet",
1021+
Host: "access.mainnet.nodes.onflow.org:9000",
1022+
})
1023+
1024+
// Add mainnet-fork with its own host AND fork field
1025+
// Should use mainnet's host (from Fork) not its own
1026+
state.Networks().AddOrUpdate(config.Network{
1027+
Name: "mainnet-fork",
1028+
Host: "127.0.0.1:3569",
1029+
Fork: "mainnet",
1030+
})
1031+
1032+
// Create a simple test that uses the fork network
1033+
testScript := []byte(`
1034+
#test_fork(network: "mainnet-fork", height: nil)
1035+
1036+
import Test
1037+
1038+
access(all) fun testSimple() {
1039+
Test.assert(true)
1040+
}
1041+
`)
1042+
1043+
testFiles := map[string][]byte{
1044+
"test_fork_with_host.cdc": testScript,
1045+
}
1046+
1047+
result, err := testCode(testFiles, state, flagsTests{})
1048+
1049+
require.NoError(t, err)
1050+
require.Len(t, result.Results, 1)
1051+
assert.NoError(t, result.Results["test_fork_with_host.cdc"][0].Error)
1052+
}
1053+
1054+
func TestContractAddressForkResolution_UsesMainnetForkFirst(t *testing.T) {
1055+
t.Parallel()
1056+
1057+
_, state, _ := util.TestMocks(t)
1058+
1059+
// Add mainnet network
1060+
state.Networks().AddOrUpdate(config.Network{
1061+
Name: "mainnet",
1062+
Host: "access.mainnet.nodes.onflow.org:9000",
1063+
})
1064+
1065+
// Add mainnet-fork network
1066+
state.Networks().AddOrUpdate(config.Network{
1067+
Name: "mainnet-fork",
1068+
Host: "127.0.0.1:3569",
1069+
Fork: "mainnet",
1070+
})
1071+
1072+
// Contract with mainnet-fork specific alias (using proper mainnet-style address)
1073+
mainnetForkAddr := flowsdk.HexToAddress("0x1654653399040a61")
1074+
contractSource := []byte(`
1075+
access(all) contract TestContract {
1076+
access(all) var value: Int
1077+
init() { self.value = 42 }
1078+
}
1079+
`)
1080+
_ = state.ReaderWriter().WriteFile("TestContract.cdc", contractSource, 0644)
1081+
1082+
c := config.Contract{
1083+
Name: "TestContract",
1084+
Location: "TestContract.cdc",
1085+
Aliases: config.Aliases{
1086+
{
1087+
Network: "mainnet-fork",
1088+
Address: mainnetForkAddr,
1089+
},
1090+
},
1091+
}
1092+
state.Contracts().AddOrUpdate(c)
1093+
1094+
testScript := []byte(`
1095+
#test_fork(network: "mainnet-fork", height: nil)
1096+
1097+
import Test
1098+
import "TestContract"
1099+
1100+
access(all) fun testUsesMainnetForkAddress() {
1101+
// Verify TestContract resolves to the mainnet-fork address
1102+
let addr = Type<TestContract>().address!
1103+
Test.assertEqual(0x1654653399040a61 as Address, addr)
1104+
}
1105+
`)
1106+
1107+
testFiles := map[string][]byte{
1108+
"test_mainnet_fork_addr.cdc": testScript,
1109+
}
1110+
1111+
result, err := testCode(testFiles, state, flagsTests{})
1112+
1113+
require.NoError(t, err)
1114+
require.Len(t, result.Results, 1)
1115+
assert.NoError(t, result.Results["test_mainnet_fork_addr.cdc"][0].Error)
1116+
}
1117+
1118+
func TestContractAddressForkResolution_FallbackToMainnet(t *testing.T) {
1119+
t.Parallel()
1120+
1121+
_, state, _ := util.TestMocks(t)
1122+
1123+
// Add mainnet network
1124+
state.Networks().AddOrUpdate(config.Network{
1125+
Name: "mainnet",
1126+
Host: "access.mainnet.nodes.onflow.org:9000",
1127+
})
1128+
1129+
// Add mainnet-fork network
1130+
state.Networks().AddOrUpdate(config.Network{
1131+
Name: "mainnet-fork",
1132+
Host: "127.0.0.1:3569",
1133+
Fork: "mainnet",
1134+
})
1135+
1136+
// Contract with ONLY mainnet alias (no mainnet-fork alias)
1137+
mainnetAddr := flowsdk.HexToAddress("0xf233dcee88fe0abe")
1138+
contractSource := []byte(`
1139+
access(all) contract TestContract {
1140+
access(all) var value: Int
1141+
init() { self.value = 99 }
1142+
}
1143+
`)
1144+
_ = state.ReaderWriter().WriteFile("TestContract.cdc", contractSource, 0644)
1145+
1146+
c := config.Contract{
1147+
Name: "TestContract",
1148+
Location: "TestContract.cdc",
1149+
Aliases: config.Aliases{
1150+
{
1151+
Network: "mainnet",
1152+
Address: mainnetAddr,
1153+
},
1154+
},
1155+
}
1156+
state.Contracts().AddOrUpdate(c)
1157+
1158+
testScript := []byte(`
1159+
#test_fork(network: "mainnet-fork", height: nil)
1160+
1161+
import Test
1162+
import "TestContract"
1163+
1164+
access(all) fun testFallbackToMainnetAddress() {
1165+
// Verify TestContract falls back to mainnet address since mainnet-fork has no alias
1166+
let addr = Type<TestContract>().address!
1167+
Test.assertEqual(0xf233dcee88fe0abe as Address, addr)
1168+
}
1169+
`)
1170+
1171+
testFiles := map[string][]byte{
1172+
"test_fallback_mainnet.cdc": testScript,
1173+
}
1174+
1175+
result, err := testCode(testFiles, state, flagsTests{})
1176+
1177+
require.NoError(t, err)
1178+
require.Len(t, result.Results, 1)
1179+
assert.NoError(t, result.Results["test_fallback_mainnet.cdc"][0].Error)
1180+
}
1181+
1182+
func TestContractAddressForkResolution_PrioritizesForkOverParent(t *testing.T) {
1183+
t.Parallel()
1184+
1185+
_, state, _ := util.TestMocks(t)
1186+
1187+
// Add mainnet network
1188+
state.Networks().AddOrUpdate(config.Network{
1189+
Name: "mainnet",
1190+
Host: "access.mainnet.nodes.onflow.org:9000",
1191+
})
1192+
1193+
// Add mainnet-fork network
1194+
state.Networks().AddOrUpdate(config.Network{
1195+
Name: "mainnet-fork",
1196+
Host: "127.0.0.1:3569",
1197+
Fork: "mainnet",
1198+
})
1199+
1200+
// Contract with BOTH mainnet and mainnet-fork aliases - should use mainnet-fork first
1201+
mainnetAddr := flowsdk.HexToAddress("0x1654653399040a61")
1202+
mainnetForkAddr := flowsdk.HexToAddress("0xf233dcee88fe0abe")
1203+
1204+
contractSource := []byte(`
1205+
access(all) contract TestContract {
1206+
access(all) var value: Int
1207+
init() { self.value = 123 }
1208+
}
1209+
`)
1210+
_ = state.ReaderWriter().WriteFile("TestContract.cdc", contractSource, 0644)
1211+
1212+
c := config.Contract{
1213+
Name: "TestContract",
1214+
Location: "TestContract.cdc",
1215+
Aliases: config.Aliases{
1216+
{
1217+
Network: "mainnet",
1218+
Address: mainnetAddr,
1219+
},
1220+
{
1221+
Network: "mainnet-fork",
1222+
Address: mainnetForkAddr,
1223+
},
1224+
},
1225+
}
1226+
state.Contracts().AddOrUpdate(c)
1227+
1228+
// Should use the mainnet-fork address (0xf233dcee88fe0abe), not mainnet (0x1654653399040a61)
1229+
testScript := []byte(`
1230+
#test_fork(network: "mainnet-fork", height: nil)
1231+
1232+
import Test
1233+
import "TestContract"
1234+
1235+
access(all) fun testPrioritizesFork() {
1236+
// Verify TestContract uses mainnet-fork address (0xf233dcee88fe0abe), NOT mainnet (0x1654653399040a61)
1237+
let addr = Type<TestContract>().address!
1238+
Test.assertEqual(0xf233dcee88fe0abe as Address, addr)
1239+
}
1240+
`)
1241+
1242+
testFiles := map[string][]byte{
1243+
"test_priority.cdc": testScript,
1244+
}
1245+
1246+
result, err := testCode(testFiles, state, flagsTests{})
1247+
1248+
require.NoError(t, err)
1249+
require.Len(t, result.Results, 1)
1250+
assert.NoError(t, result.Results["test_priority.cdc"][0].Error)
1251+
}

0 commit comments

Comments
 (0)