diff --git a/src/main/java/no/entur/uttu/export/netex/NetexExportContext.java b/src/main/java/no/entur/uttu/export/netex/NetexExportContext.java index a2f33099..21704108 100644 --- a/src/main/java/no/entur/uttu/export/netex/NetexExportContext.java +++ b/src/main/java/no/entur/uttu/export/netex/NetexExportContext.java @@ -23,6 +23,7 @@ import java.util.concurrent.atomic.AtomicLong; import no.entur.uttu.export.model.AvailabilityPeriod; import no.entur.uttu.export.model.ServiceLinkExportContext; +import no.entur.uttu.model.Branding; import no.entur.uttu.model.DayType; import no.entur.uttu.model.DestinationDisplay; import no.entur.uttu.model.FlexibleStopPlace; @@ -62,6 +63,8 @@ public class NetexExportContext { public Set serviceLinks = new HashSet(); + public Set brandings = new HashSet<>(); + private Map idSequences = new HashMap<>(); private Export export; diff --git a/src/main/java/no/entur/uttu/export/netex/producer/NetexObjectFactory.java b/src/main/java/no/entur/uttu/export/netex/producer/NetexObjectFactory.java index 1b40ab26..a495f631 100644 --- a/src/main/java/no/entur/uttu/export/netex/producer/NetexObjectFactory.java +++ b/src/main/java/no/entur/uttu/export/netex/producer/NetexObjectFactory.java @@ -210,7 +210,8 @@ public CompositeFrame createCompositeFr public ResourceFrame createResourceFrame( NetexExportContext context, Collection authorities, - Collection operators + Collection operators, + Collection brandings ) { String resourceFrameId = NetexIdProducer.generateId(ResourceFrame.class, context); OrganisationsInFrame_RelStructure organisationsStruct = objectFactory @@ -229,11 +230,28 @@ public ResourceFrame createResourceFrame( .collect(Collectors.toList()) ); - return objectFactory + var resourceFrame = objectFactory .createResourceFrame() .withOrganisations(organisationsStruct) .withVersion(VERSION_ONE) .withId(resourceFrameId); + + if (!brandings.isEmpty()) { + TypesOfValueInFrame_RelStructure typesOfValueStruct = objectFactory + .createTypesOfValueInFrame_RelStructure() + .withValueSetOrTypeOfValue( + brandings + .stream() + .map(Branding_VersionStructure.class::cast) + .distinct() + .map(this::wrapAsJAXBElement) + .collect(Collectors.toList()) + ); + + resourceFrame = resourceFrame.withTypesOfValue(typesOfValueStruct); + } + + return resourceFrame; } private static Collector> toDistinctOrganisation() { diff --git a/src/main/java/no/entur/uttu/export/netex/producer/common/BrandingProducer.java b/src/main/java/no/entur/uttu/export/netex/producer/common/BrandingProducer.java new file mode 100644 index 00000000..1e05f6aa --- /dev/null +++ b/src/main/java/no/entur/uttu/export/netex/producer/common/BrandingProducer.java @@ -0,0 +1,36 @@ +package no.entur.uttu.export.netex.producer.common; + +import java.util.List; +import java.util.stream.Collectors; +import no.entur.uttu.export.netex.NetexExportContext; +import org.rutebanken.netex.model.Branding; +import org.rutebanken.netex.model.MultilingualString; +import org.springframework.stereotype.Component; + +@Component +public class BrandingProducer { + + public List produce(NetexExportContext context) { + return context.brandings + .stream() + .map(branding -> + new Branding() + .withId(branding.getNetexId()) + .withVersion("1") + .withName(new MultilingualString().withValue(branding.getName())) + .withShortName( + branding.getShortName() != null + ? new MultilingualString().withValue(branding.getShortName()) + : null + ) + .withDescription( + branding.getDescription() != null + ? new MultilingualString().withValue(branding.getDescription()) + : null + ) + .withUrl(branding.getUrl()) + .withImage(branding.getImageUrl()) + ) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/no/entur/uttu/export/netex/producer/common/NetexCommonFileProducer.java b/src/main/java/no/entur/uttu/export/netex/producer/common/NetexCommonFileProducer.java index b0071a29..51dbbfbb 100644 --- a/src/main/java/no/entur/uttu/export/netex/producer/common/NetexCommonFileProducer.java +++ b/src/main/java/no/entur/uttu/export/netex/producer/common/NetexCommonFileProducer.java @@ -27,6 +27,7 @@ import no.entur.uttu.model.Ref; import no.entur.uttu.util.ExportUtil; import org.rutebanken.netex.model.Authority; +import org.rutebanken.netex.model.Branding; import org.rutebanken.netex.model.CompositeFrame; import org.rutebanken.netex.model.DestinationDisplay; import org.rutebanken.netex.model.FlexibleStopAssignment; @@ -62,6 +63,7 @@ public class NetexCommonFileProducer { private final ServiceCalendarFrameProducer serviceCalendarFrameProducer; private final NetworkProducer networkProducer; private final ServiceLinkProducer serviceLinkProducer; + private final BrandingProducer brandingProducer; @Value("${export.blob.commonFileFilenameSuffix:_flexible_shared_data}") private String commonFileFilenameSuffix; @@ -72,7 +74,8 @@ public NetexCommonFileProducer( FlexibleStopPlaceProducer flexibleStopPlaceProducer, ServiceCalendarFrameProducer serviceCalendarFrameProducer, NetworkProducer networkProducer, - ServiceLinkProducer serviceLinkProducer + ServiceLinkProducer serviceLinkProducer, + BrandingProducer brandingProducer ) { this.objectFactory = objectFactory; this.organisationProducer = organisationProducer; @@ -80,6 +83,7 @@ public NetexCommonFileProducer( this.serviceCalendarFrameProducer = serviceCalendarFrameProducer; this.networkProducer = networkProducer; this.serviceLinkProducer = serviceLinkProducer; + this.brandingProducer = brandingProducer; } public NetexFile toCommonFile(NetexExportContext context) { @@ -112,7 +116,13 @@ public NetexFile toCommonFile(NetexExportContext context) { private ResourceFrame createResourceFrame(NetexExportContext context) { List netexOperators = organisationProducer.produceOperators(context); List netexAuthorities = organisationProducer.produceAuthorities(context); - return objectFactory.createResourceFrame(context, netexAuthorities, netexOperators); + List brandings = brandingProducer.produce(context); + return objectFactory.createResourceFrame( + context, + netexAuthorities, + netexOperators, + brandings + ); } private SiteFrame createSiteFrame(NetexExportContext context) { diff --git a/src/main/java/no/entur/uttu/export/netex/producer/line/LineProducer.java b/src/main/java/no/entur/uttu/export/netex/producer/line/LineProducer.java index 5c2256e6..0a25d1c3 100644 --- a/src/main/java/no/entur/uttu/export/netex/producer/line/LineProducer.java +++ b/src/main/java/no/entur/uttu/export/netex/producer/line/LineProducer.java @@ -27,6 +27,7 @@ import org.rutebanken.netex.model.AllVehicleModesOfTransportEnumeration; import org.rutebanken.netex.model.BookingAccessEnumeration; import org.rutebanken.netex.model.BookingMethodEnumeration; +import org.rutebanken.netex.model.BrandingRefStructure; import org.rutebanken.netex.model.FlexibleLineTypeEnumeration; import org.rutebanken.netex.model.Line_VersionStructure; import org.rutebanken.netex.model.NoticeAssignment; @@ -96,6 +97,13 @@ protected void mapCommon( ); context.networks.add(local.getNetwork()); context.notices.addAll(local.getNotices()); + + if (local.getBranding() != null) { + netex.setBrandingRef( + new BrandingRefStructure().withRef(local.getBranding().getNetexId()) + ); + context.brandings.add(local.getBranding()); + } } protected void mapBookingArrangements( diff --git a/src/main/java/no/entur/uttu/graphql/LinesGraphQLSchema.java b/src/main/java/no/entur/uttu/graphql/LinesGraphQLSchema.java index cee196c5..328678f2 100644 --- a/src/main/java/no/entur/uttu/graphql/LinesGraphQLSchema.java +++ b/src/main/java/no/entur/uttu/graphql/LinesGraphQLSchema.java @@ -27,6 +27,7 @@ import static graphql.schema.GraphQLObjectType.newObject; import static no.entur.uttu.graphql.GraphQLNames.*; +import graphql.GraphQL; import graphql.Scalars; import graphql.schema.DataFetcher; import graphql.schema.GraphQLArgument; @@ -60,6 +61,7 @@ import no.entur.uttu.graphql.scalars.LocalTimeScalar; import no.entur.uttu.model.BookingAccessEnumeration; import no.entur.uttu.model.BookingMethodEnumeration; +import no.entur.uttu.model.Branding; import no.entur.uttu.model.DayType; import no.entur.uttu.model.DayTypeAssignment; import no.entur.uttu.model.DirectionTypeEnumeration; @@ -79,6 +81,7 @@ import no.entur.uttu.model.job.ExportStatusEnumeration; import no.entur.uttu.model.job.SeverityEnumeration; import no.entur.uttu.profile.Profile; +import no.entur.uttu.repository.BrandingRepository; import no.entur.uttu.repository.DataSpaceCleaner; import no.entur.uttu.repository.DayTypeRepository; import no.entur.uttu.repository.ExportRepository; @@ -99,6 +102,9 @@ @Component public class LinesGraphQLSchema { + public static final String FIELD_BRANDING_REF = "brandingRef"; + public static final String FIELD_BRANDING = "branding"; + @Autowired private DateTimeScalar dateTimeScalar; @@ -165,6 +171,12 @@ public class LinesGraphQLSchema { @Autowired private DataFetcher routingFetcher; + @Autowired + private BrandingRepository brandingRepository; + + @Autowired + private DataFetcher brandingUpdater; + private GraphQLEnumType createEnum( String name, T[] values, @@ -269,6 +281,7 @@ private GraphQLEnumType createEnum( private GraphQLObjectType organisationObjectType; private GraphQLObjectType routeGeometryObjectType; private GraphQLObjectType serviceLinkObjectType; + private GraphQLObjectType brandingObjectType; private GraphQLArgument idArgument; private GraphQLArgument idsArgument; @@ -690,6 +703,16 @@ private void initCommonTypes() { ) .build(); + brandingObjectType = + newObject(identifiedEntityObjectType) + .name("Branding") + .field(newFieldDefinition().name("name").type(new GraphQLNonNull(GraphQLString))) + .field(newFieldDefinition().name("shortName").type(GraphQLString)) + .field(newFieldDefinition().name("description").type(GraphQLString)) + .field(newFieldDefinition().name("url").type(GraphQLString)) + .field(newFieldDefinition().name("imageUrl").type(GraphQLString)) + .build(); + lineObjectType = newObject(groupOfEntitiesObjectType) .name("Line") @@ -713,6 +736,7 @@ private void initCommonTypes() { .name(FIELD_NETWORK) .type(new GraphQLNonNull(networkObjectType)) ) + .field(newFieldDefinition().name(FIELD_BRANDING).type(brandingObjectType)) .field(newFieldDefinition().name(FIELD_OPERATOR_REF).type(GraphQLString)) .field( newFieldDefinition() @@ -1072,6 +1096,21 @@ private GraphQLObjectType createQueryObject() { .argument(idArgument) .dataFetcher(env -> networkRepository.getOne(env.getArgument(FIELD_ID))) ) + .field( + newFieldDefinition() + .type(new GraphQLList(brandingObjectType)) + .name("brandings") + .description("List of brandings") + .dataFetcher(env -> brandingRepository.findAll()) + ) + .field( + newFieldDefinition() + .type(brandingObjectType) + .name(FIELD_BRANDING) + .description("Get branding by id") + .argument(idArgument) + .dataFetcher(env -> brandingRepository.getOne(env.getArgument(FIELD_ID))) + ) .field( newFieldDefinition() .type(new GraphQLList(exportObjectType)) @@ -1209,6 +1248,15 @@ private GraphQLObjectType createMutationObject() { ) .build(); + GraphQLInputObjectType brandingInputType = newInputObject(identifiedEntityInputType) + .name("BrandingInput") + .field(newInputObjectField().name("name").type(new GraphQLNonNull(GraphQLString))) + .field(newInputObjectField().name("shortName").type(GraphQLString)) + .field(newInputObjectField().name("description").type(GraphQLString)) + .field(newInputObjectField().name("url").type(GraphQLString)) + .field(newInputObjectField().name("imageUrl").type(GraphQLString)) + .build(); + GraphQLInputObjectType keyValuesInputType = newInputObject() .name("KeyValuesInput") .field(newInputObjectField().name(FIELD_KEY).type(GraphQLString)) @@ -1494,6 +1542,7 @@ private GraphQLObjectType createMutationObject() { .name(FIELD_NETWORK_REF) .type(new GraphQLNonNull(GraphQLString)) ) + .field(newInputObjectField().name(FIELD_BRANDING_REF).type(GraphQLString)) .field(newInputObjectField().name(FIELD_OPERATOR_REF).type(GraphQLString)) .field( newInputObjectField() @@ -1567,6 +1616,24 @@ private GraphQLObjectType createMutationObject() { .argument(idArgument) .dataFetcher(networkUpdater) ) + .field( + newFieldDefinition() + .type(new GraphQLNonNull(brandingObjectType)) + .name("mutateBranding") + .description("Create new or update existing branding") + .argument( + GraphQLArgument.newArgument().name(FIELD_INPUT).type(brandingInputType) + ) + .dataFetcher(brandingUpdater) + ) + .field( + newFieldDefinition() + .type(new GraphQLNonNull(brandingObjectType)) + .name("deleteBranding") + .description("Delete an existing branding") + .argument(idArgument) + .dataFetcher(brandingUpdater) + ) .field( newFieldDefinition() .type(new GraphQLNonNull(lineObjectType)) diff --git a/src/main/java/no/entur/uttu/graphql/fetchers/BrandingUpdater.java b/src/main/java/no/entur/uttu/graphql/fetchers/BrandingUpdater.java new file mode 100644 index 00000000..8a069fec --- /dev/null +++ b/src/main/java/no/entur/uttu/graphql/fetchers/BrandingUpdater.java @@ -0,0 +1,54 @@ +package no.entur.uttu.graphql.fetchers; + +import graphql.schema.DataFetchingEnvironment; +import no.entur.uttu.error.codederror.EntityHasReferencesCodedError; +import no.entur.uttu.graphql.mappers.AbstractProviderEntityMapper; +import no.entur.uttu.model.Branding; +import no.entur.uttu.repository.FixedLineRepository; +import no.entur.uttu.repository.FlexibleLineRepository; +import no.entur.uttu.repository.generic.ProviderEntityRepository; +import no.entur.uttu.util.Preconditions; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service("brandingUpdater") +@Transactional +public class BrandingUpdater extends AbstractProviderEntityUpdater { + + private final FixedLineRepository fixedLineRepository; + private final FlexibleLineRepository flexibleLineRepository; + + public BrandingUpdater( + AbstractProviderEntityMapper mapper, + ProviderEntityRepository repository, + FixedLineRepository fixedLineRepository, + FlexibleLineRepository flexibleLineRepository + ) { + super(mapper, repository); + this.fixedLineRepository = fixedLineRepository; + this.flexibleLineRepository = flexibleLineRepository; + } + + @Override + protected Branding deleteEntity(DataFetchingEnvironment env) { + return super.deleteEntity(env); + } + + @Override + protected void verifyDeleteAllowed(String id) { + var branding = repository.getOne(id); + if (branding != null) { + long noOfLines = + fixedLineRepository.countByBranding(branding) + + flexibleLineRepository.countByBranding(branding); + Preconditions.checkArgument( + noOfLines == 0, + EntityHasReferencesCodedError.fromNumberOfReferences((int) noOfLines), + "%s cannot be deleted as it is referenced by %s line(s)", + branding.identity(), + noOfLines + ); + } + super.verifyDeleteAllowed(id); + } +} diff --git a/src/main/java/no/entur/uttu/graphql/mappers/BrandingMapper.java b/src/main/java/no/entur/uttu/graphql/mappers/BrandingMapper.java new file mode 100644 index 00000000..65af2750 --- /dev/null +++ b/src/main/java/no/entur/uttu/graphql/mappers/BrandingMapper.java @@ -0,0 +1,32 @@ +package no.entur.uttu.graphql.mappers; + +import no.entur.uttu.graphql.ArgumentWrapper; +import no.entur.uttu.model.Branding; +import no.entur.uttu.repository.ProviderRepository; +import no.entur.uttu.repository.generic.ProviderEntityRepository; +import org.springframework.stereotype.Component; + +@Component +public class BrandingMapper extends AbstractProviderEntityMapper { + + public BrandingMapper( + ProviderRepository providerRepository, + ProviderEntityRepository entityRepository + ) { + super(providerRepository, entityRepository); + } + + @Override + protected Branding createNewEntity(ArgumentWrapper input) { + return new Branding(); + } + + @Override + protected void populateEntityFromInput(Branding entity, ArgumentWrapper input) { + input.apply("name", entity::setName); + input.apply("shortName", entity::setShortName); + input.apply("description", entity::setDescription); + input.apply("url", entity::setUrl); + input.apply("imageUrl", entity::setImageUrl); + } +} diff --git a/src/main/java/no/entur/uttu/graphql/mappers/LineMapper.java b/src/main/java/no/entur/uttu/graphql/mappers/LineMapper.java index 06197b06..5babe5af 100644 --- a/src/main/java/no/entur/uttu/graphql/mappers/LineMapper.java +++ b/src/main/java/no/entur/uttu/graphql/mappers/LineMapper.java @@ -2,10 +2,12 @@ import static no.entur.uttu.graphql.GraphQLNames.*; import static no.entur.uttu.graphql.GraphQLNames.FIELD_NOTICES; +import static no.entur.uttu.graphql.LinesGraphQLSchema.FIELD_BRANDING_REF; import no.entur.uttu.graphql.ArgumentWrapper; import no.entur.uttu.model.Line; import no.entur.uttu.organisation.spi.OrganisationRegistry; +import no.entur.uttu.repository.BrandingRepository; import no.entur.uttu.repository.NetworkRepository; import no.entur.uttu.repository.ProviderRepository; import no.entur.uttu.repository.generic.ProviderEntityRepository; @@ -28,6 +30,9 @@ public abstract class LineMapper @Autowired private OrganisationRegistry organisationRegistry; + @Autowired + private BrandingRepository brandingRepository; + public LineMapper( ProviderRepository providerRepository, ProviderEntityRepository repository @@ -42,6 +47,7 @@ protected void populateEntityFromInput(T entity, ArgumentWrapper input) { input.apply(FIELD_TRANSPORT_MODE, entity::setTransportMode); input.apply(FIELD_TRANSPORT_SUBMODE, entity::setTransportSubmode); input.applyReference(FIELD_NETWORK_REF, networkRepository, entity::setNetwork); + input.applyReference(FIELD_BRANDING_REF, brandingRepository, entity::setBranding); input.apply( FIELD_OPERATOR_REF, diff --git a/src/main/java/no/entur/uttu/model/Branding.java b/src/main/java/no/entur/uttu/model/Branding.java new file mode 100644 index 00000000..3dcb8ea5 --- /dev/null +++ b/src/main/java/no/entur/uttu/model/Branding.java @@ -0,0 +1,75 @@ +package no.entur.uttu.model; + +import jakarta.persistence.Entity; +import no.entur.uttu.util.Preconditions; + +@Entity +public class Branding extends ProviderEntity { + + private String name; + private String shortName; + private String description; + private String url; + private String imageUrl; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getShortName() { + return shortName; + } + + public void setShortName(String shortName) { + this.shortName = shortName; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getImageUrl() { + return imageUrl; + } + + public void setImageUrl(String imageUrl) { + this.imageUrl = imageUrl; + } + + @Override + public boolean equals(Object o) { + return super.equals(o); + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public void checkPersistable() { + super.checkPersistable(); + + Preconditions.checkArgument( + getName() != null && !getName().isEmpty(), + "%s branding must have a non-empty name", + this + ); + } +} diff --git a/src/main/java/no/entur/uttu/model/Line.java b/src/main/java/no/entur/uttu/model/Line.java index 57ef8786..4aa0fc93 100644 --- a/src/main/java/no/entur/uttu/model/Line.java +++ b/src/main/java/no/entur/uttu/model/Line.java @@ -66,6 +66,9 @@ public abstract class Line extends GroupOfEntities_VersionStructure { @OneToMany(mappedBy = "line", cascade = CascadeType.ALL, orphanRemoval = true) private final List journeyPatterns = new ArrayList<>(); + @ManyToOne + private Branding branding; + public String getPublicCode() { return publicCode; } @@ -126,6 +129,14 @@ public void setTransportSubmode(VehicleSubmodeEnumeration transportSubmode) { this.transportSubmode = transportSubmode; } + public Branding getBranding() { + return branding; + } + + public void setBranding(Branding branding) { + this.branding = branding; + } + @Override public boolean isValid(LocalDate from, LocalDate to) { return ( diff --git a/src/main/java/no/entur/uttu/repository/BrandingRepository.java b/src/main/java/no/entur/uttu/repository/BrandingRepository.java new file mode 100644 index 00000000..5fb611c1 --- /dev/null +++ b/src/main/java/no/entur/uttu/repository/BrandingRepository.java @@ -0,0 +1,21 @@ +/* + * Licensed under the EUPL, Version 1.2 or – as soon they will be approved by + * the European Commission - subsequent versions of the EUPL (the "Licence"); + * You may not use this work except in compliance with the Licence. + * You may obtain a copy of the Licence at: + * + * https://joinup.ec.europa.eu/software/page/eupl + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the Licence is distributed on an "AS IS" basis, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the Licence for the specific language governing permissions and + * limitations under the Licence. + */ + +package no.entur.uttu.repository; + +import no.entur.uttu.model.Branding; +import no.entur.uttu.repository.generic.ProviderEntityRepository; + +public interface BrandingRepository extends ProviderEntityRepository {} diff --git a/src/main/java/no/entur/uttu/repository/FixedLineRepository.java b/src/main/java/no/entur/uttu/repository/FixedLineRepository.java index 0f6e9181..a7d31689 100644 --- a/src/main/java/no/entur/uttu/repository/FixedLineRepository.java +++ b/src/main/java/no/entur/uttu/repository/FixedLineRepository.java @@ -15,10 +15,12 @@ package no.entur.uttu.repository; +import no.entur.uttu.model.Branding; import no.entur.uttu.model.FixedLine; import no.entur.uttu.model.Network; import no.entur.uttu.repository.generic.ProviderEntityRepository; public interface FixedLineRepository extends ProviderEntityRepository { int countByNetwork(Network network); + int countByBranding(Branding branding); } diff --git a/src/main/java/no/entur/uttu/repository/FlexibleLineRepository.java b/src/main/java/no/entur/uttu/repository/FlexibleLineRepository.java index 68a711b7..af813292 100644 --- a/src/main/java/no/entur/uttu/repository/FlexibleLineRepository.java +++ b/src/main/java/no/entur/uttu/repository/FlexibleLineRepository.java @@ -15,10 +15,12 @@ package no.entur.uttu.repository; +import no.entur.uttu.model.Branding; import no.entur.uttu.model.FlexibleLine; import no.entur.uttu.model.Network; import no.entur.uttu.repository.generic.ProviderEntityRepository; public interface FlexibleLineRepository extends ProviderEntityRepository { int countByNetwork(Network network); + int countByBranding(Branding branding); } diff --git a/src/main/resources/db/migration/V18__Create_branding_table.sql b/src/main/resources/db/migration/V18__Create_branding_table.sql new file mode 100644 index 00000000..d92b2b7b --- /dev/null +++ b/src/main/resources/db/migration/V18__Create_branding_table.sql @@ -0,0 +1,36 @@ +CREATE TABLE branding ( + pk bigint NOT NULL, + changed timestamp without time zone NOT NULL, + changed_by character varying(255) NOT NULL, + created timestamp without time zone NOT NULL, + created_by character varying(255) NOT NULL, + version bigint NOT NULL, + netex_id character varying(255) NOT NULL, + provider_pk bigint NOT NULL, + name character varying(255), + short_name character varying(255), + description character varying(255), + url character varying(255), + image_url character varying(255) +); + +ALTER TABLE branding OWNER TO uttu; + +ALTER TABLE ONLY branding + ADD CONSTRAINT branding_pkey PRIMARY KEY (pk); + +CREATE SEQUENCE branding_seq + START WITH 1 + INCREMENT BY 10 + NO MINVALUE + NO MAXVALUE + CACHE 1; + +ALTER TABLE branding_seq + OWNER TO uttu; + +ALTER TABLE ONLY line ADD COLUMN + branding_pk bigint; + +ALTER TABLE ONLY line + ADD CONSTRAINT line_branding_fk FOREIGN KEY (branding_pk) REFERENCES branding(pk); diff --git a/src/test/java/no/entur/uttu/integration/BrandingGraphQLIntegrationTest.java b/src/test/java/no/entur/uttu/integration/BrandingGraphQLIntegrationTest.java new file mode 100644 index 00000000..54d1c221 --- /dev/null +++ b/src/test/java/no/entur/uttu/integration/BrandingGraphQLIntegrationTest.java @@ -0,0 +1,123 @@ +package no.entur.uttu.integration; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Map; +import org.junit.Test; +import org.springframework.graphql.test.tester.GraphQlTester; + +public class BrandingGraphQLIntegrationTest extends AbstractGraphQLIntegrationTest { + + @Test + public void testCreateUpdateDeleteBranding() { + var input = InputGenerators.generateBrandingInput("TestBrand"); + + GraphQlTester.Response response = graphQlTester + .documentName("mutateBranding") + .variable("branding", input) + .execute(); + + String brandingId = response.path("mutateBranding.id").entity(String.class).get(); + + response = + graphQlTester.documentName("branding").variable("id", brandingId).execute(); + + response + .path("branding.id") + .entity(String.class) + .matches(id -> id.startsWith("TST:Branding:")); + + response + .path("branding.name") + .entity(String.class) + .matches(name -> name.equals("TestBrand")); + + input = InputGenerators.generateBrandingInputForEdit(brandingId, "AnotherTestBrand"); + + graphQlTester.documentName("mutateBranding").variable("branding", input).execute(); + + graphQlTester + .documentName("branding") + .variable("id", brandingId) + .execute() + .path("branding.name") + .entity(String.class) + .matches(name -> name.equals("AnotherTestBrand")); + + graphQlTester + .documentName("deleteBranding") + .variable("id", brandingId) + .execute() + .path("deleteBranding.id") + .entity(String.class) + .matches(id -> id.equals(brandingId)); + } + + @Test + public void testUnableToDeleteBrandingUsedByLine() { + String brandingId = createBrandingWithName("TestBrand") + .path("mutateBranding.id") + .entity(String.class) + .get(); + + createFixedLine("TestLine", brandingId); + + graphQlTester + .documentName("deleteBranding") + .variable("id", brandingId) + .execute() + .errors() + .satisfy(errors -> + assertThat(errors) + .anyMatch(error -> + error.getExtensions().get("code").equals("ENTITY_IS_REFERENCED") && + ((Map) error.getExtensions().get("metadata")).get( + "numberOfReferences" + ) + .equals(1) + ) + ); + } + + private GraphQlTester.Response createFixedLine(String name, String brandingId) { + String networkId = createNetworkWithName(name) + .path("mutateNetwork.id") + .entity(String.class) + .get(); + + String dayTypeRef = createDayType() + .path("mutateDayType.id") + .entity(String.class) + .get(); + + var input = InputGenerators.generateFixedLineInput( + name, + networkId, + dayTypeRef, + brandingId + ); + return graphQlTester + .documentName("mutateFixedLine") + .variable("input", input) + .execute(); + } + + private GraphQlTester.Response createDayType() { + var input = InputGenerators.generateDayTypeInput(); + return graphQlTester.documentName("mutateDayType").variable("input", input).execute(); + } + + private GraphQlTester.Response createNetworkWithName(String name) { + return graphQlTester + .documentName("mutateNetwork") + .variable("network", InputGenerators.generateNetworkInput(name, "NOG:Authority:1")) + .execute(); + } + + private GraphQlTester.Response createBrandingWithName(String name) { + return graphQlTester + .documentName("mutateBranding") + .variable("branding", InputGenerators.generateBrandingInput(name)) + .execute(); + } +} diff --git a/src/test/java/no/entur/uttu/integration/FixedLineGraphQLIntegrationTest.java b/src/test/java/no/entur/uttu/integration/FixedLineGraphQLIntegrationTest.java index 5fa7fe08..ea7beed4 100644 --- a/src/test/java/no/entur/uttu/integration/FixedLineGraphQLIntegrationTest.java +++ b/src/test/java/no/entur/uttu/integration/FixedLineGraphQLIntegrationTest.java @@ -46,7 +46,17 @@ private GraphQlTester.Response createFixedLine(String name) { .entity(String.class) .get(); - var input = InputGenerators.generateFixedLineInput(name, networkId, dayTypeRef); + String brandingId = createBrandingWithName(name) + .path("mutateBranding.id") + .entity(String.class) + .get(); + + var input = InputGenerators.generateFixedLineInput( + name, + networkId, + dayTypeRef, + brandingId + ); return graphQlTester .documentName("mutateFixedLine") .variable("input", input) @@ -64,4 +74,11 @@ private GraphQlTester.Response createNetworkWithName(String name) { .variable("network", InputGenerators.generateNetworkInput(name, "NOG:Authority:1")) .execute(); } + + private GraphQlTester.Response createBrandingWithName(String name) { + return graphQlTester + .documentName("mutateBranding") + .variable("branding", InputGenerators.generateBrandingInput(name)) + .execute(); + } } diff --git a/src/test/java/no/entur/uttu/integration/InputGenerators.java b/src/test/java/no/entur/uttu/integration/InputGenerators.java index 4d21ee79..e6b06dca 100644 --- a/src/test/java/no/entur/uttu/integration/InputGenerators.java +++ b/src/test/java/no/entur/uttu/integration/InputGenerators.java @@ -81,6 +81,70 @@ public static String generateDateString(LocalDate date) { ); } + public static @NotNull Map generateFixedLineInput( + String name, + String networkId, + String dayTypeRef, + String brandingId + ) { + return Map.of( + "name", + name, + "publicCode", + "TestFixedLine", + "transportMode", + "bus", + "transportSubmode", + "localBus", + "networkRef", + networkId, + "brandingRef", + brandingId, + "operatorRef", + "NOG:Operator:1", + "journeyPatterns", + List.of( + Map.of( + "pointsInSequence", + List.of( + Map.of( + "quayRef", + "NSR:Quay:494", + "destinationDisplay", + Map.of("frontText", "Første stopp") + ), + Map.of("quayRef", "NSR:Quay:563") + ), + "serviceJourneys", + List.of( + Map.of( + "name", + "Hverdager3-" + System.currentTimeMillis(), + "dayTypesRefs", + List.of(dayTypeRef), + "passingTimes", + List.of( + Map.of("departureTime", "07:00:00"), + Map.of("arrivalTime", "07:15:00") + ) + ) + ) + ) + ) + ); + } + + public static @NotNull Map generateBrandingInputForEdit( + String id, + String name + ) { + return Map.of("id", id, "name", name); + } + + public static @NotNull Map generateBrandingInput(String name) { + return Map.of("name", name); + } + public static @NotNull Map generateNetworkInput( String name, String authorityRef diff --git a/src/test/java/no/entur/uttu/model/BrandingTest.java b/src/test/java/no/entur/uttu/model/BrandingTest.java new file mode 100644 index 00000000..00b047c5 --- /dev/null +++ b/src/test/java/no/entur/uttu/model/BrandingTest.java @@ -0,0 +1,28 @@ +package no.entur.uttu.model; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class BrandingTest { + + @Test + void testCheckPersistableThrowsWithEmptyName() { + var subject = new Branding(); + subject.setName(""); + Assertions.assertThrows(IllegalArgumentException.class, subject::checkPersistable); + } + + @Test + void testCheckPersistableThrowsWithNullName() { + var subject = new Branding(); + subject.setName(null); + Assertions.assertThrows(IllegalArgumentException.class, subject::checkPersistable); + } + + @Test + void testCheckPersistable() { + var subject = new Branding(); + subject.setName("test"); + Assertions.assertDoesNotThrow(subject::checkPersistable); + } +} diff --git a/src/test/resources/graphql-test/branding.graphql b/src/test/resources/graphql-test/branding.graphql new file mode 100644 index 00000000..fc01dcfc --- /dev/null +++ b/src/test/resources/graphql-test/branding.graphql @@ -0,0 +1,6 @@ +query GetBranding($id: ID!) { + branding(id: $id) { + id + name + } +} diff --git a/src/test/resources/graphql-test/deleteBranding.graphql b/src/test/resources/graphql-test/deleteBranding.graphql new file mode 100644 index 00000000..6faf4083 --- /dev/null +++ b/src/test/resources/graphql-test/deleteBranding.graphql @@ -0,0 +1,5 @@ +mutation DeleteBranding($id: ID!) { + deleteBranding(id: $id) { + id + } +} diff --git a/src/test/resources/graphql-test/mutateBranding.graphql b/src/test/resources/graphql-test/mutateBranding.graphql new file mode 100644 index 00000000..353ca78b --- /dev/null +++ b/src/test/resources/graphql-test/mutateBranding.graphql @@ -0,0 +1,6 @@ +mutation mutateBranding($branding: BrandingInput!) { + mutateBranding(input: $branding) { + id + name + } +}