Skip to content

Commit 0f44cc2

Browse files
committed
added tests for flight data schemas
1 parent d4fbfb2 commit 0f44cc2

File tree

5 files changed

+508
-0
lines changed

5 files changed

+508
-0
lines changed

common/models/build.gradle.kts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ plugins {
55
dependencies {
66
// Add model-specific dependencies here
77
implementation("org.apache.avro:avro:1.11.3")
8+
9+
// Test dependencies
10+
testImplementation(platform("org.junit:junit-bom:5.10.0"))
11+
testImplementation("org.junit.jupiter:junit-jupiter")
12+
testImplementation("org.junit.jupiter:junit-jupiter-params")
13+
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
14+
testImplementation("org.assertj:assertj-core:3.24.2")
815
}
916

1017
repositories {
@@ -29,3 +36,18 @@ sourceSets {
2936
}
3037
}
3138
}
39+
40+
tasks.test {
41+
useJUnitPlatform()
42+
testLogging {
43+
events("passed", "skipped", "failed")
44+
}
45+
}
46+
47+
testing {
48+
suites {
49+
val test by getting(JvmTestSuite::class) {
50+
useJUnitJupiter()
51+
}
52+
}
53+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package io.confluent.developer.models.flight;
2+
3+
import org.apache.avro.Schema;
4+
import org.apache.avro.SchemaCompatibility;
5+
import org.apache.avro.SchemaCompatibility.SchemaCompatibilityType;
6+
import org.apache.avro.SchemaCompatibility.SchemaPairCompatibility;
7+
import org.junit.jupiter.api.Test;
8+
9+
import java.io.IOException;
10+
import java.io.InputStream;
11+
import java.time.Instant;
12+
13+
import static org.assertj.core.api.Assertions.assertThat;
14+
import static org.junit.jupiter.api.Assertions.assertEquals;
15+
16+
/**
17+
* Tests for schema compatibility between different versions of the Flight schema.
18+
* This is important for Avro in streaming applications where schema evolution is common.
19+
*/
20+
public class FlightSchemaCompatibilityTest {
21+
22+
@Test
23+
public void testCurrentSchemaMatchesAvroFile() throws IOException {
24+
// Given
25+
Schema currentSchema = Flight.getClassSchema();
26+
Schema fileSchema = loadSchemaFromResource("flight-v1.avsc");
27+
28+
// Then
29+
assertThat(currentSchema.toString()).isEqualTo(fileSchema.toString());
30+
}
31+
32+
@Test
33+
public void testForwardCompatibility() throws IOException {
34+
// Given - Reader with current schema, writer with future schema (v2)
35+
Schema readerSchema = Flight.getClassSchema();
36+
Schema writerSchema = loadSchemaFromResource("flight-v2.avsc");
37+
38+
// When
39+
SchemaPairCompatibility compatibility =
40+
SchemaCompatibility.checkReaderWriterCompatibility(readerSchema, writerSchema);
41+
42+
// Then - Current schema should be able to read data written with future schema
43+
assertEquals(SchemaCompatibilityType.COMPATIBLE, compatibility.getType(),
44+
"Current schema should be able to read data from future schema");
45+
}
46+
47+
@Test
48+
public void testBackwardCompatibility() throws IOException {
49+
// Given - Reader with future schema (v2), writer with current schema
50+
Schema readerSchema = loadSchemaFromResource("flight-v2.avsc");
51+
Schema writerSchema = Flight.getClassSchema();
52+
53+
// When
54+
SchemaPairCompatibility compatibility =
55+
SchemaCompatibility.checkReaderWriterCompatibility(readerSchema, writerSchema);
56+
57+
// Then - Future schema should be able to read data written with current schema
58+
assertEquals(SchemaCompatibilityType.COMPATIBLE, compatibility.getType(),
59+
"Future schema should be able to read data from current schema");
60+
}
61+
62+
@Test
63+
public void testDeserializeWithNewSchemaFields() throws IOException {
64+
// Given
65+
Schema v1Schema = loadSchemaFromResource("flight-v1.avsc");
66+
Schema v2Schema = loadSchemaFromResource("flight-v2.avsc");
67+
68+
// Create a v1 flight record
69+
Flight v1Flight = Flight.newBuilder()
70+
.setFlightNumber("AA123")
71+
.setAirline("American Airlines")
72+
.setOrigin("JFK")
73+
.setDestination("LAX")
74+
.setScheduledDeparture(Instant.now().toEpochMilli())
75+
.setActualDeparture(null)
76+
.setStatus("SCHEDULED")
77+
.build();
78+
79+
// Verify schemas are compatible
80+
SchemaPairCompatibility compatibility =
81+
SchemaCompatibility.checkReaderWriterCompatibility(v2Schema, v1Schema);
82+
assertEquals(SchemaCompatibilityType.COMPATIBLE, compatibility.getType(),
83+
"V2 schema should be able to read data from V1 schema");
84+
}
85+
86+
private Schema loadSchemaFromResource(String resourceName) throws IOException {
87+
try (InputStream is = getClass().getClassLoader().getResourceAsStream(resourceName)) {
88+
if (is == null) {
89+
throw new IOException("Resource not found: " + resourceName);
90+
}
91+
return new Schema.Parser().parse(is);
92+
}
93+
}
94+
}
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
package io.confluent.developer.models.flight;
2+
3+
import org.apache.avro.io.BinaryDecoder;
4+
import org.apache.avro.io.BinaryEncoder;
5+
import org.apache.avro.io.DatumReader;
6+
import org.apache.avro.io.DatumWriter;
7+
import org.apache.avro.io.DecoderFactory;
8+
import org.apache.avro.io.EncoderFactory;
9+
import org.apache.avro.io.JsonDecoder;
10+
import org.apache.avro.io.JsonEncoder;
11+
import org.apache.avro.specific.SpecificDatumReader;
12+
import org.apache.avro.specific.SpecificDatumWriter;
13+
import org.junit.jupiter.api.Test;
14+
import org.junit.jupiter.params.ParameterizedTest;
15+
import org.junit.jupiter.params.provider.Arguments;
16+
import org.junit.jupiter.params.provider.MethodSource;
17+
18+
import java.io.ByteArrayOutputStream;
19+
import java.io.IOException;
20+
import java.nio.ByteBuffer;
21+
import java.nio.charset.StandardCharsets;
22+
import java.time.Instant;
23+
import java.util.stream.Stream;
24+
25+
import static org.assertj.core.api.Assertions.assertThat;
26+
import static org.junit.jupiter.api.Assertions.assertEquals;
27+
import static org.junit.jupiter.api.Assertions.assertNull;
28+
29+
/**
30+
* Tests for the Avro-generated Flight class.
31+
*/
32+
public class FlightTest {
33+
34+
@Test
35+
public void testFlightCreationAndGetters() {
36+
// Given
37+
String flightNumber = "AA123";
38+
String airline = "American Airlines";
39+
String origin = "JFK";
40+
String destination = "LAX";
41+
long scheduledDeparture = Instant.now().toEpochMilli();
42+
Long actualDeparture = scheduledDeparture + 1800000; // 30 minutes later
43+
String status = "ON_TIME";
44+
45+
// When
46+
Flight flight = Flight.newBuilder()
47+
.setFlightNumber(flightNumber)
48+
.setAirline(airline)
49+
.setOrigin(origin)
50+
.setDestination(destination)
51+
.setScheduledDeparture(scheduledDeparture)
52+
.setActualDeparture(actualDeparture)
53+
.setStatus(status)
54+
.build();
55+
56+
// Then
57+
assertEquals(flightNumber, flight.getFlightNumber());
58+
assertEquals(airline, flight.getAirline());
59+
assertEquals(origin, flight.getOrigin());
60+
assertEquals(destination, flight.getDestination());
61+
assertEquals(scheduledDeparture, flight.getScheduledDeparture());
62+
assertEquals(actualDeparture, flight.getActualDeparture());
63+
assertEquals(status, flight.getStatus());
64+
}
65+
66+
@Test
67+
public void testNullableActualDeparture() {
68+
// Given
69+
Flight flight = Flight.newBuilder()
70+
.setFlightNumber("AA123")
71+
.setAirline("American Airlines")
72+
.setOrigin("JFK")
73+
.setDestination("LAX")
74+
.setScheduledDeparture(Instant.now().toEpochMilli())
75+
.setStatus("SCHEDULED")
76+
.build();
77+
78+
// Then
79+
assertNull(flight.getActualDeparture());
80+
}
81+
82+
@Test
83+
public void testEquals() {
84+
// Given
85+
long now = Instant.now().toEpochMilli();
86+
Flight flight1 = createSampleFlight("AA123", "JFK", "LAX", now, now + 1800000, "ON_TIME");
87+
Flight flight2 = createSampleFlight("AA123", "JFK", "LAX", now, now + 1800000, "ON_TIME");
88+
Flight flight3 = createSampleFlight("AA456", "JFK", "LAX", now, now + 1800000, "ON_TIME");
89+
90+
// Then
91+
assertThat(flight1).isEqualTo(flight2);
92+
assertThat(flight1).isNotEqualTo(flight3);
93+
}
94+
95+
@Test
96+
public void testHashCode() {
97+
// Given
98+
long now = Instant.now().toEpochMilli();
99+
Flight flight1 = createSampleFlight("AA123", "JFK", "LAX", now, now + 1800000, "ON_TIME");
100+
Flight flight2 = createSampleFlight("AA123", "JFK", "LAX", now, now + 1800000, "ON_TIME");
101+
102+
// Then
103+
assertThat(flight1.hashCode()).isEqualTo(flight2.hashCode());
104+
}
105+
106+
@Test
107+
public void testToString() {
108+
// Given
109+
Flight flight = createSampleFlight("AA123", "JFK", "LAX", 1614556800000L, 1614558600000L, "ON_TIME");
110+
111+
// Then
112+
String toString = flight.toString();
113+
assertThat(toString).contains("\"flightNumber\": \"AA123\"");
114+
assertThat(toString).contains("\"airline\": \"American Airlines\"");
115+
assertThat(toString).contains("\"origin\": \"JFK\"");
116+
assertThat(toString).contains("\"destination\": \"LAX\"");
117+
assertThat(toString).contains("\"scheduledDeparture\": 1614556800000");
118+
assertThat(toString).contains("\"actualDeparture\": 1614558600000");
119+
assertThat(toString).contains("\"status\": \"ON_TIME\"");
120+
}
121+
122+
@Test
123+
public void testSerializationDeserialization() throws IOException {
124+
// Given
125+
Flight originalFlight = createSampleFlight("AA123", "JFK", "LAX", 1614556800000L, 1614558600000L, "ON_TIME");
126+
127+
// When
128+
byte[] serialized = serializeToJson(originalFlight);
129+
Flight deserializedFlight = deserializeFromJson(serialized);
130+
131+
// Then
132+
assertThat(deserializedFlight).isEqualTo(originalFlight);
133+
}
134+
135+
@Test
136+
public void testBinarySerializationDeserialization() throws IOException {
137+
// Given
138+
Flight originalFlight = createSampleFlight("AA123", "JFK", "LAX", 1614556800000L, 1614558600000L, "ON_TIME");
139+
140+
// When
141+
byte[] serialized = serializeToBinary(originalFlight);
142+
Flight deserializedFlight = deserializeFromBinary(serialized);
143+
144+
// Then
145+
assertThat(deserializedFlight).isEqualTo(originalFlight);
146+
}
147+
148+
@Test
149+
public void testBuiltInEncoderDecoder() throws IOException {
150+
// Given
151+
Flight originalFlight = createSampleFlight("AA123", "JFK", "LAX", 1614556800000L, 1614558600000L, "ON_TIME");
152+
153+
// When - Using the built-in encoder/decoder from the Avro-generated class
154+
ByteBuffer byteBuffer = Flight.getEncoder().encode(originalFlight);
155+
byte[] serialized = new byte[byteBuffer.remaining()];
156+
byteBuffer.get(serialized);
157+
Flight deserializedFlight = Flight.getDecoder().decode(serialized);
158+
159+
// Then
160+
assertThat(deserializedFlight).isEqualTo(originalFlight);
161+
}
162+
163+
@ParameterizedTest
164+
@MethodSource("provideFlightStatusScenarios")
165+
public void testFlightWithDifferentStatuses(String status, Long actualDeparture) {
166+
// Given
167+
long scheduledDeparture = Instant.now().toEpochMilli();
168+
169+
// When
170+
Flight flight = Flight.newBuilder()
171+
.setFlightNumber("AA123")
172+
.setAirline("American Airlines")
173+
.setOrigin("JFK")
174+
.setDestination("LAX")
175+
.setScheduledDeparture(scheduledDeparture)
176+
.setActualDeparture(actualDeparture)
177+
.setStatus(status)
178+
.build();
179+
180+
// Then
181+
assertEquals(status, flight.getStatus());
182+
assertEquals(actualDeparture, flight.getActualDeparture());
183+
}
184+
185+
private static Stream<Arguments> provideFlightStatusScenarios() {
186+
long now = Instant.now().toEpochMilli();
187+
return Stream.of(
188+
Arguments.of("SCHEDULED", null),
189+
Arguments.of("BOARDING", null),
190+
Arguments.of("DEPARTED", now),
191+
Arguments.of("DELAYED", now + 3600000), // 1 hour delay
192+
Arguments.of("CANCELLED", null)
193+
);
194+
}
195+
196+
private Flight createSampleFlight(String flightNumber, String origin, String destination,
197+
long scheduledDeparture, Long actualDeparture, String status) {
198+
return Flight.newBuilder()
199+
.setFlightNumber(flightNumber)
200+
.setAirline("American Airlines")
201+
.setOrigin(origin)
202+
.setDestination(destination)
203+
.setScheduledDeparture(scheduledDeparture)
204+
.setActualDeparture(actualDeparture)
205+
.setStatus(status)
206+
.build();
207+
}
208+
209+
private byte[] serializeToJson(Flight flight) throws IOException {
210+
DatumWriter<Flight> writer = new SpecificDatumWriter<>(Flight.class);
211+
ByteArrayOutputStream out = new ByteArrayOutputStream();
212+
JsonEncoder encoder = EncoderFactory.get().jsonEncoder(Flight.getClassSchema(), out);
213+
writer.write(flight, encoder);
214+
encoder.flush();
215+
return out.toByteArray();
216+
}
217+
218+
private Flight deserializeFromJson(byte[] data) throws IOException {
219+
DatumReader<Flight> reader = new SpecificDatumReader<>(Flight.class);
220+
JsonDecoder decoder = DecoderFactory.get().jsonDecoder(
221+
Flight.getClassSchema(),
222+
new String(data, StandardCharsets.UTF_8)
223+
);
224+
return reader.read(null, decoder);
225+
}
226+
227+
private byte[] serializeToBinary(Flight flight) throws IOException {
228+
DatumWriter<Flight> writer = new SpecificDatumWriter<>(Flight.class);
229+
ByteArrayOutputStream out = new ByteArrayOutputStream();
230+
BinaryEncoder encoder = EncoderFactory.get().binaryEncoder(out, null);
231+
writer.write(flight, encoder);
232+
encoder.flush();
233+
return out.toByteArray();
234+
}
235+
236+
private Flight deserializeFromBinary(byte[] data) throws IOException {
237+
DatumReader<Flight> reader = new SpecificDatumReader<>(Flight.class);
238+
BinaryDecoder decoder = DecoderFactory.get().binaryDecoder(data, null);
239+
return reader.read(null, decoder);
240+
}
241+
}

0 commit comments

Comments
 (0)